Hướng dẫn tạo menu dạng cây phân cấp bằng Javascript và CSS
1. Mô tả menu dạng cây phân cấpMenu dạng cây phân cấp hiện nay được sử dụng khá nhiều ở các trang web. Điểm đặc biệt của nó là chủ yếu được viết bằng Javascript nên có thể chạy trên các trang web tĩnh. Nó có khá nhiều lợi ích như tiết kiệm diện tích khi cần thiết, tăng tính tương tác với người dùng. Menu dạng cây phân cấp có dạng như hình sau:
Ảnh đang được tải lên...
Mỗi phần tử của menu có thể là 1 điểm cuối, hoặc cũng có thể chính là 1 menu, nghĩa là bên trong nó còn có thể chứa 1 hoặc nhiều phần tử khác, bản thân các phần tử này lại có thể là điểm cuối hay 1 menu khác. Điểm cuối ở đây hiểu là 1 phần tử mà trong nó không có phần tử nào. Cấu trúc này cho phép chúng ta xây dựng menu dạng cây phân cấp có số cấp vô hạn. Trong hình vẽ trên điểm cuối được biểu thị bằng hình file, còn menu được biểu thị bằng hình thư mục.
Khi nhấn lên 1 menu thì các mục trong nó sẽ được mở ra, nhấn thêm 1 lần nữa thì menu đó sẽ được đóng lại. Còn nhấn lên 1 điểm cuối thì chúng ta sẽ thực hiện 1 tác vụ nào đó cần thiết.
2. Mô tả cấu trúc dữ liệu biểu diễn menu dạng cây phân cấp
Mỗi 1 phần tử của menu trên các trang web thông thường có các thuộc tính sau:
- name: dùng để ghi tên của phần tử đó và cũng chính là tên khi hiển thị phần tử đó trên trình duyệt.
- url: thường được dùng trong các điểm cuối khi muốn hiển thị url ở 1 vị trí của trang web hay chuyển đến trang khác.
- desc: là phần mô tả 1 phần tử của menu. Trong hình trên phần này không có, tuy nhiên khi thiết kế những menu mà mỗi 1 phần tử chứa trong nó những dữ liệu phức tạp như form đăng kí, hình ảnh, ... thì phần này rất cần thiết.
- list: nếu phần tử đó là 1 menu thì cần phải có 1 danh sách các phần tử con của nó.
Trong các thuộc tính trên, thuộc tính name và list gần như là bắt buộc, url và desc là tuy chọn, có thể có hoặc không. Ngoài các thuộc tính này, các bạn có thể thêm vào menu của mình 1 số thuộc tính khác như ID chẳng hạn, tùy vào hoàn cảnh và mục đích sử dụng.
Như vậy thì mô hình chung của 1 phần tử menu có dạng sau:
menu
- name: 'Menu Element Name';
- url: 'Menu Element Link';
- desc: 'Something about this element';
- list: menu, menu, ...; // nếu có
Dữ liệu dạng phân cấp này thông thường có thể biểu diễn theo 2 cách: XML và JSON (JavaScript Object Notation). Cách biểu diễn dạng XML uyển chuyển hơn trong việc xóa, bổ xung dữ liệu và khá đơn giản với những người không phải là coder. Còn cách biểu diễn JSON quen thuộc với những coder hơn, dễ xảy ra những lỗi cú pháp khi biểu diễn nhưng lại dễ dàng thao tác với dữ liệu hơn. Với 1 đối tượng viết ở dạng JSON thì chúng ta có thể thao tác trực tiếp với các phần tử trong đó mà không gặp rắc rối, còn với XML các bạn có thể sẽ nghĩ đến ngay Ajax để lấy dữ liệu và thao tác trên từng DOM element của nó để trích xuất dữ liệu ra.
Ở đây chúng ta dùng JavaScript là chủ yếu nên sẽ dùng JSON để tiện thao tác. Như thế thì dữ liệu của menu chúng ta có thể mô tả dạng như sau:
HTML Code:
var menuData = {
name: 'Main menu',
list: [
{
name: 'Sub menu 1',
url: 'http://linksubmenu1.com',
list: [
{name: 'Sub sub menu 1', url: 'http://linksubsubmenu1'},
{name: 'Sub sub menu 2', url: 'http://linksubsubmenu2'}
]
},
{
name: 'Sub menu 2',
url: 'http://linksubmenu2.com',
list: [
{name: 'Sub sub menu 1', url: 'http://linksubsubmenu1'},
{name: 'Sub sub menu 2', url: 'http://linksubsubmenu2'}
]
}
]
};
Như vậy phần biểu diễn dữ liệu cho menu đã xong. Phần này khá quan trọng, vì dữ liệu sẽ quyết định cách chúng ta làm việc sau này. Nếu biểu diễn không thống nhất sẽ dẫn đến các phần sau không biết nên làm thế nào và có nguy cơ phải biểu diễn lại.
3. Biểu diễn các phần tử của menu
Ở phần trên chúng ta đã biểu diễn dữ liệu của menu. Phần đó cần được tách riêng biệt để dễ bổ xung, sửa đổi khi cần thiết. Nhiệm vụ của chúng ta là từ dữ liệu đó tạo ra các đối tượng có các phương thức biểu diễn cần thiết để thao tác với chúng.
Vì thế, chúng ta xây dựng 1 đối tượng gọi là menuTree dùng để mô phỏng menu của chúng ta dựa trên dữ liệu được cho bởi 1 đối tượng menuData được viết theo cú pháp JSON. Hàm tạo của đối tượng menuTree sẽ như sau:
HTML Code:
// Create a menu from an object written in JSON format
function menuTree(menuData) {
this.name = menuData.name;
this.url = (menuData.url) ? menuData.url : '' ;
this.desc = (menuData.desc) ? menuData.desc : '';
// If it's a cursive menu, create a list of submenu and add it
if (menuData.list) {
var list = new Array();
for (var i = 0; i < menuData.list.length; i++) {
list[i] = new menuTree(menuData.list[i]);
list[i].parent = this;
}
this.list = new Array();
this.addList(list);
}
// Create DOM node for menu
this.render();
}
Sau khi sinh ra hết các phần tử rồi, chúng ta đưa nó vào 1 mảng tên list và bổ xung nó vào đối tượng của chúng ta qua hàm addList(). Hàm addList() đuwọc viết như sau:
HTML Code:
// Add a list of items to menu
menuTree.prototype.addList = function(list) {
if (this.isMenu() && list.length > 0) {
var n = this.list.length;
for (var i = 0; i < list.length; i++, n++) {
this.list[n] = list[i];
}
}
}
Như vậy toàn bộ dữ liệu từ menuData đã được 'sao chép' qua đối tượng của chúng ta. Có thể bạn tự hỏi tại sao phải sao chép như vậy mà không dùng menuData làm 1 đối tượng của chúng ta luôn và thao tác trên đó? Câu trả lời là chúng ta có thể làm như thế, nhưng với mỗi dữ liệu khác nhau chúng ta lại phải có các hàm xử lí tương ứng, mà không thể nhóm chúng lại thành 1 đối tượng chung để dùng, sẽ rất phức tạp và rối nếu như bạn muốn làm 1 lúc 3-4 menu chẳng hạn. Hơn nữa việc biểu diễn dữ liệu độc lập sẽ giúp chúng ta tùy biến dữ liệu hơn, chỉnh sửa, xóa mà không ảnh hưởng đến các chức năng phía sau và cũng không ảnh hưởng đến các đối tượng menu khác.
4. Thao tác với các DOM node
Muốn hiển thị được menu của chúng ta trên trình duyệt, chúng ta phải 'viết' nội dung của nó ra. Có khá nhiều cách để đưa nội dung vào trang web như dùng hàm document.write(), bổ xung các DOM node bằng appendChild(), hay qua thuộc tính innerHTML, ... Chúng ta sẽ dùng cách thao tác với DOM node, bởi chúng khá giống với các node trong menu của chúng ta, đồng thời nó cũng tránh được các lỗi hiển thị khi sử dụng các phương thức kia (vì viết code HTML trực tiếp sẽ làm rối và dễ có lỗi cú pháp cũng như khó sửa chữa).
Để sinh ra các DOM node tương ứng với các phần tử trong 1 menu, chúng ta dùng hàm render() như sau:
HTML Code:
// Create DOM node for all menu elements
menuTree.prototype.render = function() {
this.html = document.createElement('div');
this.html.className = 'menuElement';
// Create node for header
this.header = document.createElement('a');
this.header.setAttribute('href', 'javascript:void(0)');
this.header.me = this;
this.header.onclick = (this.isMenu()) ? showHide : clickHandler;
this.header.appendChild(document.createTextNode(this.name));
// Create node for body
this.body = document.createElement('div');
this.body.className = 'menuBody';
this.body.appendChild(document.createTextNode(this.desc));
// Append header and body
this.html.appendChild(this.header);
this.html.appendChild(this.body);
// If it has submenus, create DOM node for each item recusively
if (!this.isMenu()) {
return;
}
for (var i = 0; i < this.list.length; i++) {
this.list[i].render();
this.body.appendChild(this.list[i].html);
}
}
2 dòng đầu tiên chúng ta sinh ra DOM node div tương ứng với html, và gán cho nó thuộc tính lớp CSS là menuElement.
Đoạn tiếp theo chúng ta sinh ra DOM node a tương ứng với header. Đây chính là tên của menu khi hiển thị trên trình duyệt. Sở dĩ ta không dùng div như trước là vì phần header của menu có thể click được, dùng a là tiện nhất. Tất nhiên bạn có thể dùng CSS để tùy biến, tuy nhiên có 1 vài vấn đề không tương thích giữa các trình duyệt như việc đổi màu sắc tiêu đề khi di chuột lên chẳng hạn (Firefox và IE không hoạt động, Opera thì hoạt động). Chúng ta gán cho nó thuộc tính href = javascript(0) để nó không nhảy đi đâu cả. Dòng code:
this.header.onclick = (this.isMenu()) ? showHide : clickHandler;
rất quan trọng, nó sẽ nhận biết sự kiện khi chúng ta click lên tiêu đề của menu và đưa ra các tác vụ tương ứng. Dễ thấy là nếu như phần tử đang thao tác là 1 menu (tức là có các phần tử con) thì công việc của chúng ta là đóng / mở menu đó, tức là hiển thị hay không hiển thị các phần tử con đó thông qua hàm showHide(). Còn nếu đây không phải là menu, tức là 1 điểm cuối, thì chúng ta truyền quyền kiểm soát cho hàm clickHandler(). Hàm showHide() là chung cho mọi menu nên chúng ta sẽ code cho nó, còn hàm clickHandler() thì tùy vào trường hợp mà bạn code cho nó thế nào:
HTML Code:
// Handler when click on menu items
function clickHandler() {
var obj = this.me;
// Do something here
}
HTML Code:
menuTree.prototype.isMenu = function() {
return (this.list) ? true : false;
}
this.header.appendChild(document.createTextNode(th is.name));
Ở đoạn code tạo header này còn có câu lệnh:
this.header.me = this;
Lệnh này chúng ta sử dụng để tham chiếu ngược đến đối tượng hiện thời (bạn sẽ thấy nó hữu dụng ở hàm showHide() phía sau).
Đoạn tiếp theo là tạo ra 1 DOM node div tương ứng với body, phần thân của menu. Chúng ta cũng gán cho nó thuộc tính lớp CSS là menuBody và bổ xung phần desc vào đó.
Như vậy ta đã cơ bản tạo các DOM node cho 1 phần tử của menu. Nhưng còn các phần tử con (nếu có) thì sao? Đó là điều mà đoạn code tiếp theo đã làm:
HTML Code:
// If it has submenus, create DOM node for each item recusively
if (!this.isMenu()) {
return;
}
for (var i = 0; i < this.list.length; i++) {
this.list[i].render();
this.body.appendChild(this.list[i].html);
}
Hàm render() này nên được chạy ngay khi khởi tạo đối tượng, vì thế chúng ta đưa nó vào thân của hàm tạo, chúng ta có hàm tạo cuối cùng như sau:
HTML Code:
// Create DOM node for all menu elements
menuTree.prototype.render = function() {
this.html = document.createElement('div');
this.html.className = 'menuElement';
// Create node for header
this.header = document.createElement('a');
this.header.setAttribute('href', 'javascript:void(0)');
this.header.me = this;
this.header.onclick = (this.isMenu()) ? showHide : clickHandler;
this.header.appendChild(document.createTextNode(this.name));
// Create node for body
this.body = document.createElement('div');
this.body.className = 'menuBody';
this.body.appendChild(document.createTextNode(this.desc));
// Append header and body
this.html.appendChild(this.header);
this.html.appendChild(this.body);
// If it has submenus, create DOM node for each item recusively
if (!this.isMenu()) {
return;
}
for (var i = 0; i < this.list.length; i++) {
this.list[i].render();
this.body.appendChild(this.list[i].html);
}
}
HTML Code:
// Toogle menu on/off
function showHide() {
var obj = this.me;
if (!obj.isMenu()) {
return;
}
if (obj.isOpened()) {
obj.hideBody();
} else {
obj.showBody();
}
}
Đoạn lệnh sau cũng đơn giản: nếu phần tử đó không phải là menu thì thoát ra, nếu là menu thì kiểm tra xem nó mở hay không để đóng / mở nó tương ứng. Các hàm isOpened(), hideBody(), showBody() được viết như sau:
HTML Code:
menuTree.prototype.isOpened = function() {
return (this.body.style.display == '');
}
menuTree.prototype.hideBody = function() {
this.body.style.display = 'none';
this.header.className = 'menuHeaderClosed';
}
menuTree.prototype.showBody = function() {
this.body.style.display = '';
this.header.className = 'menuHeaderOpened';
}
Như vậy về cơ bản, menu của chúng ta đã xây dựng xong. Bây giờ chúng ta xây dựng hàm để hiển thị menu đó ra trình duyệt. Để hiển thị menu, chúng ta cần 1 vị trí được định danh id="..." để hiển thị ở đó. Ngoài ra, ta cũng xây dựng hàm cho phép menu ở trạng thái đóng hoặc mở khi hiển thị. Hàm của chúng ta có dạng sau:
HTML Code:
// Show menu at place indentified by containerID
// showAll is defaulted by false
menuTree.prototype.show = function(containerID, showAll) {
this.prepare(showAll);
var container = $(containerID);
container.innerHTML = '';
container.appendChild(this.html);
}
HTML Code:
// Reference to DOM node
function $(id) {
return document.getElementById(id);
}
Bây giờ chúng ta sẽ xây dựng hàm prepare(), nó có dạng như sau:
HTML Code:
// Prepare menu before show it
// showAll is defaulted by false
menuTree.prototype.prepare = function(showAll) {
if (!this.isMenu()) {
this.header.className = 'itemHeader';
return;
}
showAll = (typeof(showAll) == 'undefined') ? false : true;
this.header.className = (showAll) ? 'menuHeaderOpened' : 'menuHeaderClosed';
this.body.style.display = (showAll) ? '' : 'none';
for (var i = 0; i < this.list.length; i++) {
this.list[i].prepare();
}
}
Việc xây dựng menu dạng cây phân cấp như vậy đã hoàn tất. Để tạo 1 menu cây phân cấp dạng này, bạn chỉ đơn thuần thực hiện các bước sau:
- Tạo phần dữ liệu cho biến menuData (hoặc tên khác, tùy ý), nhớ là viết theo quy ước JSON:
var menuData = {}
- Tạo 1 đối tượng menuTree dựa trên dữ liệu của menuData như sau:
var myMenu = new menuTree(menuData);
- Và hiển thị nó ở nơi cần thiết:
myMenu.show('menu'); // menu là id của nới được hiển thị
5. Tùy biến CSS
Nhưng như vậy chưa đủ, bởi menu của bạn vẫn chưa có 'hình dáng', chúng chỉ đơn thuần là text mà thôi. Nguyên nhân là vì chúng ta chưa thiết kế các lớp CSS cho từng phần của menu. Hãy chú ý tên các lớp mà ta đã tạo cho từng phần của menu, khi gom lại, ta có 1 nhóm các lớp CSS cần xử lí như sau:
HTML Code:
.menuElement {
}
.menuElement a {
}
.menuElement a:hover {
}
.menuHeaderClosed {
}
.menuHeaderOpened {
}
.itemHeader {
}
.menuBody {
}
HTML Code:
.menuElement {
margin-left: 10px;
display: block;
}
.menuElement a {
text-decoration: none;
}
.menuElement a:hover {
font-weight: bold;
}
.menuHeaderClosed {
background: url(images/folderClosed.gif) no-repeat;
padding: 2px 0 2px 20px;
background-position: 0 0;
line-height: 18px;
}
.menuHeaderOpened {
background: url(images/folderOpened.gif) no-repeat;
padding: 2px 0 2px 20px;
background-position: 0 0;
line-height: 18px;
}
.itemHeader {
background: url(images/file.gif) no-repeat;
padding: 2px 0 2px 20px;
background-position: 0 0;
line-height: 18px;
}
.menuBody {
}
Demo các bạn có thể xem tại trang Tin tức tổng hợp
6. Lời kết
Bài viết này cố gắng trình bày cho các bạn cách làm menu dạng cây phân cấp, loại menu rất hay được sử dụng trong các trang web để tăng tính tướng tác của người dùng. Vì bài viết được viết vội vàng nên không khỏi những thiếu sót, rất mong nhận được sự góp ý của các bạn để phần code và bài viết được tốt hơn. Xin cảm ơn.
Download : http://www.mediafire.com/?d5a6kq9d28yzvw3
CHÚC MỌI NGƯỜI CODE TỐT.
Nguồn: xpt.vn
Nguồn: xpt.vn
Không có nhận xét nào:
Đăng nhận xét