Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Manipulate jquery menu on re-size for responsive layout

There is simple menu with list itmes. UL LI. width and numbers of LI is dynamic. And there is dropdown kind of thing "more" on hover/click it will show remaining LI , which will not fit available space.

I tried using jquery while user resize windows from right to left, it will hide the last visible menu item. What is the possible way to do this revers and also add LI in "more" link.

Tried some option, as width is less when we resize then list item move below and increase height of UL so using this method I am able to hide last visible. code

http://jsbin.com/flexmenu/2/edit

Step 1

resize of window

Step 2enter image description here

Step 3enter image description here

These steps will be reverse when user re-size (increase the width)

Markup

<div class="twelve columns filter-wrapper">
    <ul class="nav-bar-filter" id="nav-bar-filter">
        <li><a href="#">All</a></li>
        <li><a href="#">Small</a></li>
        <li><a href="#">Medium</a></li>
        <li><a href="#">Extra large</a></li>
        <li><a href="#">Text</a></li>
        <li><a href="#">Small-1</a></li>
        <li><a href="#">Medium-1</a></li>
        <li><a href="#">Extra large text</a></li>
        <li><a href="#">Large text</a></li>
        <li><a href="#">Text</a></li>
    </ul>
<ul id="more-nav">
  <li><a href="#">More > </a>
    <ul class="subfilter"><li><a href="#">Text</a></li></ul>
  </li>    
</ul>
</div>

Basically this menu will be used for responsive layout menu.Any help in this will be helpful. Edit 1: added markup

like image 257
Wasim Shaikh Avatar asked Mar 14 '13 21:03

Wasim Shaikh


4 Answers

Well, I've tried to build some script for doing it, here's what I've got:

$().ready(function () { 

    //we reconstruct menu on window.resize
    $(window).on("resize", function (e) {                                                   
        var parentWidth = $("#nav-bar-filter").parent().width() - 40;
        var ulWidth = $("#more-nav").outerWidth();                  
        var menuLi = $("#nav-bar-filter > li");                 
        var liForMoving = new Array();      
        //take all elements that can't fit parent width to array
        menuLi.each(function () {                       
            ulWidth += $(this).outerWidth(); 
            if (ulWidth > parentWidth) {
                console.log(ulWidth);
                liForMoving.push($(this));
            }
        });                         
        if (liForMoving.length > 0) {   //if have any in array -> move them to "more" ul
            e.preventDefault();                     
            liForMoving.forEach(function (item) {
                item.clone().appendTo(".subfilter");
                item.remove();
            });                         
        }
        else if (ulWidth < parentWidth) { //check if we can put some 'li' back to menu
            liForMoving = new Array();
            var moved = $(".subfilter > li");
            for (var i = moved.length - 1; i >= 0; i--) { //reverse order
                var tmpLi = $(moved[i]).clone();
                tmpLi.appendTo($("#nav-bar-filter"));
                ulWidth += $(moved[i]).outerWidth();
                if (ulWidth < parentWidth) {                                
                    $(moved[i]).remove();
                }
                else {
                    ulWidth -= $(moved[i]).outerWidth();
                    tmpLi.remove();
                }                           
            }                       
        }                       
        if ($(".subfilter > li").length > 0) { //if we have elements in extended menu - show it
            $("#more-nav").show();
        }
        else {
            $("#more-nav").hide();
        }
    });

    $(window).trigger("resize"); //call resize handler to build menu right
});

And JSFiddle sample

Had to change your css styles to make it work properly. What we do is:

  1. Get width of parent container, initial width of main menu is zero, but we probably have to show the additional menu, so we get its size as well.
  2. On window.resize we loop through all elements in main menu (horizontal) accumulating each element width in the ulWidth variable.
  3. Once the width of main menu is more than width of parent container -> we need to move the rest of menu items to the submenu (vertical) - so we push those elements to an array liForMoving.
  4. If array liForMoving isn't empty - we clone its elements, append them to the submenu and remove them from the main menu.
  5. If width of all elements in mainMenu is minor than the width of its container, we need to check if we can move some elements from the submenu to the main one
  6. We iterate through submenu elements, grab each item, append to main menu (if you have different fonts and paddings in menus - final size of element will differ), check if its size is good to fit the parent container. If it is - we remove element from submenu ($(moved[i]).remove()), if it's not - we remove appended element (tmpLi.remove())
  7. Finally we check if the submenu has any items, and show/hide it.
like image 78
Sergio Avatar answered Oct 17 '22 22:10

Sergio


