Menu cây phân cấp bằng Javascript và CSS

Bài mới nhất gửi 09/10/2008 6:40 PM, do Tài Năng Trẻ gửi. 1 hồi âm.
Trang 1 trên 1
Cách sắp xếp: Trước Tiếp
  • 09/10/2008 3:44 PM

    • TrungDD
    • Top 10 Contributor
    • Joined on 29/07/2008
    • Bài gửi 417
    • Điểm 1,550

    Menu cây phân cấp bằng Javascript và CSS

    Mô tả menu dạng cây phân cấp

    Menu 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:

    Ở đó mỗi khi chúng ta nhấn vào một đề mục thì nó sẽ được mở ra để hiện các mục nhỏ hơn phía dưới. Menu dạng này giống như menu trong các chương trình duyệt file (Windows Explorer chẳng hạn).

    Mỗi phần tử của menu có thể là một điểm cuối, hoặc cũng có thể chính là một menu, nghĩa là bên trong nó còn có thể chứa một 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 một menu khác. Điểm cuối ở đây hiểu là một 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 một menu thì các mục trong nó sẽ được mở ra, nhấn thêm một lần nữa thì menu đó sẽ được đóng lại. Còn nhấn lên một điểm cuối thì chúng ta sẽ thực hiện một tác vụ nào đó cần thiết.

    Mô tả cấu trúc dữ liệu biểu diễn menu dạng cây phân cấp

    Mỗi 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 ở một vị trí của trang web hay chuyển đến trang khác.

    - desc: là phần mô tả một 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 một 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à một menu thì cần phải có một 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 namelist gần như là bắt buộc, urldesc 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 một 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 một 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 hai cách: XMLJSON (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:

    Đó là mô phỏng một menu dạng cây đa cấp, bạn có thể bổ xung bao nhiêu phần tử vào cũng được miễn là phải tôn trọng quy tắc JSON. Quy tắc này thực ra cũng không có gì phức tạp cả, chỉ cần chú ý biểu diễn một đối tượng chúng ta dùng dấu ngoặc nhọn {}, còn khi liệt kê một mảng chúng ta dùng dấu ngoặc vuông [, mỗi thuộc tính thì biểu diễn bằng cặp name: value.

    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.

    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 một đố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 một đố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:

    Ba dòng đầu dùng để gán các thuộc tính name, url, desc từ menuData sang đối tượng của chúng ta. Phần tiếp theo để xử lí khi menuData chứa một danh sách các phần tử con. Chú ý là ở phần này chúng ta gọi đệ quy để sinh ra chính các phần tử của menu tương ứng với các nút của menuData, tức là sẽ sinh thêm một phần tử ứng với một nút, rồi bên trong phần tử đó lại tiếp tục sinh ra một phần tử con khác nếu có. Chỗ này khá quan trọng vì nó sẽ đảm bảo cho bạn mô phỏng được cây phân cấp với số cấp bất kì. Chúng ta cũng tạo một liên kết ngược đến cha của nó qua lệnh listIdea.parent = this;, dùng để dự trữ cho các trường hợp cần thiết.

    Sau khi sinh ra hết các phần tử rồi, chúng ta đưa nó vào một 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:

    Không có gì khó hiểu cả, chúng ta chèn phần tử listIdea vào cuối mảng list của đối tượng thô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 một đố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 một đố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 một lúc ba bốn 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.

    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 một menu, chúng ta dùng hàm render() như sau:

    Ở đây chúng ta bổ xung các thuộc tính của một phần tử menu là html (dùng để chứa toàn bộ menu), header (chứa tiêu đề name của menu), body (chứa phần mô tả desc của menu và các phần tử con nếu có).

    Hai 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ó một 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:

    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à một 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à một đ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:

    Ở đây chúng ta dùng hàm isMenu() để kiểm tra xem phần tử hiện thời có phải là menu không, đoạn code để kiểm tra rất đơn giản như sau:

    menuTree.prototype.isMenu = function() {
        return (this.list) ? true : false;
    }

    Sau khi đã gán các sự kiện tương ứng cho phần tiêu đề, chúng ta chỉ đơn giản gán tên cho nó thông qua câu lệnh:

    Ở đoạn code tạo header này còn có câu lệnh:

    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 một 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 một 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:

    Cũng dễ hiểu thôi phải không? Chúng ta dùng đệ quy để cho các phần tử con tự sinh ra các DOM node tương ứng, sau đó bổ xung nó vào phần body của phần tử hiện thời thông qua hàm appendChild().

    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:

    Bây giờ chúng ta sẽ xây dựng hàm showHide(), dùng để ẩn / hiện menu khi chúng ta click lên tên của nó. Dạng chung của hàm này như sau:

    Ở đây chúng ta dùng lại tham chiếu obj = this.me; mà ta đã gán ở phần tạo header. Tại sao phải dùng tham chiếu ngược như thế này mà không dùng ngay đối tượng this? Nguyên nhân là vì khi bạn nhấn lên tiêu đề của menu, thì đối tượng this được gửi tới hàm sẽ là phần header chứ không phải là đối tượng menu tổng thể (bao gồm cả header, body, ...). Nói cách khác là đối tượng được gửi tới (context object) không phải là đối tượng chúng ta cần thao tác. Vì vậy để lấy đối tượng cần thao tác, chúng ta dùng tham chiếu ngược obj = this.me;.

    Đ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:

    Ở đây ta dùng thuộc tính CSS display để quyết định menu có được hiển thị hay không ('' là có hiển thị, 'none' là không hiển thị). Ngoài ra ta cũng gán thuộc tính lớp CSS cho phần header (bạn có thể thấy trên hình VD ở đầu là khi menu đóng thì có biểu tượng thư mục đóng, menu mở tương ứng với biểu tượng thư mục mở).

    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:

    Trong đó hàm prepare() chúng ta dùng để chuẩn bị menu trước khi hiển thị (như việc đóng / mở chẳng hạn, chúng ta tách ra riêng biệt để dễ tùy biến). Hàm $() dùng để tham chiếu đến vị trí được định danh bởi containerID. Hàm này viết đơn giản như sau:

    Ngoài ra, chúng ta 'làm sạch' chỗ sẽ hiển thị menu bằng lệnh container.innerHTML = ''; (bạn có thể không muốn cũng được nếu như muốn bổ xung menu vào 1 phần có sẵn nào đó). Sau đó chúng ta thêm menu vào phần đó bằng lệnh container.appendChild(this.html); là xong.

    Bây giờ chúng ta sẽ xây dựng hàm prepare(), nó có dạng như sau:

    Bạn có thể thấy là chúng ta sẽ gán thuộc tính lớp CSS cho phần tiêu đề ở đây, tương ứng phần tử đó là một điểm cuối (itemHeader), hay là menu đang mở (menuHeaderOpened) hoặc đóng (menuHeaderClosed). Sau đó tương ứng với tham biến truyền vào showAll mà chúng ta cho phần body hiển thị hay không qua thuộc tính CSS display. Ở đây mặc định showAllfalse, tức là menu sẽ đóng. Cuối cùng là việc duyệt qua các phần tử con trong danh sách của menu (nếu có) và gọi đệ quy để chúng tự 'chuẩn bị' cho mình.

    Việc xây dựng menu dạng cây phân cấp như vậy đã hoàn tất. Để tạo một 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 một đố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ị

    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:

    Ở phần này, bạn có thể tùy biến CSS một cách tối đa để tạo ra một menu độc đáo và đẹp, VD như với menu dạng thư mục ở đầu tiên, mình dùng CSS như sau:

    Ngoài dạng thư mục này ra, bạn có thể tạo ra nhiều dạng menu khác, chủ yếu là dựa vào các hình mà bạn có. Dưới đây là một vài loại khác cho các bạn tham khảo:

      

    Các file CSS tương ứng cho từng loại, cùng với hình ảnh và code của file Javascript các bạn có thể download tại đây. Muốn dùng loại menu nào, bạn hãy thay đổi tên file CSS tương ứng. Trong đó cũng có sẵn file demo, bạn có thể chỉnh sửa lại để thành menu của mình.

    Demo các bạn có thể xem tại trang Tin tức tổng hợp

    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.

    [HaiPhong-Aptech] st

    Xếp trong: , , ,
    • Điểm bài gửi: 8
  • 09/10/2008 6:40 PM trả lời cho

    Re: Menu cây phân cấp bằng Javascript và CSS

    http://javascriptbank.com

    Yes có hàng trăm kiểu Menu, con trỏ, link ...

    • Điểm bài gửi: 3
Trang 1 trên 1
Tầng 3, Tòa nhà Sholega, số 275 Lạch Tray, Ngô Quyền, Hải Phòng
Tel: +84 (31) 3.733.000 - 3.733.111      Fax: +84 (31) 3.733.222
Email: center@hp-aptech.edu.vn
© 2004-2008 HaiPhong - Aptech Computer Education Giới thiệu  |  Liên hệ