/** sidebar functions. 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 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, 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 = $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 ... 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(shouldwait) 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, 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 = $duration || this.settings.duration; $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(shouldwait) 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 || 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 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); } } }) $.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; }; $.fn.ace_sidebar.defaults = { 'duration': 300 } })(window.jquery);