$(document).ready(function () {
    var menu = $("#nav-bar-filter"),
        subMenu = $(".subfilter"),
        more = $("#more-nav"),
        parent = $(".filter-wrapper"),
        ww = $(window).width(),
        smw = more.outerWidth();

    menu.children("li").each(function () {
        var w = $(this).outerWidth();
        if (w > smw) smw = w + 20;
        return smw
    });
    more.css('width', smw);

    function contract() {
        var w = 0,
            outerWidth = parent.width() - smw - 50;
        for (i = 0; i < menu.children("li").size(); i++) {
            w += menu.children("li").eq(i).outerWidth();
            if (w > outerWidth) {
                menu.children("li").eq(i - 1).nextAll()
                    .detach()
                    .css('opacity', 0)
                    .prependTo(".subfilter")
                    .stop().animate({
                    'opacity': 1
                }, 300);
                break;
            }
        }
    }

    function expand() {
        var w = 0,
            outerWidth = parent.width() - smw - 20;
        menu.children("li").each(function () {
            w += $(this).outerWidth();
            return w;
        });
        for (i = 0; i < subMenu.children("li").size(); i++) {
            w += subMenu.children("li").eq(i).outerWidth();
            if (w > outerWidth) {
                var a = 0;
                while (a < i) {
                    subMenu.children("li").eq(a)
                        .css('opacity', 0)
                        .detach()
                        .appendTo("#nav-bar-filter")
                        .stop().animate({
                        'opacity': 1
                    }, 300);
                    a++;
                }
                break;
            }
        }
    }
    contract();

    $(window).on("resize", function (e) {
        ($(window).width() > ww) ? expand() : contract();
        ww = $(window).width();
    });

});

DEMO

Had to modify CSS to make it work. I didn't give static dimensions for any element so that the menu would be responsive regardless to it's contents.

How it works: there are simply 2 functions contract() and expand(), when page load contract() will be called to move extra items to submenu, when window is resized if it's expanding expand() will be called and if it's contracting contract() will be called instead.

UPDATE: added animation and fixed submenu location to the right, see the demo.

like image 34
razz Avatar answered Oct 17 '22 23:10

razz


Try this: Have your jQuery detect how many terms in the nav bar have to be truncated. Then, add li elements to the ul by using the document.createElement() JavaScript method based on the number of terms truncated. For example, if your jQuery detects that 5 terms have been truncated, then use the following code:

var truncated_elements = 5;
for (i=1; i<truncated_elements; i++){
    var new_li = document.createElement('li');
    new_li.innerHTML = "truncated_term";
    document.getElementsByTagName('ul')[0].appendChild(new_li);
}

In the above code, the jQuery would figure out that 5 elements need to be truncated, and run a for loop in which it would create lis for each truncated element. The innerHTML of the new_li would be set to the content of the truncated element (using an array possibly), and then the new_li would be appended to the ul in the "More" submenu.

I can provide a JSFiddle/JSBin if necessary.

like image 22
Shrey Gupta Avatar answered Oct 17 '22 23:10

Shrey Gupta


I think the drop down menu should be reversed to keep the tab sequence in focusable items in the same order.

For accessibility you could also set the setsize and the position in the set. For accessibility reason I reset the focus to the cloned item when the moved element had focus and added setWaiAria to set the setsize and set position. And set the focus to the last item when the more link had focus and disappears. see fiddle

$().ready(function () { 

  var setWaiAria = function(menuLi){
    menuLi.each(function (i,el) {
      var $el = $(el);
      $el.attr('aria-setsize',menuLi.length);
      $el.attr('aria-posinset',i+1);
    });
  }

  // set wai aria aria-setsize and aria-posinset before cloning elements in other list
  setWaiAria($("#nav-bar-filter > li"));

  //we reconstruct menu on window.resize
  $(window).on("resize", function (e) {                         
    var parentWidth = $("#nav-bar-filter").parent().width() - 40;
    var ulWidth = $("#more-nav").outerWidth();          
    var menuLi = $("#nav-bar-filter > li");         
    var liForMoving = new Array();
    var activeElement = $(document.activeElement)[0];

    // before remove item check if you have to reset the focus
    var removeOriginal = function(item,clone){
      // check focused element
      if(item.find('a')[0] === activeElement){
        activeElement = clone.find('a')[0];
      }

      item.remove();
    }

    //take all elements that can't fit parent width to array
    menuLi.each(function () {
      var $el = $(this);            
      ulWidth += $el.outerWidth(); 
      if (ulWidth > parentWidth) {
        liForMoving.unshift($el);
      }
    }); 

    if (liForMoving.length > 0) { //if have any in array -> move em to "more" ul
      e.preventDefault(); 

      liForMoving.forEach(function (item) {
        var clone = item.clone();
        clone.prependTo(".subfilter");

        removeOriginal(item, clone);
      });

    }
    else if (ulWidth < parentWidth) { //check if we can put some 'li' back to menu
      liForMoving = new Array();

      var moved = $(".subfilter > li");
      for (var i=0, j = moved.length ; i < j; i++) { 
        var movedItem = $(moved[i]);

        var tmpLi = movedItem.clone();
        tmpLi.appendTo($("#nav-bar-filter"));


        ulWidth += movedItem.outerWidth();
        if (ulWidth < parentWidth) {
          removeOriginal(movedItem, tmpLi);
        }
        else {
          // dont move back
          ulWidth -= movedItem.outerWidth();
          tmpLi.remove();
        }

      }
    }           
    if ($(".subfilter > li").length > 0) { //if we have elements in extended menu - show it
      $("#more-nav").show();
    }
    else {
      // check if 'more' link has focus then set focus to last item in list
      if($('#more-nav').find('a')[0] === $(document.activeElement)[0]){
        activeElement = $("#nav-bar-filter > li:last-child a")[0];
      }

      $("#more-nav").hide();
    }

    // reset focus
    activeElement.focus();
  });

  $(window).trigger("resize"); //call resize handler to build menu right
});
like image 37
Ron Jonk Avatar answered Oct 17 '22 23:10

Ron Jonk