Skip to content Skip to sidebar Skip to footer

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 no

Solution 1:

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 = newArray();      
        //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();
            });                         
        }
        elseif (ulWidth < parentWidth) { //check if we can put some 'li' back to menu
            liForMoving = newArray();
            var moved = $(".subfilter > li");
            for (var i = moved.length - 1; i >= 0; i--) { //reverse ordervar 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 liForMovingisn'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.

Solution 2:

$(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);

    functioncontract() {
        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;
            }
        }
    }

    functionexpand() {
        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.

Solution 3:

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.

Solution 4:

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 listsetWaiAria($("#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 = newArray();
    var activeElement = $(document.activeElement)[0];

    // before remove item check if you have to reset the focusvar removeOriginal = function(item,clone){
      // check focused elementif(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);
      });

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

      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 listif($('#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
});

Post a Comment for "Manipulate Jquery Menu On Re-size For Responsive Layout"