/**
<b>Sidebar functions</b>. Collapsing/expanding, toggling mobile view menu and other sidebar functions.
*/
(function($ , undefined) {
var sidebar_count = 0;
function Sidebar(sidebar, options) {
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) )
var duration = options.duration || ace.helper.intAttr(sidebar, 'data-submenu-duration') ||300;//transition duration
//some vars
this.minimized = false;//will be initiated later
this.collapsible = false;//...
this.horizontal = false;//...
this.mobile_view = false;//
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;
}
this.ref = function() {
//return a reference to self
return this;
}
var toggleIcon = function(minimized) {
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(minimized !== undefined) {
if(minimized) icon.removeClass(icon1).addClass(icon2);
else icon.removeClass(icon2).addClass(icon1);
}
else {
icon.toggleClass(icon1).toggleClass(icon2);
}
}
}
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 button
this.toggleMenu = function(toggle_btn, save) {
if(this.collapsible) return;
//var minimized = this.$sidebar.hasClass('menu-min');
this.minimized = !this.minimized;
try {
//toggle_btn can also be a param to indicate saving to cookie or not?! if toggle_btn === false, it won't be saved
ace.settings.sidebar_collapsed(sidebar, this.minimized, !(toggle_btn === false || save === false));//@ ace-extra.js
} catch(e) {
if(this.minimized)
this.$sidebar.addClass('menu-min');
else this.$sidebar.removeClass('menu-min');
}
if( !toggle_btn ) {
toggle_btn = findToggleBtn();
}
if(toggle_btn) {
toggleIcon.call(toggle_btn, this.minimized);
}
//force redraw for ie8
if(ace.vars['old_ie']) ace.helper.redraw(sidebar);
}
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);
}
//collapse/expand in 2nd mobile style
this.toggleResponsive = function(toggle_btn) {
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);
}
this.minimized = !this.$sidebar.hasClass('responsive-min');
this.$sidebar.toggleClass('responsive-min responsive-max');
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
icon.toggleClass(icon1).toggleClass(icon2);
}
}
$(document).triggerHandler('settings.ace', ['sidebar_collapsed' , this.minimized]);
}
//some helper functions
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?
}
this.is_mobile_view = function() {
var toggle
return ((toggle = $('.menu-toggler[data-target="#'+(this.$sidebar.attr('id')||'')+'"]').get(0)) != null)
&& toggle.scrollHeight > 0
}
//toggling submenu
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 ) {//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, duration, false);
}
})
}
if( sub_hidden ) {//being shown now
self.show(sub, 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, 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, wait) {
if(wait !== false) {
if(submenu_working) return false;
submenu_working = true;
}
$duration = $duration || duration;
var $sub = $(sub);
var event;
$sub.trigger(event = $.Event('show.ace.submenu'))
if (event.isDefaultPrevented()) return false;
$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 ...
if( $duration > 0 ) {
$sub.css({height: sub.scrollHeight,
'transition-property': 'height',
'transition-duration': ($duration/1000)+'s'})
}
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(wait !== false) submenu_working = false;
}
if( $duration > 0 && !!$.support.transition.end ) {
$sub.one($.support.transition.end, complete);
}
else 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, wait) {
if(wait !== false) {
if(submenu_working) return false;
submenu_working = true;
}
$duration = $duration || duration;
var $sub = $(sub);
var event;
$sub.trigger(event = $.Event('hide.ace.submenu'))
if (event.isDefaultPrevented()) return false;
$sub.css({
height: sub.scrollHeight,
overflow: 'hidden',
display: 'block'
})
.parent().removeClass('open');
sub.offsetHeight;
//forces the "sub" to re-consider the new 'height' before transition
if( $duration > 0 ) {
$sub.css({'height': 0,
'transition-property': 'height',
'transition-duration': ($duration/1000)+'s'});
}
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(wait !== false) submenu_working = false;
}
if( $duration > 0 && !!$.support.transition.end ) {
$sub.one($.support.transition.end, complete);
}
else 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 || 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 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')
}//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');
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.removeClass('display');
btn.removeClass('display');
$(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') ) {
$(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);
}
}
})
/**
.on('shown.bs.collapse.sidebar hidden.bs.collapse.sidebar', '.sidebar[data-auto-hide=true]', function(e){
var click_event = ace.click_event+'.ace.autohide';
var sidebar = this;
if(e.type == 'shown') {
$(document).on(click_event, function(ev) {
if( sidebar == ev.target || $.contains(sidebar, ev.target) ) {
ev.stopPropagation();
return;
}
$(sidebar).collapse('hide');
$(document).off(click_event);
})
}
else $(document).off(click_event);
});
*/
$.fn.ace_sidebar = function (option, value) {
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 method_call = data[option](value);
}
});
return (method_call === undefined) ? $set : method_call;
};
})(window.jQuery);
|