[14926] | 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 | '<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>' |
---|
| 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 | }, userConfig); |
---|
| 96 | |
---|
| 97 | // Expand "target" if it's not a jQuery object already. |
---|
| 98 | if (typeof config.target != 'jQuery') |
---|
| 99 | config.target = $(config.target); |
---|
| 100 | |
---|
| 101 | // Panel. |
---|
| 102 | |
---|
| 103 | // Methods. |
---|
| 104 | $this._hide = function(event) { |
---|
| 105 | |
---|
| 106 | // Already hidden? Bail. |
---|
| 107 | if (!config.target.hasClass(config.visibleClass)) |
---|
| 108 | return; |
---|
| 109 | |
---|
| 110 | // If an event was provided, cancel it. |
---|
| 111 | if (event) { |
---|
| 112 | |
---|
| 113 | event.preventDefault(); |
---|
| 114 | event.stopPropagation(); |
---|
| 115 | |
---|
| 116 | } |
---|
| 117 | |
---|
| 118 | // Hide. |
---|
| 119 | config.target.removeClass(config.visibleClass); |
---|
| 120 | |
---|
| 121 | // Post-hide stuff. |
---|
| 122 | window.setTimeout(function() { |
---|
| 123 | |
---|
| 124 | // Reset scroll position. |
---|
| 125 | if (config.resetScroll) |
---|
| 126 | $this.scrollTop(0); |
---|
| 127 | |
---|
| 128 | // Reset forms. |
---|
| 129 | if (config.resetForms) |
---|
| 130 | $this.find('form').each(function() { |
---|
| 131 | this.reset(); |
---|
| 132 | }); |
---|
| 133 | |
---|
| 134 | }, config.delay); |
---|
| 135 | |
---|
| 136 | }; |
---|
| 137 | |
---|
| 138 | // Vendor fixes. |
---|
| 139 | $this |
---|
| 140 | .css('-ms-overflow-style', '-ms-autohiding-scrollbar') |
---|
| 141 | .css('-webkit-overflow-scrolling', 'touch'); |
---|
| 142 | |
---|
| 143 | // Hide on click. |
---|
| 144 | if (config.hideOnClick) { |
---|
| 145 | |
---|
| 146 | $this.find('a') |
---|
| 147 | .css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)'); |
---|
| 148 | |
---|
| 149 | $this |
---|
| 150 | .on('click', 'a', function(event) { |
---|
| 151 | |
---|
| 152 | var $a = $(this), |
---|
| 153 | href = $a.attr('href'), |
---|
| 154 | target = $a.attr('target'); |
---|
| 155 | |
---|
| 156 | if (!href || href == '#' || href == '' || href == '#' + id) |
---|
| 157 | return; |
---|
| 158 | |
---|
| 159 | // Cancel original event. |
---|
| 160 | event.preventDefault(); |
---|
| 161 | event.stopPropagation(); |
---|
| 162 | |
---|
| 163 | // Hide panel. |
---|
| 164 | $this._hide(); |
---|
| 165 | |
---|
| 166 | // Redirect to href. |
---|
| 167 | window.setTimeout(function() { |
---|
| 168 | |
---|
| 169 | if (target == '_blank') |
---|
| 170 | window.open(href); |
---|
| 171 | else |
---|
| 172 | window.location.href = href; |
---|
| 173 | |
---|
| 174 | }, config.delay + 10); |
---|
| 175 | |
---|
| 176 | }); |
---|
| 177 | |
---|
| 178 | } |
---|
| 179 | |
---|
| 180 | // Event: Touch stuff. |
---|
| 181 | $this.on('touchstart', function(event) { |
---|
| 182 | |
---|
| 183 | $this.touchPosX = event.originalEvent.touches[0].pageX; |
---|
| 184 | $this.touchPosY = event.originalEvent.touches[0].pageY; |
---|
| 185 | |
---|
| 186 | }) |
---|
| 187 | |
---|
| 188 | $this.on('touchmove', function(event) { |
---|
| 189 | |
---|
| 190 | if ($this.touchPosX === null |
---|
| 191 | || $this.touchPosY === null) |
---|
| 192 | return; |
---|
| 193 | |
---|
| 194 | var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX, |
---|
| 195 | diffY = $this.touchPosY - event.originalEvent.touches[0].pageY, |
---|
| 196 | th = $this.outerHeight(), |
---|
| 197 | ts = ($this.get(0).scrollHeight - $this.scrollTop()); |
---|
| 198 | |
---|
| 199 | // Hide on swipe? |
---|
| 200 | if (config.hideOnSwipe) { |
---|
| 201 | |
---|
| 202 | var result = false, |
---|
| 203 | boundary = 20, |
---|
| 204 | delta = 50; |
---|
| 205 | |
---|
| 206 | switch (config.side) { |
---|
| 207 | |
---|
| 208 | case 'left': |
---|
| 209 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta); |
---|
| 210 | break; |
---|
| 211 | |
---|
| 212 | case 'right': |
---|
| 213 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta)); |
---|
| 214 | break; |
---|
| 215 | |
---|
| 216 | case 'top': |
---|
| 217 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta); |
---|
| 218 | break; |
---|
| 219 | |
---|
| 220 | case 'bottom': |
---|
| 221 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta)); |
---|
| 222 | break; |
---|
| 223 | |
---|
| 224 | default: |
---|
| 225 | break; |
---|
| 226 | |
---|
| 227 | } |
---|
| 228 | |
---|
| 229 | if (result) { |
---|
| 230 | |
---|
| 231 | $this.touchPosX = null; |
---|
| 232 | $this.touchPosY = null; |
---|
| 233 | $this._hide(); |
---|
| 234 | |
---|
| 235 | return false; |
---|
| 236 | |
---|
| 237 | } |
---|
| 238 | |
---|
| 239 | } |
---|
| 240 | |
---|
| 241 | // Prevent vertical scrolling past the top or bottom. |
---|
| 242 | if (($this.scrollTop() < 0 && diffY < 0) |
---|
| 243 | || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) { |
---|
| 244 | |
---|
| 245 | event.preventDefault(); |
---|
| 246 | event.stopPropagation(); |
---|
| 247 | |
---|
| 248 | } |
---|
| 249 | |
---|
| 250 | }); |
---|
| 251 | |
---|
| 252 | // Event: Prevent certain events inside the panel from bubbling. |
---|
| 253 | $this.on('click touchend touchstart touchmove', function(event) { |
---|
| 254 | event.stopPropagation(); |
---|
| 255 | }); |
---|
| 256 | |
---|
| 257 | // Event: Hide panel if a child anchor tag pointing to its ID is clicked. |
---|
| 258 | $this.on('click', 'a[href="#' + id + '"]', function(event) { |
---|
| 259 | |
---|
| 260 | event.preventDefault(); |
---|
| 261 | event.stopPropagation(); |
---|
| 262 | |
---|
| 263 | config.target.removeClass(config.visibleClass); |
---|
| 264 | |
---|
| 265 | }); |
---|
| 266 | |
---|
| 267 | // Body. |
---|
| 268 | |
---|
| 269 | // Event: Hide panel on body click/tap. |
---|
| 270 | $body.on('click touchend', function(event) { |
---|
| 271 | $this._hide(event); |
---|
| 272 | }); |
---|
| 273 | |
---|
| 274 | // Event: Toggle. |
---|
| 275 | $body.on('click', 'a[href="#' + id + '"]', function(event) { |
---|
| 276 | |
---|
| 277 | event.preventDefault(); |
---|
| 278 | event.stopPropagation(); |
---|
| 279 | |
---|
| 280 | config.target.toggleClass(config.visibleClass); |
---|
| 281 | |
---|
| 282 | }); |
---|
| 283 | |
---|
| 284 | // Window. |
---|
| 285 | |
---|
| 286 | // Event: Hide on ESC. |
---|
| 287 | if (config.hideOnEscape) |
---|
| 288 | $window.on('keydown', function(event) { |
---|
| 289 | |
---|
| 290 | if (event.keyCode == 27) |
---|
| 291 | $this._hide(event); |
---|
| 292 | |
---|
| 293 | }); |
---|
| 294 | |
---|
| 295 | return $this; |
---|
| 296 | |
---|
| 297 | }; |
---|
| 298 | |
---|
| 299 | /** |
---|
| 300 | * Apply "placeholder" attribute polyfill to one or more forms. |
---|
| 301 | * @return {jQuery} jQuery object. |
---|
| 302 | */ |
---|
| 303 | $.fn.placeholder = function() { |
---|
| 304 | |
---|
| 305 | // Browser natively supports placeholders? Bail. |
---|
| 306 | if (typeof (document.createElement('input')).placeholder != 'undefined') |
---|
| 307 | return $(this); |
---|
| 308 | |
---|
| 309 | // No elements? |
---|
| 310 | if (this.length == 0) |
---|
| 311 | return $this; |
---|
| 312 | |
---|
| 313 | // Multiple elements? |
---|
| 314 | if (this.length > 1) { |
---|
| 315 | |
---|
| 316 | for (var i=0; i < this.length; i++) |
---|
| 317 | $(this[i]).placeholder(); |
---|
| 318 | |
---|
| 319 | return $this; |
---|
| 320 | |
---|
| 321 | } |
---|
| 322 | |
---|
| 323 | // Vars. |
---|
| 324 | var $this = $(this); |
---|
| 325 | |
---|
| 326 | // Text, TextArea. |
---|
| 327 | $this.find('input[type=text],textarea') |
---|
| 328 | .each(function() { |
---|
| 329 | |
---|
| 330 | var i = $(this); |
---|
| 331 | |
---|
| 332 | if (i.val() == '' |
---|
| 333 | || i.val() == i.attr('placeholder')) |
---|
| 334 | i |
---|
| 335 | .addClass('polyfill-placeholder') |
---|
| 336 | .val(i.attr('placeholder')); |
---|
| 337 | |
---|
| 338 | }) |
---|
| 339 | .on('blur', function() { |
---|
| 340 | |
---|
| 341 | var i = $(this); |
---|
| 342 | |
---|
| 343 | if (i.attr('name').match(/-polyfill-field$/)) |
---|
| 344 | return; |
---|
| 345 | |
---|
| 346 | if (i.val() == '') |
---|
| 347 | i |
---|
| 348 | .addClass('polyfill-placeholder') |
---|
| 349 | .val(i.attr('placeholder')); |
---|
| 350 | |
---|
| 351 | }) |
---|
| 352 | .on('focus', function() { |
---|
| 353 | |
---|
| 354 | var i = $(this); |
---|
| 355 | |
---|
| 356 | if (i.attr('name').match(/-polyfill-field$/)) |
---|
| 357 | return; |
---|
| 358 | |
---|
| 359 | if (i.val() == i.attr('placeholder')) |
---|
| 360 | i |
---|
| 361 | .removeClass('polyfill-placeholder') |
---|
| 362 | .val(''); |
---|
| 363 | |
---|
| 364 | }); |
---|
| 365 | |
---|
| 366 | // Password. |
---|
| 367 | $this.find('input[type=password]') |
---|
| 368 | .each(function() { |
---|
| 369 | |
---|
| 370 | var i = $(this); |
---|
| 371 | var x = $( |
---|
| 372 | $('<div>') |
---|
| 373 | .append(i.clone()) |
---|
| 374 | .remove() |
---|
| 375 | .html() |
---|
| 376 | .replace(/type="password"/i, 'type="text"') |
---|
| 377 | .replace(/type=password/i, 'type=text') |
---|
| 378 | ); |
---|
| 379 | |
---|
| 380 | if (i.attr('id') != '') |
---|
| 381 | x.attr('id', i.attr('id') + '-polyfill-field'); |
---|
| 382 | |
---|
| 383 | if (i.attr('name') != '') |
---|
| 384 | x.attr('name', i.attr('name') + '-polyfill-field'); |
---|
| 385 | |
---|
| 386 | x.addClass('polyfill-placeholder') |
---|
| 387 | .val(x.attr('placeholder')).insertAfter(i); |
---|
| 388 | |
---|
| 389 | if (i.val() == '') |
---|
| 390 | i.hide(); |
---|
| 391 | else |
---|
| 392 | x.hide(); |
---|
| 393 | |
---|
| 394 | i |
---|
| 395 | .on('blur', function(event) { |
---|
| 396 | |
---|
| 397 | event.preventDefault(); |
---|
| 398 | |
---|
| 399 | var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); |
---|
| 400 | |
---|
| 401 | if (i.val() == '') { |
---|
| 402 | |
---|
| 403 | i.hide(); |
---|
| 404 | x.show(); |
---|
| 405 | |
---|
| 406 | } |
---|
| 407 | |
---|
| 408 | }); |
---|
| 409 | |
---|
| 410 | x |
---|
| 411 | .on('focus', function(event) { |
---|
| 412 | |
---|
| 413 | event.preventDefault(); |
---|
| 414 | |
---|
| 415 | var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']'); |
---|
| 416 | |
---|
| 417 | x.hide(); |
---|
| 418 | |
---|
| 419 | i |
---|
| 420 | .show() |
---|
| 421 | .focus(); |
---|
| 422 | |
---|
| 423 | }) |
---|
| 424 | .on('keypress', function(event) { |
---|
| 425 | |
---|
| 426 | event.preventDefault(); |
---|
| 427 | x.val(''); |
---|
| 428 | |
---|
| 429 | }); |
---|
| 430 | |
---|
| 431 | }); |
---|
| 432 | |
---|
| 433 | // Events. |
---|
| 434 | $this |
---|
| 435 | .on('submit', function() { |
---|
| 436 | |
---|
| 437 | $this.find('input[type=text],input[type=password],textarea') |
---|
| 438 | .each(function(event) { |
---|
| 439 | |
---|
| 440 | var i = $(this); |
---|
| 441 | |
---|
| 442 | if (i.attr('name').match(/-polyfill-field$/)) |
---|
| 443 | i.attr('name', ''); |
---|
| 444 | |
---|
| 445 | if (i.val() == i.attr('placeholder')) { |
---|
| 446 | |
---|
| 447 | i.removeClass('polyfill-placeholder'); |
---|
| 448 | i.val(''); |
---|
| 449 | |
---|
| 450 | } |
---|
| 451 | |
---|
| 452 | }); |
---|
| 453 | |
---|
| 454 | }) |
---|
| 455 | .on('reset', function(event) { |
---|
| 456 | |
---|
| 457 | event.preventDefault(); |
---|
| 458 | |
---|
| 459 | $this.find('select') |
---|
| 460 | .val($('option:first').val()); |
---|
| 461 | |
---|
| 462 | $this.find('input,textarea') |
---|
| 463 | .each(function() { |
---|
| 464 | |
---|
| 465 | var i = $(this), |
---|
| 466 | x; |
---|
| 467 | |
---|
| 468 | i.removeClass('polyfill-placeholder'); |
---|
| 469 | |
---|
| 470 | switch (this.type) { |
---|
| 471 | |
---|
| 472 | case 'submit': |
---|
| 473 | case 'reset': |
---|
| 474 | break; |
---|
| 475 | |
---|
| 476 | case 'password': |
---|
| 477 | i.val(i.attr('defaultValue')); |
---|
| 478 | |
---|
| 479 | x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); |
---|
| 480 | |
---|
| 481 | if (i.val() == '') { |
---|
| 482 | i.hide(); |
---|
| 483 | x.show(); |
---|
| 484 | } |
---|
| 485 | else { |
---|
| 486 | i.show(); |
---|
| 487 | x.hide(); |
---|
| 488 | } |
---|
| 489 | |
---|
| 490 | break; |
---|
| 491 | |
---|
| 492 | case 'checkbox': |
---|
| 493 | case 'radio': |
---|
| 494 | i.attr('checked', i.attr('defaultValue')); |
---|
| 495 | break; |
---|
| 496 | |
---|
| 497 | case 'text': |
---|
| 498 | case 'textarea': |
---|
| 499 | i.val(i.attr('defaultValue')); |
---|
| 500 | |
---|
| 501 | if (i.val() == '') { |
---|
| 502 | i.addClass('polyfill-placeholder'); |
---|
| 503 | i.val(i.attr('placeholder')); |
---|
| 504 | } |
---|
| 505 | |
---|
| 506 | break; |
---|
| 507 | |
---|
| 508 | default: |
---|
| 509 | i.val(i.attr('defaultValue')); |
---|
| 510 | break; |
---|
| 511 | |
---|
| 512 | } |
---|
| 513 | }); |
---|
| 514 | |
---|
| 515 | }); |
---|
| 516 | |
---|
| 517 | return $this; |
---|
| 518 | |
---|
| 519 | }; |
---|
| 520 | |
---|
| 521 | /** |
---|
| 522 | * Moves elements to/from the first positions of their respective parents. |
---|
| 523 | * @param {jQuery} $elements Elements (or selector) to move. |
---|
| 524 | * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations. |
---|
| 525 | */ |
---|
| 526 | $.prioritize = function($elements, condition) { |
---|
| 527 | |
---|
| 528 | var key = '__prioritize'; |
---|
| 529 | |
---|
| 530 | // Expand $elements if it's not already a jQuery object. |
---|
| 531 | if (typeof $elements != 'jQuery') |
---|
| 532 | $elements = $($elements); |
---|
| 533 | |
---|
| 534 | // Step through elements. |
---|
| 535 | $elements.each(function() { |
---|
| 536 | |
---|
| 537 | var $e = $(this), $p, |
---|
| 538 | $parent = $e.parent(); |
---|
| 539 | |
---|
| 540 | // No parent? Bail. |
---|
| 541 | if ($parent.length == 0) |
---|
| 542 | return; |
---|
| 543 | |
---|
| 544 | // Not moved? Move it. |
---|
| 545 | if (!$e.data(key)) { |
---|
| 546 | |
---|
| 547 | // Condition is false? Bail. |
---|
| 548 | if (!condition) |
---|
| 549 | return; |
---|
| 550 | |
---|
| 551 | // Get placeholder (which will serve as our point of reference for when this element needs to move back). |
---|
| 552 | $p = $e.prev(); |
---|
| 553 | |
---|
| 554 | // Couldn't find anything? Means this element's already at the top, so bail. |
---|
| 555 | if ($p.length == 0) |
---|
| 556 | return; |
---|
| 557 | |
---|
| 558 | // Move element to top of parent. |
---|
| 559 | $e.prependTo($parent); |
---|
| 560 | |
---|
| 561 | // Mark element as moved. |
---|
| 562 | $e.data(key, $p); |
---|
| 563 | |
---|
| 564 | } |
---|
| 565 | |
---|
| 566 | // Moved already? |
---|
| 567 | else { |
---|
| 568 | |
---|
| 569 | // Condition is true? Bail. |
---|
| 570 | if (condition) |
---|
| 571 | return; |
---|
| 572 | |
---|
| 573 | $p = $e.data(key); |
---|
| 574 | |
---|
| 575 | // Move element back to its original location (using our placeholder). |
---|
| 576 | $e.insertAfter($p); |
---|
| 577 | |
---|
| 578 | // Unmark element as moved. |
---|
| 579 | $e.removeData(key); |
---|
| 580 | |
---|
| 581 | } |
---|
| 582 | |
---|
| 583 | }); |
---|
| 584 | |
---|
| 585 | }; |
---|
| 586 | |
---|
| 587 | })(jQuery); |
---|