1/* 2 * sf-Smallscreen v1.2b - 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 var 39 refined = menu.clone(), 40 // Things that should not be in the small-screen menus. 41 rm = refined.find('span.sf-sub-indicator, span.sf-description'), 42 // This is a helper class for those who need to add extra markup that shouldn't exist 43 // in the small-screen versions. 44 rh = refined.find('.sf-smallscreen-remove'), 45 // Mega-menus has to be removed too. 46 mm = refined.find('ul.sf-megamenu'); 47 for (var a = 0; a < rh.length; a++){ 48 rh.eq(a).replaceWith(rh.eq(a).html()); 49 } 50 for (var b = 0; b < rm.length; b++){ 51 rm.eq(b).remove(); 52 } 53 if (mm.length > 0){ 54 mm.removeClass('sf-megamenu'); 55 var ol = refined.find('div.sf-megamenu-column > ol'); 56 for (var o = 0; o < ol.length; o++){ 57 ol.eq(o).replaceWith('<ul>' + ol.eq(o).html() + '</ul>'); 58 } 59 var elements = ['div.sf-megamenu-column','.sf-megamenu-wrapper > ol','li.sf-megamenu-wrapper']; 60 for (var i = 0; i < elements.length; i++){ 61 obj = refined.find(elements[i]); 62 for (var t = 0; t < obj.length; t++){ 63 obj.eq(t).replaceWith(obj.eq(t).html()); 64 } 65 } 66 refined.find('.sf-megamenu-column').removeClass('sf-megamenu-column'); 67 } 68 refined.add(refined.find('*')).css({width:''}); 69 return refined; 70 } 71 72 // Creating <option> elements out of the menu. 73 function toSelect(menu, level){ 74 var 75 items = '', 76 childLI = $(menu).children('li'); 77 for (var a = 0; a < childLI.length; a++){ 78 var list = childLI.eq(a), parent = list.children('a, span'); 79 for (var b = 0; b < parent.length; b++){ 80 var 81 item = parent.eq(b), 82 path = item.is('a') ? item.attr('href') : '', 83 // Class names modification. 84 itemClone = item.clone(), 85 classes = (options.hyperlinkClasses) ? ((options.excludeClass_hyperlink && itemClone.hasClass(options.excludeClass_hyperlink)) ? itemClone.removeClass(options.excludeClass_hyperlink).attr('class') : itemClone.attr('class')) : '', 86 classes = (options.includeClass_hyperlink && !itemClone.hasClass(options.includeClass_hyperlink)) ? ((options.hyperlinkClasses) ? itemClone.addClass(options.includeClass_hyperlink).attr('class') : options.includeClass_hyperlink) : classes; 87 // Retaining the active class if requested. 88 if (options.addSelected && item.hasClass('active')){ 89 classes += ' active'; 90 } 91 // <option> has to be disabled if the item is not a link. 92 disable = item.is('span') || item.attr('href')=='#' ? ' disabled="disabled"' : '', 93 // Crystal clear. 94 subIndicator = 1 < level ? Array(level).join('-') + ' ' : ''; 95 // Preparing the <option> element. 96 items += '<option value="' + path + '" class="' + classes + '"' + disable + '>' + subIndicator + $.trim(item.text()) +'</option>', 97 childUL = list.find('> ul'); 98 // Using the function for the sub-menu of this item. 99 for (var u = 0; u < childUL.length; u++){
100 items += toSelect(childUL.eq(u), level + 1); 101 } 102 } 103 } 104 return items; 105 } 106 107 // Create the new version, hide the original. 108 function convert(menu){ 109 var menuID = menu.attr('id'), 110 // Creating a refined version of the menu. 111 refinedMenu = refine(menu); 112 // Currently the plugin provides two reactions to small screens. 113 // Converting the menu to a <select> element, and converting to an accordion version of the menu. 114 if (options.type == 'accordion'){ 115 var 116 toggleID = menuID + '-toggle', 117 accordionID = menuID + '-accordion'; 118 // Making sure the accordion does not exist. 119 if ($('#' + accordionID).length == 0){ 120 var 121 // Getting the style class. 122 styleClass = menu.attr('class').split(' ').filter(function(item){ 123 return item.indexOf('sf-style-') > -1 ? item : ''; 124 }), 125 // Creating the accordion. 126 accordion = $(refinedMenu).attr('id', accordionID); 127 // Removing unnecessary classes. 128 accordion.removeClass('sf-horizontal sf-vertical sf-navbar sf-shadow sf-js-enabled'); 129 // Adding necessary classes. 130 accordion.addClass('sf-accordion sf-hidden'); 131 // Removing style attributes and any unnecessary class. 132 accordion.children('li').removeAttr('style').removeClass('sfHover'); 133 // Doing the same and making sure all the sub-menus are off-screen (hidden). 134 accordion.find('ul').removeAttr('style').not('.sf-hidden').addClass('sf-hidden'); 135 // Creating the accordion toggle switch. 136 var toggle = '<div class="sf-accordion-toggle ' + styleClass + '"><a href="#" id="' + toggleID + '"><span>' + options.title + '</span></a></div>'; 137 138 // Adding Expand\Collapse buttons if requested. 139 if (options.accordionButton == 2){ 140 var parent = accordion.find('li.menuparent'); 141 for (var i = 0; i < parent.length; i++){ 142 parent.eq(i).prepend('<a href="#" class="sf-accordion-button">' + options.expandText + '</a>'); 143 } 144 } 145 // Inserting the according and hiding the original menu. 146 menu.before(toggle).before(accordion).hide(); 147 148 var 149 accordionElement = $('#' + accordionID), 150 // Deciding what should be used as accordion buttons. 151 buttonElement = (options.accordionButton < 2) ? 'a.menuparent,span.nolink.menuparent' : 'a.sf-accordion-button', 152 button = accordionElement.find(buttonElement); 153 154 // Attaching a click event to the toggle switch. 155 $('#' + toggleID).bind('click', function(e){ 156 // Preventing the click. 157 e.preventDefault(); 158 // Adding the sf-expanded class. 159 $(this).toggleClass('sf-expanded'); 160 161 if (accordionElement.hasClass('sf-expanded')){ 162 // If the accordion is already expanded: 163 // Hiding its expanded sub-menus and then the accordion itself as well. 164 accordionElement.add(accordionElement.find('li.sf-expanded')).removeClass('sf-expanded') 165 .end().find('ul').hide() 166 // This is a bit tricky, it's the same trick that has been in use in the main plugin for sometime. 167 // Basically we'll add a class that keeps the sub-menu off-screen and still visible, 168 // and make it invisible and removing the class one moment before showing or hiding it. 169 // This helps screen reader software access all the menu items. 170 .end().hide().addClass('sf-hidden').show(); 171 // Changing the caption of any existing accordion buttons to 'Expand'. 172 if (options.accordionButton == 2){ 173 accordionElement.find('a.sf-accordion-button').text(options.expandText); 174 } 175 } 176 else { 177 // But if it's collapsed, 178 accordionElement.addClass('sf-expanded').hide().removeClass('sf-hidden').show(); 179 } 180 }); 181 182 // Attaching a click event to the buttons. 183 button.bind('click', function(e){ 184 // Making sure the buttons does not exist already. 185 if ($(this).closest('li').children('ul').length > 0){ 186 e.preventDefault(); 187 // Selecting the parent menu items. 188 var parent = $(this).closest('li'); 189 // Creating and inserting Expand\Collapse buttons to the parent menu items, 190 // of course only if not already happened.
191 if (options.accordionButton == 1 && parent.children('a.menuparent,span.nolink.menuparent').length > 0 && parent.children('ul').children('li.sf-clone-parent').length == 0){ 192 var 193 // Cloning the hyperlink of the parent menu item. 194 cloneLink = parent.children('a.menuparent,span.nolink.menuparent').clone(), 195 // Wrapping the hyerplinks in <li>. 196 cloneLink = $('<li class="sf-clone-parent" />').html(cloneLink); 197 // Adding a helper class and attaching them to the sub-menus. 198 parent.children('ul').addClass('sf-has-clone-parent').prepend(cloneLink); 199 } 200 // Once the button is clicked, collapse the sub-menu if it's expanded. 201 if (parent.hasClass('sf-expanded')){ 202 parent.children('ul').slideUp('fast', function(){ 203 // Doing the accessibility trick after hiding the sub-menu. 204 $(this).closest('li').removeClass('sf-expanded').end().addClass('sf-hidden').show(); 205 }); 206 // Changing the caption of the inserted Collapse link to 'Expand', if any is inserted. 207 if (options.accordionButton == 2 && parent.children('.sf-accordion-button').length > 0){ 208 parent.children('.sf-accordion-button').text(options.expandText); 209 } 210 } 211 // Otherwise, expand the sub-menu. 212 else { 213 // Doing the accessibility trick and then showing the sub-menu. 214 parent.children('ul').hide().removeClass('sf-hidden').slideDown('fast') 215 // Changing the caption of the inserted Expand link to 'Collape', if any is inserted. 216 .end().addClass('sf-expanded').children('a.sf-accordion-button').text(options.collapseText) 217 // Hiding any expanded sub-menu of the same level. 218 .end().siblings('li.sf-expanded').children('ul') 219 .slideUp('fast', function(){ 220 // Doing the accessibility trick after hiding it. 221 $(this).closest('li').removeClass('sf-expanded').end().addClass('sf-hidden').show(); 222 }) 223 // Assuming Expand\Collapse buttons do exist, resetting captions, in those hidden sub-menus. 224 .parent().children('a.sf-accordion-button').text(options.expandText); 225 } 226 } 227 }); 228 } 229 } 230 else { 231 var 232 // Class names modification. 233 menuClone = menu.clone(), classes = (options.menuClasses) ? ((options.excludeClass_menu && menuClone.hasClass(options.excludeClass_menu)) ? menuClone.removeClass(options.excludeClass_menu).attr('class') : menuClone.attr('class')) : '', 234 classes = (options.includeClass_menu && !menuClone.hasClass(options.includeClass_menu)) ? ((options.menuClasses) ? menuClone.addClass(options.includeClass_menu).attr('class') : options.includeClass_menu) : classes, 235 classes = (classes) ? ' class="' + classes + '"' : ''; 236 237 // Making sure the <select> element does not exist already. 238 if ($('#' + menuID + '-select').length == 0){ 239 // Creating the <option> elements. 240 var newMenu = toSelect(refinedMenu, 1), 241 // Creating the <select> element and assigning an ID and class name. 242 selectList = $('<select' + classes + ' id="' + menuID + '-select"/>') 243 // Attaching the title and the items to the <select> element. 244 .html('<option>' + options.title + '</option>' + newMenu) 245 // Attaching an event then. 246 .change(function(){ 247 // Except for the first option that is the menu title and not a real menu item. 248 if ($('option:selected', this).index()){ 249 window.location = selectList.val(); 250 } 251 }); 252 // Applying the addSelected option to it. 253 if (options.addSelected){ 254 selectList.find('.active').attr('selected', !0); 255 } 256 // Finally inserting the <select> element into the document then hiding the original menu. 257 menu.before(selectList).hide(); 258 } 259 } 260 } 261 262 // Turn everything back to normal. 263 function turnBack(menu){ 264 var 265 id = '#' + menu.attr('id'); 266 // Removing the small screen version. 267 $(id + '-' + options.type).remove(); 268 // Removing the accordion toggle switch as well. 269 if (options.type == 'accordion'){ 270 $(id + '-toggle').parent('div').remove(); 271 } 272 // Crystal clear! 273 $(id).show(); 274 } 275
276 // Return original object to support chaining. 277 // Although this is unnecessary because of the way the module uses these plugins. 278 for (var s = 0; s < this.length; s++){ 279 var 280 menu = $(this).eq(s), 281 mode = options.mode; 282 // The rest is crystal clear, isn't it? :) 283 if (mode == 'always_active'){ 284 convert(menu); 285 } 286 else if (mode == 'window_width'){ 287 var breakpoint = (options.breakpointUnit == 'em') ? (options.breakpoint * parseFloat($('body').css('font-size'))) : options.breakpoint, 288 windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth, 289 timer; 290 if ((typeof Modernizr === 'undefined' || typeof Modernizr.mq !== 'function') && windowWidth < breakpoint){ 291 convert(menu); 292 } 293 else if (typeof Modernizr !== 'undefined' && typeof Modernizr.mq === 'function' && Modernizr.mq('(max-width:' + (breakpoint - 1) + 'px)')) { 294 convert(menu); 295 } 296 $(window).resize(function(){ 297 clearTimeout(timer); 298 timer = setTimeout(function(){ 299 var windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 300 if ((typeof Modernizr === 'undefined' || typeof Modernizr.mq !== 'function') && windowWidth < breakpoint){ 301 convert(menu); 302 } 303 else if (typeof Modernizr !== 'undefined' && typeof Modernizr.mq === 'function' && Modernizr.mq('(max-width:' + (breakpoint - 1) + 'px)')) { 304 convert(menu); 305 } 306 else { 307 turnBack(menu); 308 } 309 }, 50); 310 }); 311 } 312 else if (mode == 'useragent_custom'){ 313 if (options.useragent != ''){ 314 var ua = RegExp(options.useragent, 'i'); 315 if (navigator.userAgent.match(ua)){ 316 convert(menu); 317 } 318 } 319 } 320 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)){ 321 convert(menu); 322 } 323 } 324 return this; 325 } 326})(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.