Manipulate Jquery Menu On Re-size For Responsive Layout
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 = 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:
- 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. - On
window.resize
we loop through all elements in main menu (horizontal) accumulating each element width in theulWidth
variable. - Once the
width
of main menu is more thanwidth
of parent container -> we need to move the rest of menu items to the submenu (vertical) - so we push those elements to an arrayliForMoving
. - If array
liForMoving
isn't empty - we clone its elements, append them to the submenu and remove them from the main menu. - If
width
of all elements inmainMenu
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 - 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()
) - 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);
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();
});
});
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 li
s 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 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
});
Post a Comment for "Manipulate Jquery Menu On Re-size For Responsive Layout"