hjg
2024-10-30 8cf23534166c07e711aac2a25911ada317ba01f0
提交 | 用户 | 时间
58d006 1 /**
A 2  <b>Sidebar functions</b>. Collapsing/expanding, toggling mobile view menu and other sidebar functions.
3 */
4
5 (function($ , undefined) {
6     var sidebar_count = 0;
7
8     function Sidebar(sidebar, settings) {
9         var self = this;
10         this.$sidebar = $(sidebar);
11         this.$sidebar.attr('data-sidebar', 'true');
12         if( !this.$sidebar.attr('id') ) this.$sidebar.attr( 'id' , 'id-sidebar-'+(++sidebar_count) )
13
14         
15         //get a list of 'data-*' attributes that override 'defaults' and 'settings'
16         var attrib_values = ace.helper.getAttrSettings(sidebar, $.fn.ace_sidebar.defaults, 'sidebar-');
17         this.settings = $.extend({}, $.fn.ace_sidebar.defaults, settings, attrib_values);
18
19
20         //some vars
21         this.minimized = false;//will be initialized later
22         this.collapsible = false;//...
23         this.horizontal = false;//...
24         this.mobile_view = false;//
25
26
27         //return an array containing sidebar state variables
28         this.vars = function() {
29             return {'minimized': this.minimized, 'collapsible': this.collapsible, 'horizontal': this.horizontal, 'mobile_view': this.mobile_view}
30         }
31         this.get = function(name) {
32             if(this.hasOwnProperty(name)) return this[name];
33         }
34         this.set = function(name, value) {
35             if(this.hasOwnProperty(name)) this[name] = value;
36         }
37         
38
39         //return a reference to self (sidebar instance)
40         this.ref = function() {
41             return this;
42         }
43
44         
45         //toggle icon for sidebar collapse/expand button
46         var toggleIcon = function(minimized, save) {
47             var icon = $(this).find(ace.vars['.icon']), icon1, icon2;
48             if(icon.length > 0) {
49                 icon1 = icon.attr('data-icon1');//the icon for expanded state
50                 icon2 = icon.attr('data-icon2');//the icon for collapsed state
51
52                 if(typeof minimized !== "undefined") {
53                     if(minimized) icon.removeClass(icon1).addClass(icon2);
54                     else icon.removeClass(icon2).addClass(icon1);
55                 }
56                 else {
57                     icon.toggleClass(icon1).toggleClass(icon2);
58                 }
59                 
60                 try {
61                     if(save !== false) ace.settings.saveState(icon.get(0));
62                 } catch(e) {}
63             }
64         }
65         
66         //if not specified, find the toggle button related to this sidebar
67         var findToggleBtn = function() {
68             var toggle_btn = self.$sidebar.find('.sidebar-collapse');
69             if(toggle_btn.length == 0) toggle_btn = $('.sidebar-collapse[data-target="#'+(self.$sidebar.attr('id')||'')+'"]');
70             if(toggle_btn.length != 0) toggle_btn = toggle_btn[0];
71             else toggle_btn = null;
72             
73             return toggle_btn;
74         }
75         
76         
77         //collapse/expand sidebar
78         this.toggleMenu = function(toggle_btn, save) {
79             if(this.collapsible) return;
80
81             this.minimized = !this.minimized;
82             var save = !(toggle_btn === false || save === false);
83             
84         
85             if(this.minimized) this.$sidebar.addClass('menu-min');
86             else this.$sidebar.removeClass('menu-min');
87
88             try {
89                 if(save) ace.settings.saveState(sidebar, 'class', 'menu-min', this.minimized);
90             } catch(e) {}
91         
92             if( !toggle_btn ) {
93                 toggle_btn = findToggleBtn();
94             }
95             if(toggle_btn) {
96                 toggleIcon.call(toggle_btn, this.minimized, save);
97             }
98
99             //force redraw for ie8
100             if(ace.vars['old_ie']) ace.helper.redraw(sidebar);
101             
102             
103             $(document).trigger('settings.ace', ['sidebar_collapsed' , this.minimized, sidebar, save]);
104         }
105         this.collapse = function(toggle_btn, save) {
106             if(this.collapsible) return;
107             this.minimized = false;
108             
109             this.toggleMenu(toggle_btn, save);
110         }
111         this.expand = function(toggle_btn, save) {
112             if(this.collapsible) return;
113             this.minimized = true;
114             
115             this.toggleMenu(toggle_btn, save);
116         }
117         
118
119         
120         this.showResponsive = function() {
121             this.$sidebar.removeClass(responsive_min_class).removeClass(responsive_max_class);
122         }
123         
124         //collapse/expand in 2nd mobile style
125         this.toggleResponsive = function(toggle_btn, showMenu) {
126             if( !this.mobile_view || this.mobile_style != 3 ) return;
127         
128             if( this.$sidebar.hasClass('menu-min') ) {
129                 //remove menu-min because it interferes with responsive-max
130                 this.$sidebar.removeClass('menu-min');
131                 var btn = findToggleBtn();
132                 if(btn) toggleIcon.call(btn);
133             }
134
135
136             var showMenu = typeof showMenu !== 'undefined' ? showMenu : this.$sidebar.hasClass(responsive_min_class);
137             if(showMenu) {
138                 this.$sidebar.addClass(responsive_max_class).removeClass(responsive_min_class);
139             }
140             else {
141                 this.$sidebar.removeClass(responsive_max_class).addClass(responsive_min_class);
142             }
143             this.minimized = !showMenu;
144
145
146             if( !toggle_btn ) {
147                 toggle_btn = this.$sidebar.find('.sidebar-expand');
148                 if(toggle_btn.length == 0) toggle_btn = $('.sidebar-expand[data-target="#'+(this.$sidebar.attr('id')||'')+'"]');
149                 if(toggle_btn.length != 0) toggle_btn = toggle_btn[0];
150                 else toggle_btn = null;
151             }
152             
153             if(toggle_btn) {
154                 var icon = $(toggle_btn).find(ace.vars['.icon']), icon1, icon2;
155                 if(icon.length > 0) {
156                     icon1 = icon.attr('data-icon1');//the icon for expanded state
157                     icon2 = icon.attr('data-icon2');//the icon for collapsed state
158
159                     if(!showMenu) icon.removeClass(icon2).addClass(icon1);
160                     else icon.removeClass(icon1).addClass(icon2);
161                 }
162             }
163
164             $(document).triggerHandler('settings.ace', ['sidebar_collapsed' , this.minimized]);
165         }
166         
167         
168         //some helper functions
169         
170         //determine if we have 4th mobile style responsive sidebar and we are in mobile view
171         this.is_collapsible = function() {
172             var toggle
173             return (this.$sidebar.hasClass('navbar-collapse'))
174             && ((toggle = $('.navbar-toggle[data-target="#'+(this.$sidebar.attr('id')||'')+'"]').get(0)) != null)
175             &&  toggle.scrollHeight > 0
176             //sidebar is collapsible and collapse button is visible?
177         }
178         //determine if we are in mobile view
179         this.is_mobile_view = function() {
180             var toggle
181             return ((toggle = $('.menu-toggler[data-target="#'+(this.$sidebar.attr('id')||'')+'"]').get(0)) != null)
182             &&  toggle.scrollHeight > 0
183         }
184
185
186         //toggling (show/hide) submenu elements
187         this.$sidebar.on(ace.click_event+'.ace.submenu', '.nav-list', function (ev) {
188             var nav_list = this;
189
190             //check to see if we have clicked on an element which is inside a .dropdown-toggle element?!
191             //if so, it means we should toggle a submenu
192             var link_element = $(ev.target).closest('a');
193             if(!link_element || link_element.length == 0) return;//return if not clicked inside a link element
194
195             var minimized  = self.minimized && !self.collapsible;
196             //if .sidebar is .navbar-collapse and in small device mode, then let minimized be uneffective
197     
198             if( !link_element.hasClass('dropdown-toggle') ) {//it doesn't have a submenu return
199                 //just one thing before we return
200                 //if sidebar is collapsed(minimized) and we click on a first level menu item
201                 //and the click is on the icon, not on the menu text then let's cancel event and cancel navigation
202                 //Good for touch devices, that when the icon is tapped to see the menu text, navigation is cancelled
203                 //navigation is only done when menu text is tapped
204
205                 if( ace.click_event == 'tap'
206                     &&
207                     minimized
208                     &&
209                     link_element.get(0).parentNode.parentNode == nav_list )//only level-1 links
210                 {
211                     var text = link_element.find('.menu-text').get(0);
212                     if( text != null && ev.target != text && !$.contains(text , ev.target) ) {//not clicking on the text or its children
213                         ev.preventDefault();
214                         return false;
215                     }
216                 }
217
218
219                 //ios safari only has a bit of a problem not navigating to link address when scrolling down
220                 //specify data-link attribute to ignore this
221                 if(ace.vars['ios_safari'] && link_element.attr('data-link') !== 'false') {
222                     //only ios safari has a bit of a problem not navigating to link address when scrolling down
223                     //please see issues section in documentation
224                     document.location = link_element.attr('href');
225                     ev.preventDefault();
226                     return false;
227                 }
228
229                 return;
230             }
231             
232             ev.preventDefault();
233             
234             
235
236
237             var sub = link_element.siblings('.submenu').get(0);
238             if(!sub) return false;
239             var $sub = $(sub);
240
241             var height_change = 0;//the amount of height change in .nav-list
242
243             var parent_ul = sub.parentNode.parentNode;
244             if
245             (
246                 ( minimized && parent_ul == nav_list )
247                  || 
248                 ( ( $sub.parent().hasClass('hover') && $sub.css('position') == 'absolute' ) && !self.collapsible )
249             )
250             {
251                 return false;
252             }
253
254             
255             var sub_hidden = (sub.scrollHeight == 0)
256
257             //if not open and visible, let's open it and make it visible
258             if( sub_hidden && self.settings.hide_open_subs ) {//being shown now
259               $(parent_ul).find('> .open > .submenu').each(function() {
260                 //close all other open submenus except for the active one
261                 if(this != sub && !$(this.parentNode).hasClass('active')) {
262                     height_change -= this.scrollHeight;
263                     self.hide(this, self.settings.duration, false);
264                 }
265               })
266             }
267
268             if( sub_hidden ) {//being shown now
269                 self.show(sub, self.settings.duration);
270                 //if a submenu is being shown and another one previously started to hide, then we may need to update/hide scrollbars
271                 //but if no previous submenu is being hidden, then no need to check if we need to hide the scrollbars in advance
272                 if(height_change != 0) height_change += sub.scrollHeight;//we need new updated 'scrollHeight' here
273             } else {
274                 self.hide(sub, self.settings.duration);
275                 height_change -= sub.scrollHeight;
276                 //== -1 means submenu is being hidden
277             }
278
279             //hide scrollbars if content is going to be small enough that scrollbars is not needed anymore
280             //do this almost before submenu hiding begins
281             //but when minimized submenu's toggle should have no effect
282             if (height_change != 0) {
283                 if(self.$sidebar.attr('data-sidebar-scroll') == 'true' && !self.minimized) 
284                     self.$sidebar.ace_sidebar_scroll('prehide', height_change)
285             }
286
287             return false;
288         })
289
290         var submenu_working = false;
291         this.show = function(sub, $duration, shouldWait) {
292             //'shouldWait' indicates whether to wait for previous transition (submenu toggle) to be complete or not?
293             shouldWait = (shouldWait !== false);
294             if(shouldWait && submenu_working) return false;
295                     
296             var $sub = $(sub);
297             var event;
298             $sub.trigger(event = $.Event('show.ace.submenu'))
299             if (event.isDefaultPrevented()) {
300                 return false;
301             }
302             
303             if(shouldWait) submenu_working = true;
304
305
306             $duration = typeof $duration !== 'undefined' ? $duration : this.settings.duration;
307             
308             $sub.css({
309                 height: 0,
310                 overflow: 'hidden',
311                 display: 'block'
312             })
313             .removeClass('nav-hide').addClass('nav-show')//only for window < @grid-float-breakpoint and .navbar-collapse.menu-min
314             .parent().addClass('open');
315             
316             sub.scrollTop = 0;//this is for submenu_hover when sidebar is minimized and a submenu is scrollTop'ed using scrollbars ...
317
318             
319             var complete = function(ev, trigger) {
320                 ev && ev.stopPropagation();
321                 $sub
322                 .css({'transition-property': '', 'transition-duration': '', overflow:'', height: ''})
323                 //if(ace.vars['webkit']) ace.helper.redraw(sub);//little Chrome issue, force redraw ;)
324
325                 if(trigger !== false) $sub.trigger($.Event('shown.ace.submenu'))
326                 
327                 if(shouldWait) submenu_working = false;
328             }
329             
330             
331             var finalHeight = sub.scrollHeight;
332
333             if($duration == 0 || finalHeight == 0 || !$.support.transition.end) {
334                 //(if duration is zero || element is hidden (scrollHeight == 0) || CSS3 transitions are not available)
335                 complete();
336             }
337             else {
338                 $sub
339                 .css({
340                      'height': finalHeight,
341                      'transition-property': 'height',
342                      'transition-duration': ($duration/1000)+'s'
343                     }
344                 )
345                 .one($.support.transition.end, complete);
346                 
347                 //there is sometimes a glitch, so maybe retry
348                 if(ace.vars['android'] ) {
349                     setTimeout(function() {
350                         complete(null, false);
351                         ace.helper.redraw(sub);
352                     }, $duration + 20);
353                 }
354             }
355
356             return true;
357          }
358          
359          
360          this.hide = function(sub, $duration, shouldWait) {
361             //'shouldWait' indicates whether to wait for previous transition (submenu toggle) to be complete or not?
362             shouldWait = (shouldWait !== false);
363             if(shouldWait && submenu_working) return false;
364          
365             
366             var $sub = $(sub);
367             var event;
368             $sub.trigger(event = $.Event('hide.ace.submenu'))
369             if (event.isDefaultPrevented()) {
370                 return false;
371             }
372             
373             if(shouldWait) submenu_working = true;
374             
375
376             $duration = typeof $duration !== 'undefined' ? $duration : this.settings.duration;
377             
378             
379             var initialHeight = sub.scrollHeight;
380             $sub.css({
381                 height: initialHeight,
382                 overflow: 'hidden',
383                 display: 'block'
384             })
385             .parent().removeClass('open');
386
387             sub.offsetHeight;
388             //forces the "sub" to re-consider the new 'height' before transition
389
390             
391             var complete = function(ev, trigger) {
392                 ev && ev.stopPropagation();
393                 $sub
394                 .css({display: 'none', overflow:'', height: '', 'transition-property': '', 'transition-duration': ''})
395                 .removeClass('nav-show').addClass('nav-hide')//only for window < @grid-float-breakpoint and .navbar-collapse.menu-min
396
397                 if(trigger !== false) $sub.trigger($.Event('hidden.ace.submenu'))
398                 
399                 if(shouldWait) submenu_working = false;
400             }
401             
402             
403             if( $duration == 0 || initialHeight == 0 || !$.support.transition.end) {
404                 //(if duration is zero || element is hidden (scrollHeight == 0) || CSS3 transitions are not available)
405                 complete();
406             }
407             else {
408                 $sub
409                 .css({
410                      'height': 0,
411                      'transition-property': 'height',
412                      'transition-duration': ($duration/1000)+'s'
413                     }
414                 )
415                 .one($.support.transition.end, complete);
416                 
417                 //there is sometimes a glitch, so maybe retry
418                 if(ace.vars['android'] ) {
419                     setTimeout(function() {
420                         complete(null, false);
421                         ace.helper.redraw(sub);
422                     }, $duration + 20);
423                 }
424             }
425
426             return true;
427          }
428
429          this.toggle = function(sub, $duration) {
430             $duration = $duration || self.settings.duration;
431          
432             if( sub.scrollHeight == 0 ) {//if an element is hidden scrollHeight becomes 0
433                 if( this.show(sub, $duration) ) return 1;
434             } else {
435                 if( this.hide(sub, $duration) ) return -1;
436             }
437             return 0;
438          }
439
440
441         //sidebar vars
442         var minimized_menu_class  = 'menu-min';
443         var responsive_min_class  = 'responsive-min';
444         var responsive_max_class  = 'responsive-max';
445         var horizontal_menu_class = 'h-sidebar';
446
447         var sidebar_mobile_style = function() {
448             //differnet mobile menu styles
449             this.mobile_style = 1;//default responsive mode with toggle button inside navbar
450             if(this.$sidebar.hasClass('responsive') && !$('.menu-toggler[data-target="#'+this.$sidebar.attr('id')+'"]').hasClass('navbar-toggle')) this.mobile_style = 2;//toggle button behind sidebar
451              else if(this.$sidebar.hasClass(responsive_min_class)) this.mobile_style = 3;//minimized menu
452               else if(this.$sidebar.hasClass('navbar-collapse')) this.mobile_style = 4;//collapsible (bootstrap style)
453         }
454         sidebar_mobile_style.call(self);
455           
456         function update_vars() {
457             this.mobile_view = this.mobile_style < 4 && this.is_mobile_view();
458             this.collapsible = !this.mobile_view && this.is_collapsible();
459
460             this.minimized = 
461             (!this.collapsible && this.$sidebar.hasClass(minimized_menu_class))
462              ||
463             (this.mobile_style == 3 && this.mobile_view && this.$sidebar.hasClass(responsive_min_class))
464
465             this.horizontal = !(this.mobile_view || this.collapsible) && this.$sidebar.hasClass(horizontal_menu_class)
466         }
467
468         //update some basic variables
469         $(window).on('resize.sidebar.vars' , function(){
470             update_vars.call(self);
471         }).triggerHandler('resize.sidebar.vars')
472         
473         
474         
475         
476         
477         this.mobileToggle = function(showMenu) {
478             var showMenu = typeof showMenu === "undefined" ? undefined : showMenu;
479             
480             if(this.mobile_view) {
481                 if(this.mobile_style == 1 || this.mobile_style == 2) {
482                     this.toggleMobile(null, showMenu);
483                 }
484                 else if(this.mobile_style == 3) {
485                     this.toggleResponsive(null, showMenu);
486                 }
487             }
488             else if(this.collapsible) {
489                 this.toggleCollapsible(null, showMenu);
490             }
491         }
492         this.mobileShow = function() {
493             this.mobileToggle(true);
494         }
495         this.mobileHide = function() {
496             this.mobileToggle(false);
497         }
498         
499         
500         
501         this.toggleMobile = function(toggle_btn, showMenu) {
502             if(!(this.mobile_style == 1 || this.mobile_style == 2)) return;
503             
504             var showMenu = typeof showMenu !== 'undefined' ? showMenu : !this.$sidebar.hasClass('display');
505             if(!toggle_btn) {
506                 toggle_btn = $('.menu-toggler[data-target="#'+(this.$sidebar.attr('id')||'')+'"]');
507                 if(toggle_btn.length != 0) toggle_btn = toggle_btn[0];
508                 else toggle_btn = null;
509             }
510             if(showMenu) {
511                 this.$sidebar.addClass('display');
512                 if(toggle_btn) $(toggle_btn).addClass('display');
513             }
514             else {
515                 this.$sidebar.removeClass('display');
516                 if(toggle_btn) $(toggle_btn).removeClass('display');
517             }
518         }
519         
520         
521         this.toggleCollapsible = function(toggle_btn, showMenu) {
522             if(this.mobile_style != 4) return;
523             
524             var showMenu = typeof showMenu !== 'undefined' ? showMenu : !this.$sidebar.hasClass('in');
525             if(showMenu) {
526                 this.$sidebar.collapse('show');
527             }
528             else {
529                 this.$sidebar.removeClass('display');
530                 this.$sidebar.collapse('hide');
531             }    
532         }
533
534     }//end of Sidebar
535     
536
537     //sidebar events
538     
539     //menu-toggler
540     $(document)
541     .on(ace.click_event+'.ace.menu', '.menu-toggler', function(e){
542         var btn = $(this);
543         var sidebar = $(btn.attr('data-target'));
544         if(sidebar.length == 0) return;
545         
546         e.preventDefault();
547                 
548         //sidebar.toggleClass('display');
549         //btn.toggleClass('display');
550         
551         sidebar.ace_sidebar('toggleMobile', this);
552         
553         var click_event = ace.click_event+'.ace.autohide';
554         var auto_hide = sidebar.attr('data-auto-hide') === 'true';
555
556         if( btn.hasClass('display') ) {
557             //hide menu if clicked outside of it!
558             if(auto_hide) {
559                 $(document).on(click_event, function(ev) {
560                     if( sidebar.get(0) == ev.target || $.contains(sidebar.get(0), ev.target) ) {
561                         ev.stopPropagation();
562                         return;
563                     }
564
565                     sidebar.ace_sidebar('toggleMobile', this, false);
566                     $(document).off(click_event);
567                 })
568             }
569
570             if(sidebar.attr('data-sidebar-scroll') == 'true') sidebar.ace_sidebar_scroll('reset');
571         }
572         else {
573             if(auto_hide) $(document).off(click_event);
574         }
575
576         return false;
577     })
578     //sidebar collapse/expand button
579     .on(ace.click_event+'.ace.menu', '.sidebar-collapse', function(e){
580         
581         var target = $(this).attr('data-target'), $sidebar = null;
582         if(target) $sidebar = $(target);
583         if($sidebar == null || $sidebar.length == 0) $sidebar = $(this).closest('.sidebar');
584         if($sidebar.length == 0) return;
585
586         e.preventDefault();
587         $sidebar.ace_sidebar('toggleMenu', this);
588     })
589     //this button is used in `mobile_style = 3` responsive menu style to expand minimized sidebar
590     .on(ace.click_event+'.ace.menu', '.sidebar-expand', function(e){
591         var target = $(this).attr('data-target'), $sidebar = null;
592         if(target) $sidebar = $(target);
593         if($sidebar == null || $sidebar.length == 0) $sidebar = $(this).closest('.sidebar');
594         if($sidebar.length == 0) return;    
595     
596         var btn = this;
597         e.preventDefault();
598         $sidebar.ace_sidebar('toggleResponsive', this);
599         
600         var click_event = ace.click_event+'.ace.autohide';
601         if($sidebar.attr('data-auto-hide') === 'true') {
602             if( $sidebar.hasClass(responsive_max_class) ) {
603                 $(document).on(click_event, function(ev) {
604                     if( $sidebar.get(0) == ev.target || $.contains($sidebar.get(0), ev.target) ) {
605                         ev.stopPropagation();
606                         return;
607                     }
608
609                     $sidebar.ace_sidebar('toggleResponsive', btn);
610                     $(document).off(click_event);
611                 })
612             }
613             else {
614                 $(document).off(click_event);
615             }
616         }
617     })
618
619     
620     $.fn.ace_sidebar = function (option, value, value2) {
621         var method_call;
622
623         var $set = this.each(function () {
624             var $this = $(this);
625             var data = $this.data('ace_sidebar');
626             var options = typeof option === 'object' && option;
627
628             if (!data) $this.data('ace_sidebar', (data = new Sidebar(this, options)));
629             if (typeof option === 'string' && typeof data[option] === 'function') {
630                 if(value instanceof Array) method_call = data[option].apply(data, value);
631                 else if(value2 !== undefined) method_call = data[option](value, value2);
632                 else method_call = data[option](value);
633             }
634         });
635
636         return (method_call === undefined) ? $set : method_call;
637     };
638     
639     
640     $.fn.ace_sidebar.defaults = {
641         'duration': 300,
642         'hide_open_subs': true
643     }
644
645
646 })(window.jQuery);