Draggable Bootstrap nav-tabs with jQuery UI
Horizontally draggable nav, also working on responsive websites
Alert
Be sure to include also jQuery UI Draggable component besides jQuery and Bootstrap.The Code
Inspired by how Google handles long nav menus on mobile, I've found a method to add horizontal scrolling on Bootstrap navs.
The first thing to do is to add a direct ancestor to the Bootstrap .nav, we call it .draggable-container in the demo, and then we add the .draggable class to the .nav:
<div class="draggable-container"> <ul class="nav nav-tabs draggable" ...>
We add the styles that keeps the .nav all on one line, the .draggable-container has width 100% so it inherit the width of the parents (columns or others):
/* draggable */
.draggable-container {
position: relative;
float: left;
width: 100%;
overflow: hidden;
/* no selection */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.draggable {
white-space: nowrap; /* all on same line */
font-size: 0; /* fix inline block spacing */
}
.draggable.nav > li {
display: inline-block;
float: none;
}
.draggable.nav > li > a {
margin: 0;
font-size: 14px;
}
.draggable.nav > li + li {
margin-left: 8px;
}
.draggable.btn-toolbar {
margin-bottom: 0 !important;
margin-left: 0 !important;
}
.draggable .btn-group {
margin-bottom: 0 !important;
}
.draggable .btn-group:first-child {
margin-left: 0 !important;
}
Be sure to set the desired font-size inside .draggable.nav > li > a and the margin-left of the .draggable.nav > li + li.
As last we use this javascript to do all the calculations on load and window resize, and to make .draggable draggable with jQuery UI:
// function to calculate and set nav width
$.fn.draggable_nav_calc = function(options) {
return this.each( function(i) {
var $element = $(this);
if ($element.is(":visible")) {
// x or y
if (options.axis === "x") {
// calculate
var navWidth = 1;
$element.find("> *").each( function(i) {
navWidth += $(this).outerWidth(true);
});
// set width
var parentWidth = $element.parent().width();
if (navWidth > parentWidth) {
$element.css("width", navWidth);
} else {
$element.css("width", parentWidth);
}
} else if (options.axis === "y") {
// calculate
var navHeight = 1;
$element.find("> *").each( function(i) {
navHeight += $(this).outerHeight(true);
});
// set height
var parentHeight = $element.parent().width();
if (navHeight > parentHeight) {
$element.css("height", navHeight);
} else {
$element.css("height", parentHeight);
}
}
}
});
};
// function to calculate and set nav width
$.fn.draggable_nav_check = function() {
return this.each( function(i) {
var $element = $(this);
var position = $element.position();
// calculate
var w = $element.width();
var pw = $element.parent().width();
var maxPosLeft = 0;
if (w > pw) {
maxPosLeft = - (w - pw);
}
var h = $element.height();
var ph = $element.parent().height();
var maxPosTop = 0;
if (h > ph) {
maxPosTop = h - ph;
}
// horizontal
if (position.left > 0) {
$element.css("left", 0);
} else if (position.left < maxPosLeft) {
$element.css("left", maxPosLeft);
}
// vertical
if (position.top > 0) {
$element.css("top", 0);
} else if (position.top < maxPosTop) {
$element.css("top", maxPosTop);
}
});
};
// function to activate draggable nav
$.fn.draggable_nav = function(options) {
return this.each( function(i) {
var $element = $(this);
// calculate first time, after delay to fix resize bugs
window.setTimeout( function(e) {
$element.draggable_nav_calc(options);
}, 100);
// on shown tabs recalculate
$element.find('[data-toggle="tab"]').on('shown.bs.tab', function(e) {
$element.draggable_nav_calc(options);
});
// on resize recalculate
function draggable_nav_resize_after() {
clearTimeout($element.data("draggable_nav_timeout"));
var timeout = window.setTimeout( function(e) {
$element.draggable_nav_calc(options);
$element.draggable_nav_check();
}, 0);
$element.data("draggable_nav_timeout", timeout);
}
$(window).on('resize', draggable_nav_resize_after);
$(window).on('scroll', draggable_nav_resize_after);
});
};
// init draggable_nav
$(".draggable").draggable_nav({
axis: 'x' // only horizontally
});
// jquery ui draggable
$(".draggable").draggable({
axis: 'x', // only horizontally
drag: function(e, ui) {
var $element = ui.helper;
// calculate
var w = $element.width();
var pw = $element.parent().width();
var maxPosLeft = 0;
if (w > pw) {
maxPosLeft = - (w - pw);
}
var h = $element.height();
var ph = $element.parent().height();
var maxPosTop = 0;
if (h > ph) {
maxPosTop = h - ph;
}
// horizontal
if (ui.position.left > 0) {
ui.position.left = 0;
} else if (ui.position.left < maxPosLeft) {
ui.position.left = maxPosLeft;
}
// vertical
if (ui.position.top > 0) {
ui.position.top = 0;
} else if (ui.position.top < maxPosTop) {
ui.position.top = maxPosTop;
}
}
});
// jquey draggable also on touch devices
// http://stackoverflow.com/questions/5186441/javascript-drag-and-drop-for-touch-devices
function touchHandler(e) {
var touch = e.originalEvent.changedTouches[0];
var simulatedEvent = document.createEvent("MouseEvent");
simulatedEvent.initMouseEvent({
touchstart: "mousedown",
touchmove: "mousemove",
touchend: "mouseup"
}[e.type], true, true, window, 1,
touch.screenX, touch.screenY,
touch.clientX, touch.clientY, false,
false, false, false, 0, null);
touch.target.dispatchEvent(simulatedEvent);
// without prevent default or links inside stop working
// event.preventDefault();
}
function initTouchHandler($element) {
$element.on("touchstart touchmove touchend touchcancel", touchHandler);
}
initTouchHandler($(".draggable"));