1(function($) { 2 3 /** 4 * Generate an indented list of links from a nav. Meant for use with panel(). 5 * @return {jQuery} jQuery object. 6 */ 7 $.fn.navList = function() { 8 9 var $this = $(this); 10 $a = $this.find('a'), 11 b = []; 12 13 $a.each(function() { 14 15 var $this = $(this), 16 indent = Math.max(0, $this.parents('li').length - 1), 17 href = $this.attr('href'), 18 target = $this.attr('target'); 19 20 b.push( 21 '<li><a ' + 22 'class="link depth-' + indent + '"' + 23 ( (typeof target !== 'undefined' && target != '') ? ' target="' + target + '"' : '') + 24 ( (typeof href !== 'undefined' && href != '') ? ' href="' + href + '"' : '') + 25 '>' + 26 '<span class="indent-' + indent + '"></span>' + 27 $this.text() + 28 '</a></li>' 29 ); 30 31 }); 32 33 return b.join(''); 34 35 }; 36 37 /** 38 * Panel-ify an element. 39 * @param {object} userConfig User config. 40 * @return {jQuery} jQuery object. 41 */ 42 $.fn.panel = function(userConfig) { 43 44 // No elements? 45 if (this.length == 0) 46 return $this; 47 48 // Multiple elements? 49 if (this.length > 1) { 50 51 for (var i=0; i < this.length; i++) 52 $(this[i]).panel(userConfig); 53 54 return $this; 55 56 } 57 58 // Vars. 59 var $this = $(this), 60 $body = $('body'), 61 $window = $(window), 62 id = $this.attr('id'), 63 config; 64 65 // Config. 66 config = $.extend({ 67 68 // Delay. 69 delay: 0, 70 71 // Hide panel on link click. 72 hideOnClick: false, 73 74 // Hide panel on escape keypress. 75 hideOnEscape: false, 76 77 // Hide panel on swipe. 78 hideOnSwipe: false, 79 80 // Reset scroll position on hide. 81 resetScroll: false, 82 83 // Reset forms on hide. 84 resetForms: false, 85 86 // Side of viewport the panel will appear. 87 side: null, 88 89 // Target element for "class". 90 target: $this, 91 92 // Class to toggle. 93 visibleClass: 'visible', 94 95 // Focus lock the navigation menu. 96 focusLock: false, 97 98 // First tabbable item (force to lastTabElement on reverse-tab) 99 firstTabElement: null, 100 101 // Last tabbable item (force to firstTabElement on tab) 102 lastTabElement: null, 103 }, userConfig); 104 105 // Expand "target" if it's not a jQuery object already. 106 if (typeof config.target != 'jQuery') 107 config.target = $(config.target); 108 109 // Panel. 110 111 // Methods. 112 $this._hide = function(event) { 113 114 // Already hidden? Bail. 115 if (!config.target.hasClass(config.visibleClass)) 116 return; 117 118 // If an event was provided, cancel it. 119 if (event) { 120 121 event.preventDefault(); 122 event.stopPropagation(); 123 124 } 125 126 // Hide. 127 config.target.removeClass(config.visibleClass); 128 129 // Post-hide stuff. 130 window.setTimeout(function() { 131 132 // Reset scroll position. 133 if (config.resetScroll) 134 $this.scrollTop(0); 135 136 // Reset forms. 137 if (config.resetForms) 138 $this.find('form').each(function() { 139 this.reset(); 140 }); 141 142 }, config.delay); 143 144 }; 145 146 // Vendor fixes. 147 $this 148 .css('-ms-overflow-style', '-ms-autohiding-scrollbar') 149 .css('-webkit-overflow-scrolling', 'touch'); 150 151 // Hide on click. 152 if (config.hideOnClick) { 153 154 $this.find('a') 155 .css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)'); 156 157 $this 158 .on('click', 'a', function(event) { 159 160 var $a = $(this), 161 href = $a.attr('href'), 162 target = $a.attr('target'); 163 164 if (!href || href == '#' || href == '' || href == '#' + id) 165 return; 166 167 // Cancel original event. 168 event.preventDefault(); 169 event.stopPropagation(); 170 171 // Hide panel. 172 $this._hide(); 173 174 // Redirect to href. 175 window.setTimeout(function() { 176 177 if (target == '_blank') 178 window.open(href); 179 else 180 window.location.href = href; 181 182 }, config.delay + 10); 183 184 }); 185 186 } 187 188 // Event: Touch stuff. 189 $this.on('touchstart', function(event) { 190 191 $this.touchPosX = event.originalEvent.touches[0].pageX; 192 $this.touchPosY = event.originalEvent.touches[0].pageY; 193 194 }) 195 196 $this.on('touchmove', function(event) { 197 198 if ($this.touchPosX === null 199 || $this.touchPosY === null) 200 return; 201 202 var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX, 203 diffY = $this.touchPosY - event.originalEvent.touches[0].pageY, 204 th = $this.outerHeight(), 205 ts = ($this.get(0).scrollHeight - $this.scrollTop()); 206 207 // Hide on swipe? 208 if (config.hideOnSwipe) { 209 210 var result = false, 211 boundary = 20, 212 delta = 50; 213 214 switch (config.side) { 215 216 case 'left': 217 result = (diffY < boundary && diffY >
217 (-1 * boundary)) && (diffX > delta); 218 break; 219 220 case 'right': 221 result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta)); 222 break; 223 224 case 'top': 225 result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta); 226 break; 227 228 case 'bottom': 229 result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta)); 230 break; 231 232 default: 233 break; 234 235 } 236 237 if (result) { 238 239 $this.touchPosX = null; 240 $this.touchPosY = null; 241 $this._hide(); 242 243 return false; 244 245 } 246 247 } 248 249 // Prevent vertical scrolling past the top or bottom. 250 if (($this.scrollTop() < 0 && diffY < 0) 251 || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) { 252 253 event.preventDefault(); 254 event.stopPropagation(); 255 256 } 257 258 }); 259 260 // Event: Prevent certain events inside the panel from bubbling. 261 $this.on('click touchend touchstart touchmove', function(event) { 262 event.stopPropagation(); 263 }); 264 265 // Event: Hide panel if a child anchor tag pointing to its ID is clicked. 266 $this.on('click', 'a[href="#' + id + '"]', function(event) { 267 268 event.preventDefault(); 269 event.stopPropagation(); 270 271 config.target.removeClass(config.visibleClass); 272 setTimeout(function () { 273 $('#' + id).attr('aria-hidden','true'); 274 }, 500); 275 }); 276 277 // Body. 278 279 // Event: Hide panel on body click/tap. 280 $body.on('click touchend', function(event) { 281 $this._hide(event); 282 }); 283 284 // Event: Toggle. 285 var ariaToggleTimeout = null; 286 var classToggleTimeout = null; 287 $body.on('click', 'a[href="#' + id + '"]', function(event) { 288 // clear old timeouts to prevent erroneous behaviour with multiple timeouts 289 clearTimeout(ariaToggleTimeout); 290 clearTimeout(classToggleTimeout); 291 292 event.preventDefault(); 293 event.stopPropagation(); 294 295 if (!config.target.hasClass(config.visibleClass)) { 296 $('#' + id).attr('aria-hidden', 'false'); 297 } else { 298 ariaToggleTimeout = setTimeout(function () { 299 $('#' + id).attr('aria-hidden', 'true'); 300 }, 500); 301 } 302 303 classToggleTimeout = setTimeout(function () { 304 config.target.toggleClass(config.visibleClass); 305 config.firstTabElement.focus(); 306 }, 0); 307 }); 308 309 // Window. 310 311 // Event: Hide on ESC. 312 if (config.hideOnEscape) { 313 $body.on('keyup', function(event){ 314 if (event.keyCode === 27 ) { 315 $this._hide(event); 316 } 317 }); 318 } 319 320 // focus lock within panel 321 if ( 322 config.focusLock 323 && config.firstTabElement !== null 324 && config.firstTabElement.length 325 && config.lastTabElement !== null 326 && config.lastTabElement.length 327 ) { 328 // ensure required first and last tab elements are defined 329 config.firstTabElement.on('keydown', function (event) { 330 if (event.which === 9 && event.shiftKey) { 331 event.preventDefault(); 332 config.lastTabElement.focus(); 333 } 334 }); 335 336 config.lastTabElement.on('keydown', function (event) { 337 if (event.which === 9 && !event.shiftKey) { 338 event.preventDefault(); 339 config.firstTabElement.focus(); 340 } 341 }); 342 } 343 344 return $this; 345 346 }; 347 348 /** 349 * Apply "placeholder" attribute polyfill to one or more forms. 350 * @return {jQuery} jQuery object. 351 */ 352 $.fn.placeholder = function() { 353 354 // Browser natively supports placeholders? Bail. 355 if (typeof (document.createElement('input')).placeholder != 'undefined') 356 return $(this); 357 358 // No elements? 359 if (this.length == 0) 360 return $this; 361 362 // Multiple elements? 363 if (this.length > 1) { 364 365 for (var i=0; i < this.length; i++) 366 $(this[i]).placeholder(); 367 368 return $this; 369 370 } 371 372 // Vars. 373 var $this = $(this); 374 375 // Text, TextArea. 376 $this.find('input[type=text],textarea') 377 .each(function() { 378 379 var i = $(this); 380 381 if (i.val() == '' 382 || i.val() == i.attr('placeholder')) 383 i 384 .addClass('polyfill-placeholder') 385 .val(i.attr('placeholder')); 386 387 }) 388 .on('blur', function() { 389 390 var i = $(this); 391 392 if (i.attr('name').match(/-polyfill-field$/)) 393 return; 394 395 if (i.val() == '') 396 i 397 .addClass('polyfill-placeholder') 398 .val(i.attr('placeholder')); 399 400 }) 401 .on('focus', function() { 402 403 var i = $(this); 404 405 if (i.attr('name').match(/-polyfill-field$/)) 406 return; 407 408 if (i.val() == i.attr('placeholder')) 409 i 410 .removeClass('polyfill-placeholder') 411 .val(''); 412 413 }); 414 415 // Password. 416 $this.find('input[type=password]') 417 .each(function() { 418 419 var i = $(this); 420 var x = $( 421 $('<div>') 422 .append(i.clone()) 423 .remove() 424 .html() 425 .replace(/type="password"/i, 'type="text"') 426 .replace(/type=password/i, 'type=text') 427 ); 428 429 if (i.attr('id') != '') 430 x.attr('id', i.attr('id') + '-polyfill-field'); 431 432 if (i.attr('name') != '') 433 x.attr('name', i.attr('name') + '-polyfill-field'); 434 435 x.addClass('polyfill-placeholder') 436 .val(x.attr('placeholder')).insertAfter(i); 437 438 if (i.val() == '') 439 i.hide(); 440 else 441 x.hide(); 442 443 i 444 .on('blur', function(event) { 445 446 event.preventDefault(); 447 448 var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 449 450 if (i.val() == '') { 451 452 i.hide(); 453 x.show(); 454 455 } 456 457 }); 458 459 x 460 .on('focus', function(event) { 461 462 event.preventDefault(); 463 464 var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']'); 465 466 x.hide(); 467 468 i 469 .show() 470 .focus(); 471 472 }) 473 .on('keypress', function(event) { 474 475 event.preventDefault(); 476 x.val(''); 477 478 }); 479 480 }); 481 482 // Events. 483 $this 484 .on('submit', function() { 485 486 $this.find('input[type=text],input[type=password],textarea') 487 .each(function(event) { 488 489 var i = $(this); 490 491 if (i.attr('name').match(/-polyfill-field$/)) 492 i.attr('name', ''); 493 494 if (i.val() == i.attr('placeholder')) { 495 496 i.removeClass('polyfill-placeholder'); 497 i.val(''); 498 499 } 500 501 }); 502 503 }) 504 .on('reset', function(event) { 505 506 event.preventDefault(); 507 508 $this.find('select') 509 .val($('option:first').val()); 510 511 $this.find('input,textarea') 512 .each(function() { 513 514 var i = $(this), 515 x; 516 517 i.removeClass('polyfill-placeholder'); 518 519 switch (this.type) { 520 521 case 'submit': 522 case 'reset': 523 break; 524 525 case 'password': 526 i.val(i.attr('defaultValue')); 527 528 x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 529 530 if (i.val() == '') { 531 i.hide(); 532 x.show(); 533 } 534 else { 535 i.show(); 536 x.hide(); 537 } 538 539 break; 540 541 case 'checkbox': 542 case 'radio': 543 i.attr('checked', i.attr('defaultValue')); 544 break; 545 546 case 'text': 547 case 'textarea': 548 i.val(i.attr('defaultValue')); 549 550 if (i.val() == '') { 551 i.addClass('polyfill-placeholder'); 552 i.val(i.attr('placeholder')); 553 } 554 555 break; 556 557 default: 558 i.val(i.attr('defaultValue')); 559 break; 560 561 } 562 }); 563 564 }); 565 566 return $this; 567 568 }; 569 570 /** 571 * Moves elements to/from the first positions of their respective parents. 572 * @param {jQuery} $elements Elements (or selector) to move. 573 * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations. 574 */ 575 $.prioritize = function($elements, condition) { 576 577 var key = '__prioritize';
578 579 // Expand $elements if it's not already a jQuery object. 580 if (typeof $elements != 'jQuery') 581 $elements = $($elements); 582 583 // Step through elements. 584 $elements.each(function() { 585 586 var $e = $(this), $p, 587 $parent = $e.parent(); 588 589 // No parent? Bail. 590 if ($parent.length == 0) 591 return; 592 593 // Not moved? Move it. 594 if (!$e.data(key)) { 595 596 // Condition is false? Bail. 597 if (!condition) 598 return; 599 600 // Get placeholder (which will serve as our point of reference for when this element needs to move back). 601 $p = $e.prev(); 602 603 // Couldn't find anything? Means this element's already at the top, so bail. 604 if ($p.length == 0) 605 return; 606 607 // Move element to top of parent. 608 $e.prependTo($parent); 609 610 // Mark element as moved. 611 $e.data(key, $p); 612 613 } 614 615 // Moved already? 616 else { 617 618 // Condition is true? Bail. 619 if (condition) 620 return; 621 622 $p = $e.data(key); 623 624 // Move element back to its original location (using our placeholder). 625 $e.insertAfter($p); 626 627 // Unmark element as moved. 628 $e.removeData(key); 629 630 } 631 632 }); 633 634 }; 635 636})(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.