PageSourceSearch

https://www.pittsburghclo.org/site-resources/js/jquery.slicknav.js

js pittsburghclo.org collected 2026-10-02 05:57:45 UTC 16,142 bytes, 441 lines download raw bytes

vendor: 16,142 bytes, lines 1-441
1/*!
2    SlickNav Responsive Mobile Menu v1.0.1
3    (c) 2014 Josh Cope
4    licensed under MIT
5*/
6;(function ($, document, window) {
7    var
8    // default settings object.
9        defaults = {
10            label: 'MENU',
11            duplicate: true,
12            duration: 200,
13            easingOpen: 'swing',
14            easingClose: 'swing',
15            closedSymbol: '►',
16            openedSymbol: '▼',
17            prependTo: 'body',
18            parentTag: 'a',
19            closeOnClick: false,
20            allowParentLinks: false,
21            nestedParentLinks: true,
22            showChildren: false,
23            init: function () {},
24            open: function () {},
25            close: function () {}
26        },
27        mobileMenu = 'slicknav',
28        prefix = 'slicknav';
29
30    function Plugin(element, options) {
31        this.element = element;
32
33        // jQuery has an extend method which merges the contents of two or
34        // more objects, storing the result in the first object. The first object
35        // is generally empty as we don't want to alter the default options for
36        // future instances of the plugin
37        this.settings = $.extend({}, defaults, options);
38
39        this._defaults = defaults;
40        this._name = mobileMenu;
41
42        this.init();
43    }
44
45    Plugin.prototype.init = function () {
46        var $this = this,
47            menu = $(this.element),
48            settings = this.settings,
49            iconClass,
50            menuBar;
51
52        // clone menu if needed
53        if (settings.duplicate) {
54            $this.mobileNav = menu.clone();
55            //remove ids from clone to prevent css issues
56            $this.mobileNav.removeAttr('id');
57            $this.mobileNav.find('*').each(function (i, e) {
58                $(e).removeAttr('id');
59            });
60        } else {
61            $this.mobileNav = menu;
62        }
63
64        // styling class for the button
65        iconClass = prefix + '_icon';
66
67        if (settings.label === '') {
68            iconClass += ' ' + prefix + '_no-text';
69        }
70
71        if (settings.parentTag == 'a') {
72            settings.parentTag = 'a href="#"';
73        }
74
75        // create menu bar
76        $this.mobileNav.attr('class', prefix + '_nav');
77        menuBar = $('<div class="' + prefix + '_menu"></div>');
78        $this.btn = $(
79            ['<' + settings.parentTag + ' aria-haspopup="true" tabindex="0" class="' + prefix + '_btn ' + prefix + '_collapsed">',
80                '<span class="' + prefix + '_menutxt">' + settings.label + '</span>',
81                '<span class="' + iconClass + '">',
82                    '<span class="' + prefix + '_icon-bar"></span>',
83                    '<span class="' + prefix + '_icon-bar"></span>',
84                    '<span class="' + prefix + '_icon-bar"></span>',
85                '</span>',
86            '</' + settings.parentTag + '>'
87            ].join('')
88        );
89        $(menuBar).append($this.btn);
90        $(settings.prependTo).prepend(menuBar);
91        menuBar.append($this.mobileNav);
92
93        // iterate over structure adding additional structure
94        var items = $this.mobileNav.find('li');
95        $(items).each(function () {
96            var item = $(this),
97                data = {};
98            data.children = item.children('ul').attr('role', 'menu');
99            item.data('menu', data);
100
101            // if a list item has a nested menu
102            if (data.children.length > 0) {
103
104                // select all text before the child menu
105                // check for anchors
106
107                var a = item.contents(),
108                    containsAnchor = false;
109                    nodes = [];
110
111                $(a).each(function () {
112                    if (!$(this).is('ul')) {
113                        nodes.push(this);
114                    } else {
115                        return false;
116                    }
117
118                    if($(this).is("a")) {
119                        containsAnchor = true;
120                    }
121                });
122
123                var wrapElement = $(
124                    '<' + settings.parentTag + ' role="menuitem" aria-haspopup="true" tabindex="-1" class="' + prefix + '_item"/>'
125                );
126
127                // wrap item text with tag and add classes unless we are separating parent links
128                if ((!settings.allowParentLinks || settings.nestedParentLinks) || !containsAnchor) {
129                    var $wrap = $(nodes).wrapAll(wrapElement).parent();
130                    $wrap.addClass(prefix+'_row');
131                } else
132                    $(nodes).wrapAll('<span class="'+prefix+'_parent-link '+prefix+'_row"/>').parent();
133
134                item.addClass(prefix+'_collapsed');
135                item.addClass(prefix+'_parent');
136
137                // create parent arrow. wrap with link if parent links and separating
138                var arrowElement = $('<span class="'+prefix+'_arrow"><img src="/site-resources/images/plus.gif" /></span>');
139
140                if (settings.allowParentLinks && !settings.nestedParentLinks && containsAnchor)
141                    arrowElement = arrowElement.wrap(wrapElement).parent();
142
143                //append arrow
144                $(nodes).last().after(arrowElement);
145
146
147            } else if ( item.children().length === 0) {
148                 item.addClass(prefix+'_txtnode');
149            }
150
151            // accessibility for links
152            item.children('a').attr('role', 'menuitem').click(function(event){
153                //Ensure that it's not a parent
154                if (settings.closeOnClick && !$(event.target).parent().closest('li').hasClass(prefix+'_parent')) {
155                        //Emulate menu close if set
156                        $($this.btn).click();
157                    }
158            });
159
160            //also close on click if parent links are set
161            if (settings.closeOnClick && settings.allowParentLinks) {
162                item.children('a').children('a').click(function (event) {
163                    //Emulate menu close
164                    $($this.btn).click();
165                });
166
167                item.find('.'+prefix+'_parent-link a:not(.'+prefix+'_item)').click(function(event){
168                    //Emulate menu close
169                        $($this.btn).click();
170                });
171            }
172        });
173
174        // structure is in place, now hide appropriate items
175        $(items).each(function () {
176            var data = $(this).data('menu');
177            if (!settings.showChildren){
178                $this._visibilityToggle(data.children, null, false, null, true);
179            }
180        });
181
182        // finally toggle entire menu
183        $this._visibilityToggle($this.mobileNav, null, false, 'init', true);
184
185        // accessibility for menu button
186        $this.mobileNav.attr('role','menu');
187
188        // outline prevention when using mouse
189        $(document).mousedown(function(){
190            $this._outlines(false);
191        });
192
193        $(document).keyup(function(){
194            $this._outlines(true);
195        });
196
197        // menu button click
198        $($this.btn).click(function (e) {
199            e.preventDefault();
200            $this._menuToggle();
201        });
202
203        // click on menu parent
204        $this.mobileNav.on('click', '.' + prefix + '_item', function (e) {
205            e.preventDefault();
206            $this._itemClick($(this));
207        });
208
209        // check for enter key on menu button and menu parents
210        $($this.btn).keydown(function (e) {
211            var ev = e || event;
212            if(ev.keyCode == 13) {
213                e.preventDefault();
214                $this._menuToggle();
215            }
216        });
217
218        $this.mobileNav.on('keydown', '.'+prefix+'_item', function(e) {
219            var ev = e || event;
220            if(ev.keyCode == 13) {
221                e.preventDefault();
222                $this._itemClick($(e.target));
223            }
224        });
225
226        // allow links clickable within parent tags if set
227        if (settings.allowParentLinks && settings.nestedParentLinks) {
228            $('.'+prefix+'_item a').click(function(e){
229                    e.stopImmediatePropagation();
230            });
231        }
232    };
233
234    //toggle menu
235    Plugin.prototype._menuToggle = function (el) {
236        var $this = this;
237        var btn = $this.btn;
238        var mobileNav = $this.mobileNav;
239		
240        if (btn.hasClass(prefix+'_collapsed')) {
241            btn.removeClass(prefix+'_collapsed');
242            btn.addClass(prefix+'_open');
243        } else {
244            btn.removeClass(prefix+'_open');
245            btn.addClass(prefix+'_collapsed');
246        }
247        btn.addClass(prefix+'_animating');
248
249		
250		
251        $this._visibilityToggle(mobileNav, btn.parent(), true, btn);
252    };
253
254    // toggle clicked items
255    Plugin.prototype._itemClick = function (el) {
256        var $this = this;
257        var settings = $this.settings;
258        var data = el.data('menu');
259        if (!data) {
260            data = {};
261            data.arrow = el.children('.'+prefix+'_arrow');
262            data.ul = el.next('ul');
263            data.parent = el.parent();
264            //Separated parent link structure
265            if (data.parent.hasClass(prefix+'_parent-link')) {
266                data.parent = el.parent().parent();
267                data.ul = el.parent().next('ul');
268            }
269            el.data('menu', data);
270        }
271        if (data.parent.hasClass(prefix+'_collapsed')) {
272            data.arrow.html('<img src="/site-resources/images/minus.gif" />');
273            data.parent.removeClass(prefix+'_collapsed');
274            data.parent.addClass(prefix+'_open');
275            data.parent.addClass(prefix+'_animating');
276            $this._visibilityToggle(data.ul, data.parent, true, el);
277        } else {
278            data.arrow.html('<img src="/site-resources/images/plus.gif" />');
279            data.parent.addClass(prefix+'_collapsed');
280            data.parent.removeClass(prefix+'_open');
281            data.parent.addClass(prefix+'_animating');
282            $this._visibilityToggle(data.ul, data.parent, true, el);
283        }
284    };
285
286    // toggle actual visibility and accessibility tags
287    Plugin.prototype._visibilityToggle = function(el, parent, animate, trigger, init) {
288        var $this = this;
289        var settings = $this.settings;
290        var items = $this._getActionItems(el);
291        var duration = 0;
292        if (animate) {
293            duration = settings.duration;
294        }
295
296        if (el.hasClass(prefix+'_hidden')) {
297            el.removeClass(prefix+'_hidden');
298            el.slideDown(duration, settings.easingOpen, function(){
299
300                $(trigger).removeClass(prefix+'_animating');
301                $(parent).removeClass(prefix+'_animating');
302
303                //Fire open callback
304                if (!init) {
305                    settings.open(trigger);
306                }
307            });
308            el.attr('aria-hidden','false');
309            items.attr('tabindex', '0');
310            $this._setVisAttr(el, false);
311        } else {
312            el.addClass(prefix+'_hidden');
313            el.slideUp(duration, this.settings.easingClose, function() {
314                el.attr('aria-hidden','true');
315                items.attr('tabindex', '-1');
316                $this._setVisAttr(el, true);
317                el.hide(); //jQuery 1.7 bug fix
318
319                $(trigger).removeClass(prefix+'_animating');
320                $(parent).removeClass(prefix+'_animating');
321
322                //Fire init or close callback
323                if (!init){
324                    settings.close(trigger);
325                }
326                else if (trigger == 'init'){
327                    settings.init();
328                }
329            });
330        }
331    };
332
333    // set attributes of element and children based on visibility
334    Plugin.prototype._setVisAttr = function(el, hidden) {
335        var $this = this;
336
337        // select all parents that aren't hidden
338        var nonHidden = el.children('li').children('ul').not('.'+prefix+'_hidden');
339
340        // iterate over all items setting appropriate tags
341        if (!hidden) {
342            nonHidden.each(function(){
343                var ul = $(this);
344                ul.attr('aria-hidden','false');
345                var items = $this._getActionItems(ul);
346                items.attr('tabindex', '0');
347                $this._setVisAttr(ul, hidden);
348            });
349        } else {
350            nonHidden.each(function(){
351                var ul = $(this);
352                ul.attr('aria-hidden','true');
353                var items = $this._getActionItems(ul);
354                items.attr('tabindex', '-1');
355                $this._setVisAttr(ul, hidden);
356            });
357        }
358    };
359
360    // get all 1st level items that are clickable
361    Plugin.prototype._getActionItems = function(el) {
362        var data = el.data("menu");
363        if (!data) {
364            data = {};
365            var items = el.children('li');
366            var anchors = items.find('a');
367            data.links = anchors.add(items.find('.'+prefix+'_item'));
368            el.data('menu', data);
369        }
370        return data.links;
371    };
372
373    Plugin.prototype._outlines = function(state) {
374        if (!state) {
375            $('.'+prefix+'_item, .'+prefix+'_btn').css('outline','none');
376        } else {
377            $('.'+prefix+'_item, .'+prefix+'_btn').css('outline','');
378        }
379    };
380
381    Plugin.prototype.toggle = function(){
382        var $this = this;
383        $this._menuToggle();
384    };
385
386    Plugin.prototype.open = function(){
387        var $this = this;
388        if ($this.btn.hasClass(prefix+'_collapsed')) {
389            $this._menuToggle();
390        }
391    };
392
393    Plugin.prototype.close = function(){
394        var $this = this;
395		$('.slicknav_nav .slicknav_open a').each(function(){
396			
397			$this._itemClick($(this));
398		});
399        if ($this.btn.hasClass(prefix+'_open')) {
400            $this._menuToggle();
401        }
402    };
403
404    $.fn[mobileMenu] = function ( options ) {
405        var args = arguments;
406
407        // Is the first parameter an object (options), or was omitted, instantiate a new instance
408        if (options === undefined || typeof options === 'object') {
409            return this.each(function () {
410
411                // Only allow the plugin to be instantiated once due to methods
412                if (!$.data(this, 'plugin_' + mobileMenu)) {
413
414                    // if it has no instance, create a new one, pass options to our plugin constructor,
415                    // and store the plugin instance in the elements jQuery data object.
416                    $.data(this, 'plugin_' + mobileMenu, new Plugin( this, options ));
417                }
418            });
419
420        // If is a string and doesn't start with an underscore or 'init' function, treat this as a call to a public method.
421        } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
422
423            // Cache the method call to make it possible to return a value
424            var returns;
425
426            this.each(function () {
427                var instance = $.data(this, 'plugin_' + mobileMenu);
428
429                // Tests that there's already a plugin-instance and checks that the requested public method exists
430                if (instance instanceof Plugin && typeof instance[options] === 'function') {
431
432                    // Call the method of our plugin instance, and pass it the supplied arguments.
433                    returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
434                }
435            });
436
437            // If the earlier cached method gives a value back return the value, otherwise return this to preserve chainability.
438            return returns !== undefined ? returns : this;
439        }
440    };
441}(jQuery, document, window));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.