/**
|
<b>Sidebar functions</b>. Collapsing/expanding, toggling mobile view menu and other sidebar functions.
|
*/
|
|
(function($ , undefined) {
|
var sidebar_count = 0;
|
|
function Sidebar(sidebar, settings) {
|
var self = this;
|
this.$sidebar = $(sidebar);
|
this.$sidebar.attr('data-sidebar', 'true');
|
if( !this.$sidebar.attr('id') ) this.$sidebar.attr( 'id' , 'id-sidebar-'+(++sidebar_count) )
|
|
|
//get a list of 'data-*' attributes that override 'defaults' and 'settings'
|
var attrib_values = ace.helper.getAttrSettings(sidebar, $.fn.ace_sidebar.defaults, 'sidebar-');
|
this.settings = $.extend({}, $.fn.ace_sidebar.defaults, settings, attrib_values);
|
|
|
//some vars
|
this.minimized = false;//will be initialized later
|
this.collapsible = false;//...
|
this.horizontal = false;//...
|
this.mobile_view = false;//
|
|
|
//return an array containing sidebar state variables
|
this.vars = function() {
|
return {'minimized': this.minimized, 'collapsible': this.collapsible, 'horizontal': this.horizontal, 'mobile_view': this.mobile_view}
|
}
|
this.get = function(name) {
|
if(this.hasOwnProperty(name)) return this[name];
|
}
|
this.set = function(name, value) {
|
if(this.hasOwnProperty(name)) this[name] = value;
|
}
|
|
|
//return a reference to self (sidebar instance)
|
this.ref = function() {
|
return this;
|
}
|
|
|
//toggle icon for sidebar collapse/expand button
|
var toggleIcon = function(minimized, save) {
|
var icon = $(this).find(ace.vars['.icon']), icon1, icon2;
|
if(icon.length > 0) {
|
icon1 = icon.attr('data-icon1');//the icon for expanded state
|
icon2 = icon.attr('data-icon2');//the icon for collapsed state
|
|
if(typeof minimized !== "undefined") {
|
if(minimized) icon.removeClass(icon1).addClass(icon2);
|
else icon.removeClass(icon2).addClass(icon1);
|
}
|
else {
|
icon.toggleClass(icon1).toggleClass(icon2);
|
}
|
|
try {
|
if(save !== false) ace.settings.saveState(icon.get(0));
|
} catch(e) {}
|
}
|
}
|
|
//if not specified, find the toggle button related to this sidebar
|
var findToggleBtn = function() {
|
var toggle_btn = self.$sidebar.find('.sidebar-collapse');
|
if(toggle_btn.length == 0) toggle_btn = $('.sidebar-collapse[data-target="#'+(self.$sidebar.attr('id')||'')+'"]');
|
if(toggle_btn.length != 0) toggle_btn = toggle_btn[0];
|
else toggle_btn = null;
|
|
return toggle_btn;
|
}
|
|
|
//collapse/expand sidebar
|
this.toggleMenu = function(toggle_btn, save) {
|
if(this.collapsible) return;
|
|
this.minimized = !this.minimized;
|
var save = !(toggle_btn === false || save === false);
|
|
|
if(this.minimized) this.$sidebar.addClass('menu-min');
|
else this.$sidebar.removeClass('menu-min');
|
|
try {
|
if(save) ace.settings.saveState(sidebar, 'class', 'menu-min', this.minimized);
|
} catch(e) {}
|
|
if( !toggle_btn ) {
|
toggle_btn = findToggleBtn();
|
}
|
if(toggle_btn) {
|
toggleIcon.call(toggle_btn, this.minimized, save);
|
}
|
|
//force redraw for ie8
|
if(ace.vars['old_ie']) ace.helper.redraw(sidebar);
|
|
|
$(document).trigger('settings.ace', ['sidebar_collapsed' , this.minimized, sidebar, save]);
|
}
|
this.collapse = function(toggle_btn, save) {
|
if(this.collapsible) return;
|
this.minimized = false;
|
|
this.toggleMenu(toggle_btn, save);
|
}
|
this.expand = function(toggle_btn, save) {
|
if(this.collapsible) return;
|
this.minimized = true;
|
|
this.toggleMenu(toggle_btn, save);
|
}
|
|
|
|
this.showResponsive = function() {
|
this.$sidebar.removeClass(responsive_min_class).removeClass(responsive_max_class);
|
}
|
|
//collapse/expand in 2nd mobile style
|
this.toggleResponsive = function(toggle_btn, showMenu) {
|
if( !this.mobile_view || this.mobile_style != 3 ) return;
|
|
if( this.$sidebar.hasClass('menu-min') ) {
|
//remove menu-min because it interferes with responsive-max
|
this.$sidebar.removeClass('menu-min');
|
var btn = findToggleBtn();
|
if(btn) toggleIcon.call(btn);
|
}
|
|
|
var showMenu = typeof showMenu !== 'undefined' ? showMenu : this.$sidebar.hasClass(responsive_min_class);
|
if(showMenu) {
|
this.$sidebar.addClass(responsive_max_class).removeClass(responsive_min_class);
|
}
|
else {
|
this.$sidebar.removeClass(responsive_max_class).addClass(responsive_min_class);
|
}
|
this.minimized = !showMenu;
|
|
|
if( !toggle_btn ) {
|
toggle_btn = this.$sidebar.find('.sidebar-expand');
|
if(toggle_btn.length == 0) toggle_btn = $('.sidebar-expand[data-target="#'+(this.$sidebar.attr('id')||'')+'"]');
|
if(toggle_btn.length != 0) toggle_btn = toggle_btn[0];
|
else toggle_btn = null;
|
}
|
|
if(toggle_btn) {
|
var icon = $(toggle_btn).find(ace.vars['.icon']), icon1, icon2;
|
if(icon.length > 0) {
|
icon1 = icon.attr('data-icon1');//the icon for expanded state
|
icon2 = icon.attr('data-icon2');//the icon for collapsed state
|
|
if(!showMenu) icon.removeClass(icon2).addClass(icon1);
|
else icon.removeClass(icon1).addClass(icon2);
|
}
|
}
|
|
$(document).triggerHandler('settings.ace', ['sidebar_collapsed' , this.minimized]);
|
}
|
|
|
//some helper functions
|
|
//determine if we have 4th mobile style responsive sidebar and we are in mobile view
|
this.is_collapsible = function() {
|
var toggle
|
return (this.$sidebar.hasClass('navbar-collapse'))
|
&& ((toggle = $('.navbar-toggle[data-target="#'+(this.$sidebar.attr('id')||'')+'"]').get(0)) != null)
|
&& toggle.scrollHeight > 0
|
//sidebar is collapsible and collapse button is visible?
|
}
|
//determine if we are in mobile view
|
this.is_mobile_view = function() {
|
var toggle
|
return ((toggle = $('.menu-toggler[data-target="#'+(this.$sidebar.attr('id')||'')+'"]').get(0)) != null)
|
&& toggle.scrollHeight > 0
|
}
|
|
|
//toggling (show/hide) submenu elements
|
this.$sidebar.on(ace.click_event+'.ace.submenu', '.nav-list', function (ev) {
|
var nav_list = this;
|
|
//check to see if we have clicked on an element which is inside a .dropdown-toggle element?!
|
//if so, it means we should toggle a submenu
|
var link_element = $(ev.target).closest('a');
|
if(!link_element || link_element.length == 0) return;//return if not clicked inside a link element
|
|
var minimized = self.minimized && !self.collapsible;
|
//if .sidebar is .navbar-collapse and in small device mode, then let minimized be uneffective
|
|
if( !link_element.hasClass('dropdown-toggle') ) {//it doesn't have a submenu return
|
//just one thing before we return
|
//if sidebar is collapsed(minimized) and we click on a first level menu item
|
//and the click is on the icon, not on the menu text then let's cancel event and cancel navigation
|
//Good for touch devices, that when the icon is tapped to see the menu text, navigation is cancelled
|
//navigation is only done when menu text is tapped
|
|
if( ace.click_event == 'tap'
|
&&
|
minimized
|
&&
|
link_element.get(0).parentNode.parentNode == nav_list )//only level-1 links
|
{
|
var text = link_element.find('.menu-text').get(0);
|
if( text != null && ev.target != text && !$.contains(text , ev.target) ) {//not clicking on the text or its children
|
ev.preventDefault();
|
return false;
|
}
|
}
|
|
|
//ios safari only has a bit of a problem not navigating to link address when scrolling down
|
//specify data-link attribute to ignore this
|
if(ace.vars['ios_safari'] && link_element.attr('data-link') !== 'false') {
|
//only ios safari has a bit of a problem not navigating to link address when scrolling down
|
//please see issues section in documentation
|
document.location = link_element.attr('href');
|
ev.preventDefault();
|
return false;
|
}
|
|
return;
|
}
|
|
ev.preventDefault();
|
|
|
|
|
var sub = link_element.siblings('.submenu').get(0);
|
if(!sub) return false;
|
var $sub = $(sub);
|
|
var height_change = 0;//the amount of height change in .nav-list
|
|
var parent_ul = sub.parentNode.parentNode;
|
if
|
(
|
( minimized && parent_ul == nav_list )
|
||
|
( ( $sub.parent().hasClass('hover') && $sub.css('position') == 'absolute' ) && !self.collapsible )
|
)
|
{
|
return false;
|
}
|
|
|
var sub_hidden = (sub.scrollHeight == 0)
|
|
//if not open and visible, let's open it and make it visible
|
if( sub_hidden && self.settings.hide_open_subs ) {//being shown now
|
$(parent_ul).find('> .open > .submenu').each(function() {
|
//close all other open submenus except for the active one
|
if(this != sub && !$(this.parentNode).hasClass('active')) {
|
height_change -= this.scrollHeight;
|
self.hide(this, self.settings.duration, false);
|
}
|
})
|
}
|
|
if( sub_hidden ) {//being shown now
|
self.show(sub, self.settings.duration);
|
//if a submenu is being shown and another one previously started to hide, then we may need to update/hide scrollbars
|
//but if no previous submenu is being hidden, then no need to check if we need to hide the scrollbars in advance
|
if(height_change != 0) height_change += sub.scrollHeight;//we need new updated 'scrollHeight' here
|
} else {
|
self.hide(sub, self.settings.duration);
|
height_change -= sub.scrollHeight;
|
//== -1 means submenu is being hidden
|
}
|
|
//hide scrollbars if content is going to be small enough that scrollbars is not needed anymore
|
//do this almost before submenu hiding begins
|
//but when minimized submenu's toggle should have no effect
|
if (height_change != 0) {
|
if(self.$sidebar.attr('data-sidebar-scroll') == 'true' && !self.minimized)
|
self.$sidebar.ace_sidebar_scroll('prehide', height_change)
|
}
|
|
return false;
|
})
|
|
var submenu_working = false;
|
this.show = function(sub, $duration, shouldWait) {
|
//'shouldWait' indicates whether to wait for previous transition (submenu toggle) to be complete or not?
|
shouldWait = (shouldWait !== false);
|
if(shouldWait && submenu_working) return false;
|
|
var $sub = $(sub);
|
var event;
|
$sub.trigger(event = $.Event('show.ace.submenu'))
|
if (event.isDefaultPrevented()) {
|
return false;
|
}
|
|
if(shouldWait) submenu_working = true;
|
|
|
$duration = typeof $duration !== 'undefined' ? $duration : this.settings.duration;
|
|
$sub.css({
|
height: 0,
|
overflow: 'hidden',
|
display: 'block'
|
})
|
.removeClass('nav-hide').addClass('nav-show')//only for window < @grid-float-breakpoint and .navbar-collapse.menu-min
|
.parent().addClass('open');
|
|
sub.scrollTop = 0;//this is for submenu_hover when sidebar is minimized and a submenu is scrollTop'ed using scrollbars ...
|
|
|
var complete = function(ev, trigger) {
|
ev && ev.stopPropagation();
|
$sub
|
.css({'transition-property': '', 'transition-duration': '', overflow:'', height: ''})
|
//if(ace.vars['webkit']) ace.helper.redraw(sub);//little Chrome issue, force redraw ;)
|
|
if(trigger !== false) $sub.trigger($.Event('shown.ace.submenu'))
|
|
if(shouldWait) submenu_working = false;
|
}
|
|
|
var finalHeight = sub.scrollHeight;
|
|
if($duration == 0 || finalHeight == 0 || !$.support.transition.end) {
|
//(if duration is zero || element is hidden (scrollHeight == 0) || CSS3 transitions are not available)
|
complete();
|
}
|
else {
|
$sub
|
.css({
|
'height': finalHeight,
|
'transition-property': 'height',
|
'transition-duration': ($duration/1000)+'s'
|
}
|
)
|
.one($.support.transition.end, complete);
|
|
//there is sometimes a glitch, so maybe retry
|
if(ace.vars['android'] ) {
|
setTimeout(function() {
|
complete(null, false);
|
ace.helper.redraw(sub);
|
}, $duration + 20);
|
}
|
}
|
|
return true;
|
}
|
|
|
this.hide = function(sub, $duration, shouldWait) {
|
//'shouldWait' indicates whether to wait for previous transition (submenu toggle) to be complete or not?
|
shouldWait = (shouldWait !== false);
|
if(shouldWait && submenu_working) return false;
|
|
|
var $sub = $(sub);
|
var event;
|
$sub.trigger(event = $.Event('hide.ace.submenu'))
|
if (event.isDefaultPrevented()) {
|
return false;
|
}
|
|
if(shouldWait) submenu_working = true;
|
|
|
$duration = typeof $duration !== 'undefined' ? $duration : this.settings.duration;
|
|
|
var initialHeight = sub.scrollHeight;
|
$sub.css({
|
height: initialHeight,
|
overflow: 'hidden',
|
display: 'block'
|
})
|
.parent().removeClass('open');
|
|
sub.offsetHeight;
|
//forces the "sub" to re-consider the new 'height' before transition
|
|
|
var complete = function(ev, trigger) {
|
ev && ev.stopPropagation();
|
$sub
|
.css({display: 'none', overflow:'', height: '', 'transition-property': '', 'transition-duration': ''})
|
.removeClass('nav-show').addClass('nav-hide')//only for window < @grid-float-breakpoint and .navbar-collapse.menu-min
|
|
if(trigger !== false) $sub.trigger($.Event('hidden.ace.submenu'))
|
|
if(shouldWait) submenu_working = false;
|
}
|
|
|
if( $duration == 0 || initialHeight == 0 || !$.support.transition.end) {
|
//(if duration is zero || element is hidden (scrollHeight == 0) || CSS3 transitions are not available)
|
complete();
|
}
|
else {
|
$sub
|
.css({
|
'height': 0,
|
'transition-property': 'height',
|
'transition-duration': ($duration/1000)+'s'
|
}
|
)
|
.one($.support.transition.end, complete);
|
|
//there is sometimes a glitch, so maybe retry
|
if(ace.vars['android'] ) {
|
setTimeout(function() {
|
complete(null, false);
|
ace.helper.redraw(sub);
|
}, $duration + 20);
|
}
|
}
|
|
return true;
|
}
|
|
this.toggle = function(sub, $duration) {
|
$duration = $duration || self.settings.duration;
|
|
if( sub.scrollHeight == 0 ) {//if an element is hidden scrollHeight becomes 0
|
if( this.show(sub, $duration) ) return 1;
|
} else {
|
if( this.hide(sub, $duration) ) return -1;
|
}
|
return 0;
|
}
|
|
|
//sidebar vars
|
var minimized_menu_class = 'menu-min';
|
var responsive_min_class = 'responsive-min';
|
var responsive_max_class = 'responsive-max';
|
var horizontal_menu_class = 'h-sidebar';
|
|
var sidebar_mobile_style = function() {
|
//differnet mobile menu styles
|
this.mobile_style = 1;//default responsive mode with toggle button inside navbar
|
if(this.$sidebar.hasClass('responsive') && !$('.menu-toggler[data-target="#'+this.$sidebar.attr('id')+'"]').hasClass('navbar-toggle')) this.mobile_style = 2;//toggle button behind sidebar
|
else if(this.$sidebar.hasClass(responsive_min_class)) this.mobile_style = 3;//minimized menu
|
else if(this.$sidebar.hasClass('navbar-collapse')) this.mobile_style = 4;//collapsible (bootstrap style)
|
}
|
sidebar_mobile_style.call(self);
|
|
function update_vars() {
|
this.mobile_view = this.mobile_style < 4 && this.is_mobile_view();
|
this.collapsible = !this.mobile_view && this.is_collapsible();
|
|
this.minimized =
|
(!this.collapsible && this.$sidebar.hasClass(minimized_menu_class))
|
||
|
(this.mobile_style == 3 && this.mobile_view && this.$sidebar.hasClass(responsive_min_class))
|
|
this.horizontal = !(this.mobile_view || this.collapsible) && this.$sidebar.hasClass(horizontal_menu_class)
|
}
|
|
//update some basic variables
|
$(window).on('resize.sidebar.vars' , function(){
|
update_vars.call(self);
|
}).triggerHandler('resize.sidebar.vars')
|
|
|
|
|
|
this.mobileToggle = function(showMenu) {
|
var showMenu = typeof showMenu === "undefined" ? undefined : showMenu;
|
|
if(this.mobile_view) {
|
if(this.mobile_style == 1 || this.mobile_style == 2) {
|
this.toggleMobile(null, showMenu);
|
}
|
else if(this.mobile_style == 3) {
|
this.toggleResponsive(null, showMenu);
|
}
|
}
|
else if(this.collapsible) {
|
this.toggleCollapsible(null, showMenu);
|
}
|
}
|
this.mobileShow = function() {
|
this.mobileToggle(true);
|
}
|
this.mobileHide = function() {
|
this.mobileToggle(false);
|
}
|
|
|
|
this.toggleMobile = function(toggle_btn, showMenu) {
|
if(!(this.mobile_style == 1 || this.mobile_style == 2)) return;
|
|
var showMenu = typeof showMenu !== 'undefined' ? showMenu : !this.$sidebar.hasClass('display');
|
if(!toggle_btn) {
|
toggle_btn = $('.menu-toggler[data-target="#'+(this.$sidebar.attr('id')||'')+'"]');
|
if(toggle_btn.length != 0) toggle_btn = toggle_btn[0];
|
else toggle_btn = null;
|
}
|
if(showMenu) {
|
this.$sidebar.addClass('display');
|
if(toggle_btn) $(toggle_btn).addClass('display');
|
}
|
else {
|
this.$sidebar.removeClass('display');
|
if(toggle_btn) $(toggle_btn).removeClass('display');
|
}
|
}
|
|
|
this.toggleCollapsible = function(toggle_btn, showMenu) {
|
if(this.mobile_style != 4) return;
|
|
var showMenu = typeof showMenu !== 'undefined' ? showMenu : !this.$sidebar.hasClass('in');
|
if(showMenu) {
|
this.$sidebar.collapse('show');
|
}
|
else {
|
this.$sidebar.removeClass('display');
|
this.$sidebar.collapse('hide');
|
}
|
}
|
|
}//end of Sidebar
|
|
|
//sidebar events
|
|
//menu-toggler
|
$(document)
|
.on(ace.click_event+'.ace.menu', '.menu-toggler', function(e){
|
var btn = $(this);
|
var sidebar = $(btn.attr('data-target'));
|
if(sidebar.length == 0) return;
|
|
e.preventDefault();
|
|
//sidebar.toggleClass('display');
|
//btn.toggleClass('display');
|
|
sidebar.ace_sidebar('toggleMobile', this);
|
|
var click_event = ace.click_event+'.ace.autohide';
|
var auto_hide = sidebar.attr('data-auto-hide') === 'true';
|
|
if( btn.hasClass('display') ) {
|
//hide menu if clicked outside of it!
|
if(auto_hide) {
|
$(document).on(click_event, function(ev) {
|
if( sidebar.get(0) == ev.target || $.contains(sidebar.get(0), ev.target) ) {
|
ev.stopPropagation();
|
return;
|
}
|
|
sidebar.ace_sidebar('toggleMobile', this, false);
|
$(document).off(click_event);
|
})
|
}
|
|
if(sidebar.attr('data-sidebar-scroll') == 'true') sidebar.ace_sidebar_scroll('reset');
|
}
|
else {
|
if(auto_hide) $(document).off(click_event);
|
}
|
|
return false;
|
})
|
//sidebar collapse/expand button
|
.on(ace.click_event+'.ace.menu', '.sidebar-collapse', function(e){
|
|
var target = $(this).attr('data-target'), $sidebar = null;
|
if(target) $sidebar = $(target);
|
if($sidebar == null || $sidebar.length == 0) $sidebar = $(this).closest('.sidebar');
|
if($sidebar.length == 0) return;
|
|
e.preventDefault();
|
$sidebar.ace_sidebar('toggleMenu', this);
|
})
|
//this button is used in `mobile_style = 3` responsive menu style to expand minimized sidebar
|
.on(ace.click_event+'.ace.menu', '.sidebar-expand', function(e){
|
var target = $(this).attr('data-target'), $sidebar = null;
|
if(target) $sidebar = $(target);
|
if($sidebar == null || $sidebar.length == 0) $sidebar = $(this).closest('.sidebar');
|
if($sidebar.length == 0) return;
|
|
var btn = this;
|
e.preventDefault();
|
$sidebar.ace_sidebar('toggleResponsive', this);
|
|
var click_event = ace.click_event+'.ace.autohide';
|
if($sidebar.attr('data-auto-hide') === 'true') {
|
if( $sidebar.hasClass(responsive_max_class) ) {
|
$(document).on(click_event, function(ev) {
|
if( $sidebar.get(0) == ev.target || $.contains($sidebar.get(0), ev.target) ) {
|
ev.stopPropagation();
|
return;
|
}
|
|
$sidebar.ace_sidebar('toggleResponsive', btn);
|
$(document).off(click_event);
|
})
|
}
|
else {
|
$(document).off(click_event);
|
}
|
}
|
})
|
|
|
$.fn.ace_sidebar = function (option, value, value2) {
|
var method_call;
|
|
var $set = this.each(function () {
|
var $this = $(this);
|
var data = $this.data('ace_sidebar');
|
var options = typeof option === 'object' && option;
|
|
if (!data) $this.data('ace_sidebar', (data = new Sidebar(this, options)));
|
if (typeof option === 'string' && typeof data[option] === 'function') {
|
if(value instanceof Array) method_call = data[option].apply(data, value);
|
else if(value2 !== undefined) method_call = data[option](value, value2);
|
else method_call = data[option](value);
|
}
|
});
|
|
return (method_call === undefined) ? $set : method_call;
|
};
|
|
|
$.fn.ace_sidebar.defaults = {
|
'duration': 300,
|
'hide_open_subs': true
|
}
|
|
|
})(window.jQuery);
|