PageSourceSearch

https://www.ms.gov/libraries/drupal-superfish/sfsmallscreen.js?tk35s9

js ms.gov collected 2026-09-24 06:58:45 UTC 17,421 bytes, 365 lines download raw bytes

1/*
2 * sf-Smallscreen v1.3b - Provides small-screen compatibility for the jQuery Superfish plugin.
3 *
4 * Developer's note:
5 * Built as a part of the Superfish project for Drupal (http://drupal.org/project/superfish)
6 * Found any bug? have any cool ideas? contact me right away! http://drupal.org/user/619294/contact
7 *
8 * jQuery version: 1.3.x or higher.
9 *
10 * Dual licensed under the MIT and GPL licenses:
11 *  http://www.opensource.org/licenses/mit-license.php
12 *  http://www.gnu.org/licenses/gpl.html
13  */
14
15(function($){
16  $.fn.sfsmallscreen = function(options){
17    options = $.extend({
18      mode: 'inactive',
19      type: 'accordion',
20      breakpoint: 768,
21      breakpointUnit: 'px',
22      useragent: '',
23      title: '',
24      addSelected: false,
25      menuClasses: false,
26      hyperlinkClasses: false,
27      excludeClass_menu: '',
28      excludeClass_hyperlink: '',
29      includeClass_menu: '',
30      includeClass_hyperlink: '',
31      accordionButton: 1,
32      expandText: 'Expand',
33      collapseText: 'Collapse'
34    }, options);
35
36    // We need to clean up the menu from anything unnecessary.
37    function refine(menu){
38      const refined = menu.clone();
39      // Things that should not be in the small-screen menus.
40      const rm = refined.find('span.sf-sub-indicator');
41      // This is a helper class for those who need to add extra markup that shouldn't exist
42      // in the small-screen versions.
43      const rh = refined.find('.sf-smallscreen-remove');
44      // Mega-menus have to be removed too.
45      const mm = refined.find('ul.sf-multicolumn');
46      for (let a = 0; a < rh.length; a++){
47        rh.eq(a).replaceWith(rh.eq(a).html());
48      }
49      if (options.accordionButton === 2 || options.type === 'select'){
50        for (let b = 0; b < rm.length; b++){
51          rm.eq(b).remove();
52        }
53      }
54      if (mm.length > 0){
55        mm.removeClass('sf-multicolumn');
56        const ol = refined.find('div.sf-multicolumn-column > ol');
57        for (let o = 0; o < ol.length; o++){
58          ol.eq(o).replaceWith('<ul>' + ol.eq(o).html() + '</ul>');
59        }
60        const elements = [
61          'div.sf-multicolumn-column',
62          '.sf-multicolumn-wrapper > ol',
63          'li.sf-multicolumn-wrapper'
64        ];
65        for (let i = 0; i < elements.length; i++){
66          let obj = refined.find(elements[i]);
67          for (let t = 0; t < obj.length; t++){
68            obj.eq(t).replaceWith(obj.eq(t).html());
69          }
70        }
71        refined.find('.sf-multicolumn-column').removeClass('sf-multicolumn-column');
72      }
73      refined.add(refined.find('*')).css({width:''});
74      return refined;
75    }
76
77    // Creating <option> elements out of the menu.
78    function toSelect(menu, level){
79      let items = '';
80      const childLI = $(menu).children('li');
81      for (let a = 0; a < childLI.length; a++){
82        const list = childLI.eq(a)
83        const parent = list.children('a, span');
84        for (let b = 0; b < parent.length; b++){
85          const item = parent.eq(b);
86          const path = (item.is('a') && !!item.attr('href')) ? item.attr('href') : '';
87          // Class names modification.
88          const itemClone = item.clone();
89          let classes = (options.hyperlinkClasses) ? ((options.excludeClass_hyperlink && itemClone.hasClass(options.excludeClass_hyperlink)) ? itemClone.removeClass(options.excludeClass_hyperlink).attr('class') : itemClone.attr('class')) : '';
90          classes = (options.includeClass_hyperlink && !itemClone.hasClass(options.includeClass_hyperlink)) ? ((options.hyperlinkClasses) ? itemClone.addClass(options.includeClass_hyperlink).attr('class') : options.includeClass_hyperlink) : classes;
91          // Retaining the active class if requested.
92          if (options.addSelected && item.hasClass('active')){
93            classes += ' active';
94          }
95          classes = classes ? ' class="' + classes + '"' : '';
96          // <option> has to be disabled if the item is not a link.
97          const disable = path === '' || path === '#' ? ' disabled="disabled"' : '';
98          // Crystal clear.
99          const subIndicator = 1 < level ? Array(level).join('-') + ' ' : '';
100          // Preparing the <option> element.
101          items += '<option value="' + path + '"' + classes + disable + '>' + subIndicator + item.text().trim() +'</option>';
102          const childUL = list.find('> ul');
103          // Using the function for the sub-menu of this item.
104          for (let u = 0; u < childUL.length; u++){
105            items += toSelect(childUL.eq(u), level + 1);
106          }
107        }
108      }
109      return items;
110    }
111
112    // Create the new version, hide the original.
113    function convert(menu){
114      const menuID = menu.attr('id');
115      // Creating a refined version of the menu.
116      const refinedMenu = refine(menu);
117      // Currently, the plugin provides two reactions to small screens.
118      // Converting the menu to a <select> element, and converting to an accordion version of the menu.
119      if (options.type === 'accordion'){
120        const toggleID = menuID + '-toggle';
121        const accordionID = menuID + '-accordion';
122        // Making sure the accordion does not exist.
123        if ($('#' + accordionID).length !== 0){
124          return;
125        }
126
127        // Getting the style class.
128        const styleClass = menu.attr('class').split(' ').filter(function(item){
129          return item.indexOf('sf-style-') > -1 ? item : '';
130        });
131        // Creating the accordion.
132        const accordion = $(refinedMenu).attr('id', accordionID);
133        // Removing unnecessary classes.
134        accordion.removeClass('sf-horizontal sf-vertical sf-navbar sf-shadow sf-js-enabled');
135        // Adding necessary classes.
136        accordion.addClass('sf-accordion sf-hidden');
137        // Removing style attributes and any unnecessary class.
138        accordion.find('li').each(function(){
139          $(this).removeAttr('style').removeClass('sfHover').attr('id', $(this).attr('id') + '-accordion');
140        });
141        // Doing the same and making sure all the sub-menus are off-screen (hidden).
142        accordion.children('ul').removeAttr('style').not('.sf-hidden').addClass('sf-hidden');
143        accordion.find('ul').each(function(){
144          $(this).removeAttr('style').not('.sf-hidden').addClass('sf-hidden');
145        });
146
147        // Creating the accordion toggle switch.
148        const toggle = '<div class="sf-accordion-toggle ' + styleClass + '"><a href="#" id="' + toggleID + '"><span>' + options.title + '</span></a></div>';
149
150        // Adding Expand\Collapse buttons if requested.
151        if (options.accordionButton === 2){
152          accordion.addClass('sf-accordion-with-buttons');
153          const parent = accordion.find('li.menuparent');
154          for (let i = 0; i < parent.length; i++){
155            parent.eq(i).prepend('<a href="#" class="sf-accordion-button">' + options.expandText + '</a>');
156          }
157        }
158        // Inserting the accordion and hiding the original menu.
159        menu.before(toggle).before(accordion).hide();
160
161        const $accordionElement = $('#' + accordionID);
162        // Deciding what should be used as accordion buttons.
163        const buttonElement = (options.accordionButton < 2) ? 'a.menuparent,span.nolink.menuparent' : 'a.sf-accordion-button';
164        const $button = $accordionElement.find(buttonElement);
165
166        // Attaching a click event to the toggle switch.
167        $('#' + toggleID).on('click', function(e){
168          // Preventing the click.
169          e.preventDefault();
170          // Adding the sf-expanded class.
171          $(this).toggleClass('sf-expanded');
172
173          if ($accordionElement.hasClass('sf-expanded')){
174            // If the accordion is already expanded:
175            // Hiding its expanded sub-menus and then the accordion itself as well.
176            $accordionElement.add($accordionElement.find('li.sf-expanded')).removeClass('sf-expanded')
177              .end().children('ul').hide()
178              // This is a bit tricky, it's the same trick that has been in use in the main plugin for some time.
179              // Basically, we'll add a class that keeps the sub-menu off-screen and still visible,
180              // and make it invisible and remove the class one moment before showing or hiding it.
181              // This helps screen reader software access all the menu items.
182              .end().hide().addClass('sf-hidden').show();
183            // Changing the caption of any existing accordion buttons to 'Expand'.
184            if (options.accordionButton === 2){
185              $accordionElement.find('a.sf-accordion-button').text(options.expandText);
186            }
187          }
188          else {
189            // But if it's collapsed,
190            $accordionElement.addClass('sf-expanded').hide().removeClass('sf-hidden').show();
191          }
192        });
193
194        // Attaching a click event to the buttons.
195        $button.on('click', function(e){
196          // Making sure the button does not exist already.
197          if ($(this).closest('li').children('ul').length === 0){
198            return;
199          }
200
201          e.preventDefault();
202
203          // Selecting the parent menu items.
204          const $parent = $(this).closest('li');
205
206          // Creating and inserting Expand\Collapse buttons to the parent menu items,
207          // of course only if not already happened.
208          if (options.accordionButton === 1 &&
209            $parent.children('a.menuparent,span.nolink.menuparent').length > 0 &&
210            $parent.children('ul').children('li.sf-clone-parent').length === 0) {
211
212            // Cloning the hyperlink of the parent menu item.
213            let cloneLink = $parent.children('a.menuparent').clone();
214            // Removing unnecessary classes and element(s).
215            cloneLink.removeClass('menuparent sf-with-ul').children('.sf-sub-indicator').remove();
216            // Wrapping the hyerplinks in <li>.
217            cloneLink = $('<li class="sf-clone-parent" />').html(cloneLink);
218            // Adding a helper class and attaching them to the sub-menus.
219            $parent.children('ul').addClass('sf-has-clone-parent').prepend(cloneLink);
220          }
221
222          if (options.accordionButton === 0 || options.accordionButton === 1){
223            // We want to hide a menu, when we click on the menu item with an anchor.
224            $accordionElement.find('a.is-active:not(.menuparent)').on('click', function(e){
225              $accordionElement.removeClass('sf-expanded').addClass('sf-hidden');
226            });
227          }
228
229          // Changing the caption of the inserted Collapse link to 'Expand', if any is inserted.
230          if (options.accordionButton === 2){
231            // We want to hide a menu, when we click on the menu item with an anchor.
232            $accordionElement.find('a.is-active').on('click', function(e){
233              $accordionElement.removeClass('sf-expanded').addClass('sf-hidden');
234            });
235          }
236
237          // Once the button is clicked, collapse the sub-menu if it's expanded.
238          if ($parent.hasClass('sf-expanded')){
239            $parent.children('ul').slideUp('fast', function(){
240              // Doing the accessibility trick after hiding the sub-menu.
241              $(this).closest('li').removeClass('sf-expanded').end().addClass('sf-hidden').show();
242            });
243            // Changing the caption of the inserted Collapse link to 'Expand', if any is inserted.
244            if (options.accordionButton === 2 && $parent.children('.sf-accordion-button').length > 0){
245              $parent.children('.sf-accordion-button').text(options.expandText);
246            }
247          }
248          // Otherwise, expand the sub-menu.
249          else {
250            // Doing the accessibility trick and then showing the sub-menu.
251            $parent.children('ul').hide().removeClass('sf-hidden').slideDown('fast')
252              // Changing the caption of the inserted Expand link to 'Collapse' if any is inserted.
253              .end().addClass('sf-expanded').children('a.sf-accordion-button').text(options.collapseText)
254              // Hiding any expanded sub-menu of the same level.
255              .end().siblings('li.sf-expanded').children('ul')
256              .slideUp('fast', function(){
257                // Doing the accessibility trick after hiding it.
258                $(this).closest('li').removeClass('sf-expanded').end().addClass('sf-hidden').show();
259              })
260              // Assuming Expand\Collapse buttons do exist, resetting captions, in those hidden sub-menus.
261              .parent().children('a.sf-accordion-button').text(options.expandText);
262          }
263        });
264      }
265      else {
266        const menuClone = menu.clone();
267        // Class names modification.
268        let classes = options.menuClasses ? ((options.excludeClass_menu && menuClone.hasClass(options.excludeClass_menu)) ? menuClone.removeClass(options.excludeClass_menu).attr('class') : menuClone.attr('class')) : '';
269        classes = options.includeClass_menu && !menuClone.hasClass(options.includeClass_menu) ? ((options.menuClasses) ? menuClone.addClass(options.includeClass_menu).attr('class') : options.includeClass_menu) : classes;
270        classes = classes ? ' class="' + classes + '"' : '';
271
272        // Making sure the <select> element does not exist already.
273        if ($('#' + menuID + '-select').length !== 0){
274          return;
275        }
276
277        // Creating the <option> elements.
278        const newMenu = toSelect(refinedMenu, 1);
279        // Creating the <select>
279 element and assigning an ID and class name.
280        const selectList = $('<select ' + classes + ' id="' + menuID + '-select"/>')
281          // Attaching the title and the items to the <select> element.
282          .html('<option>' + options.title + '</option>' + newMenu)
283          // Attaching an event then.
284          .change(function(){
285            // Except for the first option that is the menu title and not a real menu item.
286            if ($('option:selected', this).index()){
287              window.location = selectList.val();
288            }
289          });
290        // Applying the addSelected option to it.
291        if (options.addSelected){
292          selectList.find('.active').attr('selected', !0);
293        }
294        // Finally inserting the <select> element into the document then hiding the original menu.
295        menu.before(selectList).hide();
296      }
297    }
298
299    // Turn everything back to normal.
300    function turnBack(menu){
301      const id = '#' + menu.attr('id');
302      // Removing the small screen version.
303      $(id + '-' + options.type).remove();
304      // Removing the accordion toggle switch as well.
305      if (options.type === 'accordion'){
306        $(id + '-toggle').parent('div').remove();
307      }
308      // Remove inline CSS display property; less clear than simply using .show(), but respects stylesheet
309      $(id).css('display', '');
310    }
311
312    // Return an original object to support chaining.
313    // Although this is unnecessary because of the way the module uses these plugins.
314    for (let s = 0; s < this.length; s++){
315      const menu = $(this).eq(s);
316      const mode = options.mode;
317      // The rest is crystal clear, isn't it? :)
318      if (menu.children('li').length === 0){
319        // Skip an empty menu which will not be visible and don't want to suddenly make it visible.
320      }
321      else if (mode === 'always_active'){
322        convert(menu);
323      }
324      else if (mode === 'window_width'){
325        const breakpoint = options.breakpointUnit === 'em' ? options.breakpoint * parseFloat($('body').css('font-size')) : options.breakpoint;
326        const windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
327        let timer;
328        if ((typeof Modernizr === 'undefined' || typeof Modernizr.mq !== 'function') && windowWidth < breakpoint){
329          convert(menu);
330        }
331        else if (typeof Modernizr !== 'undefined' && typeof Modernizr.mq === 'function' && Modernizr.mq('(max-width:' + (breakpoint - 1) + 'px)')) {
332          convert(menu);
333        }
334
335        $(window).resize(function(){
336          clearTimeout(timer);
337          timer = setTimeout(function(){
338            const windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
339            if ((typeof Modernizr === 'undefined' || typeof Modernizr.mq !== 'function') && windowWidth < breakpoint){
340              convert(menu);
341            }
342            else if (typeof Modernizr !== 'undefined' && typeof Modernizr.mq === 'function' && Modernizr.mq('(max-width:' + (breakpoint - 1) + 'px)')) {
343              convert(menu);
344            }
345            else {
346              turnBack(menu);
347            }
348          }, 50);
349        });
350      }
351      else if (mode === 'useragent_custom'){
352        if (options.useragent !== ''){
353          const ua = RegExp(options.useragent, 'i');
354          if (navigator.userAgent.match(ua)){
355            convert(menu);
356          }
357        }
358      }
359      else if (mode === 'useragent_predefined' && navigator.userAgent.match(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i)){
360        convert(menu);
361      }
362    }
363    return this;
364  }
365})(jQuery);

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.