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