Monday, May 16, 2011

Simplifying a javascript jump menu



The jump menu is a reasonably common UI design pattern on the web. It is used by big guns such as Facebook and Twitter, and has been dissected and reproduced many times over.

When I set about implementing my own jump menu I found opening and closing the menu when it is clicked on was rather trivial.

$('.jump-menu').click(function(e){
toggleMenu();
}

The difficulty came in closing the menu when there is a click outside the jump menu. Googling for answers, I found the scenario of closing a menu on clicking outside it to be a well trodden path. Stackoverflow had plenty of examples and possible solutions such as here and here. I even found a plugin created by Ben Alman that extended Jquery to provide a custom clickoutside event. Many of the proposed solutions were fragile, and I was slow to add a new plugin to my web application - as good and all as the implementation appeared. I really wanted to understand the problem before opting to take a proposed solution off the shelf.



The state diagram above illustrates the four possible actions for the two menu states of open and closed. We can see that one of the four actions results in no change of state, while another two result in the same change of state. Clearly this diagram could be simplified, and along with it my perception of the problem.

"Make things as simple as possible, but not simpler" - Albert Einstein




Given this simplified state transition diagram, there was no need for an event register, a global array, comparing with event.target, a handler that would be called for each and every document click, or the jquery plugin. All that I needed was something like the following:


isMenuOpen = function(menu) {
return menu.hasClass('ui-active-state');
};
openMenu = function(event) {
var documentClicks, menu, self;
menu = $(event.target).parents('.jump-menu');
if (isMenuOpen(menu)) {
menu.removeClass('ui-default-state');
menu.addClass('ui-active-state');
self = this;
documentClicks = 0;
$(document).bind("click", __bind(function(e) {
documentClicks++;
if (documentClicks === 2) {
$(this).unbind(e);
this.closeMenu(menu);
}
return this;
}, this));
}
return this;
};
closeMenu = function(menu) {
menu = $(this.el).find('.jump-menu');
menu.removeClass('ui-active-state');
menu.addClass('ui-default-state');
};


There is always another way to look at a problem.
Happy Coding!

Credits:
http://davidwalsh.name/twitter-dropdown-jquery
http://stackoverflow.com/questions/152975/how-to-detect-a-click-outside-an-element
http://stackoverflow.com/questions/2868582/click-outside-menu-to-close-in-jquery
http://www.filamentgroup.com/lab/developer_your_own_jquery_themeroller_ready_components/

6 comments:

  1. Your code doesn't seem to work? Why do you set self to = this, when you don't ever use the self variable again? What am I missing?

    ReplyDelete
    Replies
    1. Oh, it has been a long time since I wrote this so I'm not sure... Looking over it, it might be the case that self is not required as this is passed into the .bind method as the last argument, so I'm assuming that is a means of supplying the context the method is called in. You could try replace: $(this).unbind(e); with $(document).unbind(e);

      By the looks of the "return this" the above code is some kind of pseudo code, I had originally written in coffeescript. Sorry if it is causing you a headache, and hope the change I suggested works for you. If you have understood the article, you should be able to get it working.

      Delete
  2. đồng tâm
    game mu
    cho thuê nhà trọ
    cho thuê phòng trọ
    nhac san cuc manh
    số điện thoại tư vấn pháp luật miễn phí
    văn phòng luật
    tổng đài tư vấn pháp luật
    dịch vụ thành lập công ty trọn gói
    lý thuyết trò chơi trong kinh tế học
    đức phật và nàng audio
    hồ sơ mật dinh độc lập audio
    đừng hoang tưởng về biển lớn ebook
    chiến thắng trò chơi cuộc sống ebook
    bước nhảy lượng tử
    ngồi khóc trên cây audio
    truy tìm ký ức audio
    mặt dày tâm đen audio
    thế giới như tôi thấy ebook

    không biết Yến vương phủ đang muốn cầu thân với Vương gia sao?”

    Mẹ nọ, ai nhiều chuyện thế không biết. Lưu Phong liền giải thích: “Tố Tố, nàng không nên nghe người khác nói bậy. Ta là người thành thật, ta làm người như thế nào nàng hẳn phải biết chứ.”

    “Hừ!”

    Ân Tố Tố hừ một tiếng nói: “ Cha thiếp chính mắt nhìn thấy chàng và ả đi cùng một chỗ, chàng còn không nói thật đi, còn định dấu diếm thiếp cho đến khi nào?”

    Lưu Phong lại một phen ngạc nhiên, vậy ra là nhạc trượng tương lai của mình đã bắt mình tại trận

    Bất quá vẫn có thể giải thích. Cha nào mà chẳng quan tâm đến hạnh phúc của con gái mình chứ.

    Lưu Phong mỉm cười, vỗ về nàng, nhẹ giọng nói: “Nàng tức giận ta sao?”

    Ân Tố Tố khẽ trừng mắt nhìn hắn, bộ dáng như mất hứng vậy.

    “Tố Tố, nàng sẽ không tức giận nữa chứ?” Lưu Phong cười hì hì, bàn tay bắt đầu mân mê đến bộ ngực sữa thơm tho của nàng: “Tin ta đi, ta chỉ yêu mỗi nàng mà thôi.”

    Ân Tố Tố vẫn im lặng không nói lời nào. Nhưng trên khuôn mặt, rõ ràng là thần thái đã trở nên tốt hơn rất nhiều.

    “Tố Tố, nàng yên tâm, ta đối với Vương Đông Đông thực sự không có ý gì cả.” Đương nhiên là nàng ta muốn quyến rũ ta thì cũng không trách được ta.

    Ân Tố Tố nghe vậy liền ôm lấy cổ hắn, ngọt ngào nói: “Phu quân thật là tốt. Thiếp biết chàng chỉ yêu thiếp. Đều là trách thiếp nghe lời cha trách oan chàng.”

    Dừng một chút, Lưu Phong khẽ hỏi: “Tố Tố, hôn sự của chúng ta khi nào mới tiến hành đây? Cô cô không gửi về tin tức gì sao?” Thành gia lập nghiệp, là sự kiện lớn của kiếp người. Có gia đình mới có trách nhiệm, mới có ý chí để tiến lên.

    ReplyDelete
  3. Great explanation with appropriate example in such way that any one can understand the concepts with ease.

    AngularJs Training
    AngularJs Training in Chennai
    JavaScript Training

    ReplyDelete