https://motorbikesimulator.com/wp-content/plugins/superstorefinder-wp/js/plugins/homebrew.js?ver=1.0
1/*! 2 * 3 * Homebrewed plugin functions! 4 * 5 * Basically it's a bunch of common functions used in the websites we 6 * build. They're built in the format of jQuery plugins for reusability. 7 * 8 * You may remove those that you don't need. 9 * 10 * - HC 11 * 12 * @TODO: Make these more extensible for easier future usage. 13 */ 14var homebrew = {}; 15(function($) { 16 /* Avoid `console` errors in browsers that lack a console. */ 17 var method, 18 noop = function () {}, 19 methods = [ 20 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 21 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 22 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 23 'timeStamp', 'trace', 'warn' 24 ], 25 length = methods.length, 26 console = (window.console = window.console || {}); 27 28 while (length--) { 29 method = methods[length]; 30 31 // Only stub undefined methods. 32 if (!console[method]) { 33 console[method] = noop; 34 } 35 } 36 37 /* Setup homebrew object */ 38 var root = $('html'); 39 $.extend(homebrew, { 40 browser : { 41 ie : root.hasClass('ie'), 42 ie9 : root.hasClass('ie9'), 43 lt9 : root.hasClass('lt9'), 44 ie8 : root.hasClass('ie8'), 45 lt8 : root.hasClass('lt8'), 46 ie7 : root.hasClass('ie7'), 47 firefox : (window.mozIndexedDB !== undefined) 48 }, 49 50 events : { 51 transitionEnd : 'oTransitionEnd otransitionend webkitTransitionEnd transitionend' 52 }, 53 54 classes : { 55 transitionable: 'is-transitionable' 56 }, 57 58 screenSize : { 59 small : false, 60 medium : false, 61 large : true 62 }, 63 64 mediaQueries : { 65 small : 'only screen and (max-width: 40em)', 66 medium : 'only screen and (min-width: 40.063em)', 67 large : 'only screen and (min-width: 64.063em)' 68 }, 69 70 mediaQueriesIE9 : { 71 small : { size : 640, method : 'max-width' }, 72 medium : { size : 641, method : 'min-width' }, 73 large : { size : 1025, method : 'min-width' } 74 } 75 }); 76 77 78 79 $.extend(homebrew, { 80 utils : { 81 /* 82 * Executes a function a max of once every n milliseconds 83 * 84 * Arguments: 85 * Func (Function): Function to be throttled. 86 * 87 * Delay (Integer): Function execution threshold in milliseconds. 88 * 89 * Returns: 90 * Lazy_function (Function): Function with throttling applied. 91 */ 92 throttle : function(func, delay) { 93 var timer = null; 94 95 return function () { 96 var context = this, 97 args = arguments; 98 99 clearTimeout(timer); 100 101 timer = setTimeout(function () { 102 func.apply(context, args); 103 }, delay); 104 }; 105 }, 106 /* End throttle */ 107 108 /* 109 * Executes a function when it stops being invoked for n seconds 110 * Modified version of _.debounce() http://underscorejs.org 111 * 112 * Arguments: 113 * Func (Function): Function to be debounced. 114 * 115 * Delay (Integer): Function execution threshold in milliseconds. 116 * 117 * Immediate (Bool): Whether the function should be called at the beginning 118 * of the delay instead of the end. Default is false. 119 * 120 * Returns: 121 * Lazy_function (Function): Function with debouncing applied. 122 */ 123 debounce : function(func, delay, immediate) { 124 var timeout, result; 125 126 return function() { 127 var context = this, 128 args = arguments, 129 later = function() { 130 timeout = null; 131 if (!immediate) result = func.apply(context, args
131); 132 }; 133 134 var callNow = immediate && !timeout; 135 136 clearTimeout(timeout); 137 timeout = setTimeout(later, delay); 138 if (callNow) result = func.apply(context, args); 139 140 return result; 141 }; 142 } 143 /* End debounce */ 144 }, 145 /* End utils */ 146 147 makePlugin : function(plugin) { 148 var pluginName = plugin.prototype.name; 149 150 $.fn[pluginName] = function(options) { 151 var args = $.makeArray(arguments), 152 after = args.slice(1); 153 154 return this.each(function() { 155 var instance = $.data(this, pluginName); 156 157 if(instance) { 158 if(typeof options === 'string') { 159 instance[options].apply(instance, after); 160 } else if(instance.update) { 161 instance.update.apply(instance, args); 162 } 163 } else { 164 new plugin(this, options); 165 } 166 }); 167 }; 168 }, 169 /* End makePlugin */ 170 171 generateUniqueID : function() { 172 return String(new Date().getTime()) + String(Math.round(Math.random()*100000, 10)); 173 }, 174 getSelectorsFromHTMLString : function(str) { 175 if(typeof str !== 'string') { 176 console.error('homebrew.getSelectorsFromHTMLString(): Expecting a string as the first argument. Please check:'); 177 console.log(str); 178 return {}; 179 } 180 181 var tagMatch = str.match(/<(.*?) /g), 182 attributesMatch = str.match(/ (.*?)=("|')(.*?)("|')/g), 183 selectorsObj = {}; 184 185 if(tagMatch) { 186 selectorsObj.tag = tagMatch[0].replace(/(<| )/g, ''); 187 } 188 189 if(attributesMatch) { 190 var attributeSplitArray, 191 classesArray; 192 193 while(attributesMatch.length) { 194 attributeSplitArray = $.trim(attributesMatch.shift()).split('='); 195 196 switch(attributeSplitArray[0]) { 197 case 'class' : 198 classesArray = attributeSplitArray[1].replace(/("|')/g, '').split(' '); 199 200 for(var i = classesArray.length-1; i > -1; i--) { 201 if(classesArray[i] === '') { 202 classesArray.splice(i, 1); 203 } 204 } 205 206 selectorsObj.classes = classesArray; 207 break; 208 209 default : 210 selectorsObj[attributeSplitArray[0]] = attributeSplitArray[1].replace(/("|')/g, ''); 211 break; 212 } 213 } 214 } 215 216 return selectorsObj; 217 }, 218 /* End getSelectorsFromHTMLString */ 219 220 getClassFromHTMLString : function(str) { 221 var selectorsObj = this.getSelectorsFromHTMLString(str); 222 223 if(selectorsObj.classes && selectorsObj.classes.length) { 224 return selectorsObj.classes; 225 } else { 226 return []; 227 } 228 }, 229 230 getKeyValuePairsFromString : function(str, pairSeparator, keyValueSeparator) { 231 pairSeparator = pairSeparator || ';'; 232 keyValueSeparator = keyValueSeparator || ':'; 233 var splitArray = str.split(pairSeparator), 234 keyValuePairs = {}, 235 currentPair; 236 while(splitArray.length) { 237 currentPair = splitArray.shift(); 238 if(!currentPair) continue; 239 currentPair = currentPair.split(keyValueSeparator); 240 currentPair = currentPair.map(function(str) { 241 return $.trim(str); 242 }); 243 if(currentPair[1] === 'true') { 244 currentPair[1] = true; 245 } else if(currentPair[1] === 'false') { 246 currentPair[1] = false; 247 } 248 keyValuePairs[currentPair[0]] = currentPair[1];
249 } 250 return keyValuePairs; 251 }, 252 watchSize : function(mediaQuery, callback) { 253 var self = this, 254 _mediaQuery = self.mediaQueries[mediaQuery]; 255 256 if(typeof _mediaQuery === 'undefined') { 257 throw new Error('homebrew.watchSize(): No match media query. mediaQuery provided is ' + mediaQuery); 258 } 259 260 /* For modern browsers, use native matchMedia.addListener */ 261 if(typeof matchMedia === 'function' && matchMedia.addListener) { 262 var matchMediaObj = matchMedia(_mediaQuery); 263 264 callback(matchMediaObj.matches); 265 266 matchMediaObj.addListener(function(mq) { 267 callback(mq.matches); 268 }); 269 /* For IE9, simulate the matchMedia.addListener behaviour using 270 * a resize handler. */ 271 } else if(!self.browser.lt9) { 272 var mediaQueryProps = self.mediaQueriesIE9[mediaQuery]; 273 if(typeof mediaQueryProps === 'undefined') return; 274 275 var currentScreen, 276 getCurrentScreen; 277 278 if(mediaQueryProps.method === 'min-width') { 279 getCurrentScreen = function() { 280 return ($(window).width() >= mediaQueryProps.size); 281 }; 282 } else if(mediaQueryProps.method === 'max-width') { 283 getCurrentScreen = function() { 284 return ($(window).width() <= mediaQueryProps.size); 285 }; 286 } 287 288 currentScreen = getCurrentScreen(); 289 290 callback(currentScreen); 291 292 $(window).on('resize.watchMedia', self.utils.throttle(function() { 293 if(currentScreen !== getCurrentScreen()) { 294 currentScreen = !currentScreen; 295 callback(currentScreen); 296 } 297 }, 100)); 298 /* For legacy browsers, only run the functions for medium 299 * screens and above. */ 300 } else { 301 if(mediaQuery === 'small' || mediaQuery === 'xsmall') { 302 callback(false); 303 } else { 304 callback(true); 305 } 306 } 307 } 308 /* End watchSize */ 309 }); 310 311 homebrew.watchSize('small', function(isMediumScreen) { 312 homebrew.screenSize.small = isMediumScreen;
313 homebrew.screenSize.medium = !isMediumScreen; 314 }); 315 316 homebrew.watchSize('large', function(isLargeScreen) { 317 homebrew.screenSize.medium = !isLargeScreen; 318 homebrew.screenSize.large = isLargeScreen; 319 }); 320 321 322 323 324 /**---- Carouselify ---**\ 325 * Turn a list of elements into a rotating carousel. 326 * 327 * Arguments: 328 * $('.carousel').carouselify({ 329 * items : '.carousel-item', 330 * activeItem : null, 331 * classes : { 332 * active : 'is-active', 333 * hidden : 'is-hidden' 334 * }, 335 * loop : true, 336 * transitions : { 337 * enable : true, 338 * classes : { 339 * transitionIn : 'is-transitioning-in', 340 * transitionOut : 'is-transitioning-out', 341 * reverse : 'is-reverse' 342 * }, 343 * onStart : null, 344 * onEnd : null 345 * }, 346 * switchers : { 347 * enable : true, 348 * markups : { 349 * nextSwitcher : '<div class="carousel__switcher carousel__switcher--next" />', 350 * prevSwitcher : '<div class="carousel__switcher carousel__switcher--prev" />' 351 * } 352 * }, 353 * pagers : { 354 * enable : true, 355 * markup : '<a href="#/" class="carousel__pager" />', 356 * holder : '<div class="carousel__pagers" />' 357 * }, 358 * timer : { 359 * enable : true, 360 * duration : 10000, 361 * showBar : true, 362 * barMarkup : '<div class="carousel__timer-bar" />' 363 * }, 364 * onSwitch : null 365 * }); 366 * 367 * - items 368 * |-- Type: String 369 * |-- Default: '.carousel-item' 370 * |-- Pass in the selector of the carousel items. 371 * - activeItem 372 * |-- Type: Number 373 * |-- Default: null 374 * |-- Pass in the index of the carousel item that should be active 375 * when the plugin is initialised. 376 * - classes 377 * |-- Type: Object
378 * |-- Pass in an object containing strings of the classes to use. 379 * Properties in this object: 380 * - active 381 * |-- Type: String 382 * |-- Default: 'is-active' 383 * |-- The class that will be added onto the active item. 384 * - hidden 385 * |-- Type: String 386 * |-- Default: 'is-hidden' 387 * |-- The class that will be added onto the switcher 388 * buttons when an unloopable carousel is at the first 389 * item and the last item. (At the first time, the 390 * prevSwitcher button will receive this class, while 391 * at the last item, the nextSwitcher button will 392 * receive it.) 393 * - loop 394 * |-- Type: Boolean 395 * |-- Default: true 396 * |-- Allows the carousel to loop back to the first item after it 397 * reaches the last item and vice versa. 398 * - transitions 399 * |-- Type: Object 400 * |-- Pass in an arguments object. Arguments in this object are: 401 * - enable 402 * |-- Type: Boolean 403 * |-- Default: true 404 * |-- Pass in `true` to have the plugin attempt to use 405 * CSS transitions by toggling the respective classes 406 * on the items and leveraging the `transitionEnd` 407 * event. 408 * - classes 409 * |-- Type: Object
410 * |-- Pass in an object containing strings of the classes 411 * to use. Properties in this object: 412 * - transitionIn: 413 * |-- Type: String 414 * |-- Default: 'is-transitioning-in' 415 * |-- The class that will be added onto the next 416 * active item to trigger the transition. 417 * - transitionOut: 418 * |-- Type: String 419 * |-- Default: 'is-transitioning-out' 420 * |-- The class that will be added onto the current 421 * active item to trigger the transition. 422 * - reverse: 423 * |-- Type: String 424 * |-- Default: 'is-reverse' 425 * |-- The class that will be added onto both the 426 * current and the next active item when the 427 * carousel is triggered in the opposite direction. 428 * This is useful to trigger a reverse transition. 429 * - onStart 430 * |-- Type: Function 431 * |-- Default: null 432 * |-- Pass in a callback function to be executed after the 433 * plugin adds the general homebrew transition class but 434 * before the plugin adds the transitionIn class to the 435 * target item. The function will receive two arguments: 436 * (1) the jQuery object of the item that the carousel 437 * is switching to, and 438 * (2) the jQuery object of the item that the carousel 439 * is switching away from. 440 * - onEnd 441 * |-- Type: Function 442 * |-- Default: null 443 * |-- Pass in a callback function to be executed after the 444 * transition completes and the plugin removes all 445 * transition-related classes from the items. The 446 * function will receive two arguments: 447 * (1) the jQuery object of the item that the carousel 448 * is switching to, and 449 * (2) the jQuery object of the item that the carousel 450 * is switching away from. 451 * - switchers 452 * |-- Type: Object 453 * |-- Pass in an arguments object. Arguments in this object are: 454 * - enable 455 * |-- Type: Boolean 456 * |-- Default: true 457 * |-- Option to have the plugin use switcher buttons. 458 * Switcher buttons make the carousel rotate between the 459 * next and previous items. 460 * - markups 461 * |-- Type: Object
462 * |-- Pass in an object containing strings of the markups 463 * to use. These markup strings have TWO uses: 464 * (1) the plugin will extract the classes in the markup 465 * string and use it to find any pre-existing switcher 466 * buttons in the carousel; if no pre-existing 467 * switcher buttons are found, then... 468 * (2) the plugin will create its own switcher buttons 469 * using the provided markup. 470 * |-- Properties in this object: 471 * - nextSwitcher: 472 * |-- Type: String 473 * |-- Default: ''<div class="carousel-switcher carousel-switcher--next" />' 474 * |-- The markup to be used for the switcher that 475 * rotates the carousel to the next item. 476 * - prevSwitcher: 477 * |-- Type: String 478 * |-- Default: ''<div class="carousel-switcher carousel-switcher--prev" />' 479 * |-- The markup to be used for the switcher that 480 * rotates the carousel to the previous item. 481 * - pagers 482 * |-- Type: Object 483 * |-- Pass in an arguments object. Arguments in this object are: 484 * - enable 485 * |-- Type: Boolean 486 * |-- Default: true 487 * |-- Option to have the plugin use pager buttons. 488 * Pager buttons make the carousel rotate directly to the 489 * corresponding item. 490 * - markup 491 * |-- Type: String 492 * |-- Default: '<div class="carousel__pager" />' 493 * |-- Pass in the markup string to be used for pagers. 494 * |-- Markup strings have TWO uses: 495 * (1) the plugin will extract the classes in the markup 496 * string and use it to find any pre-existing pager 497 * buttons in the carousel; if no pre-existing 498 * pager buttons are found, then... 499 * (2) the plugin will create its own pager buttons 500 * using the provided markup. 501 * - holder 502 * |-- Type: String 503 * |-- Default: '<div class="carousel__pagers" />' 504 * |-- Pass in the markup string to use for the holder of the 505 * holders. If a holder is used, then the plugin will look 506 * for pagers within the provided holders. 507 * |-- Pass in `null` or an empty string to refrain from using 508 * a holder. If a holder isn't used, then the plugin will 509 * search for pagers within the carousel element. 510 * |-- Markup strings have TWO uses: 511 * (1) the plugin will extract the classes in the markup 512 * string and use it to find any pre-existing pager 513 * buttons in the carousel; if no pre-existing 514 * pager buttons are found, then... 515 * (2) the plugin will create its own pager buttons 516 * using the provided markup. 517 * - timer 518 * |-- Type: Object 519 * |-- Pass in an arguments object. Arguments in this object are: 520 * - enable 521 * |-- Type: Boolean 522 * |-- Default: true 523 * |-- Option to have the plugin automatically rotate through 524 * the items based on a timer. 525 * - duration 526 * |-- Type: Number 527 * |-- Default: 10000 528 * |-- Determines the delay between each rotation. A larger 529 * number would cause the carousel to rotate less often. 530 * - onSwitch 531 * |-- Type: Function 532 * |-- Default: null 533 * |-- Pass in a callback function to be executed right before the 534 * carousel switches between items. The function will receive two 535 * arguments: 536 * (1) the jQuery object of the item that the carousel is 537 * switching to, and 538 * (2) the jQuery object of the item that the carousel is 539 * switching away from. 540 */ 541 homebrew.Carousel = function(el, args) { 542 if(!el) return; 543 this.init(el, args); 544 }; 545 546 $.extend(homebrew.Carousel.prototype, { 547 name : 'carouselify', 548 549 options : { 550 items : '.carousel-item', 551 552 activeItem : null, 553 554 classes : { 555 active : 'is-active', 556 hidden : 'is-hidden' 557 }, 558 559 loop : true, 560 561 transitions : { 562 enable : true, 563 classes : { 564 transitionIn : 'is-transitioning-in', 565 transitionOut : 'is-transitioning-out', 566 reverse : 'is-reverse' 567 }, 568 onStart : null, 569 onEnd : null 570 }, 571 572 switchers : { 573 enable : true, 574 markups : { 575 nextSwitcher : '<div class="carousel__switcher carousel__switcher--next" />', 576 prevSwitcher : '<div class="carousel__switcher carousel__switcher--prev" />' 577 } 578 }, 579 580 pagers : { 581 enable : true, 582 markup : '<a href="#/" class="carousel__pager" />', 583 holder : '<div class="carousel__pagers" />' 584 }, 585 586 timer : { 587 enable : true, 588 duration : 10000, 589 showBar : true, 590 barMarkup : '<div class="carousel__timer-bar" />' 591 }, 592 593 onSwitch : null 594 }, 595 596 init : function(el, args) { 597 var instance = this, 598 options = $.extend(true, {}, instance.options, args); 599 600 if(typeof options.items !== 'string') { 601 console.error('$.fn.carouselify: Expecting String type from `items` argument. Please check:'); 602 console.log(options.items); 603 return; 604 } 605 606 var $el = $(el), 607 $items = $el.find(options.items); 608 609 if(!$items.length) return; 610 611 instance.$el = $el; 612 instance.$items = $items; 613 instance.totalItems = $items.length; 614 instance.options = options; 615 616 if(typeof options.activeItem === 'number') { 617 $items.eq(instance.activeItem).addClass(options.classes.active); 618 instance.activeItem = options.activeItem; 619 } else if(instance.$items.filter(options.classes.active).length) { 620 instance.activeItem = instance.$items.filter(options.classes.active).index(); 621 } else { 622 instance.activeItem = 0; 623 } 624 625 if(options.switchers.enable) { 626 var $nextSwitcher, 627 nextSwitcherClasses = homebrew.getClassFromHTMLString(options.switchers.markups.nextSwitcher); 628 629 if(nextSwitcherClasses.length) { 630 $nextSwitcher = $el.find('.' + nextSwitcherClasses.join('.')); 631 } 632 if(!$nextSwitcher || !$nextSwitcher.length) { 633 $nextSwitcher = $(options.switchers.markups.nextSwitcher); 634 $el.append($prevSwitcher); 635 instance.addDestroyable($nextSwitcher); 636 } 637 638 instance.$nextSwitcher = $nextSwitcher; 639 640 var $prevSwitcher, 641 prevSwitcherClasses = homebrew.getClassFromHTMLString(options.switchers.markups.prevSwitcher); 642 643 if(prevSwitcherClasses.length) { 644 $prevSwitcher = $el.find('.' + prevSwitcherClasses.join('.')); 645 } 646 if(!$prevSwitcher || !$prevSwitcher.length) { 647 $prevSwitcher = $(options.switchers.markups.prevSwitcher); 648 $el.prepend($prevSwitcher); 649 instance.addDestroyable($prevSwitcher); 650 } 651 652 instance.$prevSwitcher = $prevSwitcher; 653 } 654 655 if(options.pagers.enable) { 656 var $holders, 657 $pagers, 658 pagerClasses = homebrew.getClassFromHTMLString(options.pagers.markup); 659 660 if(typeof options.pagers.holder === 'string' 661 && options.pagers.holder !== '') { 662 var holderClasses = homebrew.getClassFromHTMLString(options.pagers.holder); 663 664 if(holderClasses.length) { 665 $holders = $el.find('.' + holderClasses.join('.')); 666 } 667 if(!$holders || !$holders.length) { 668 $holders = $(options.pagers.holder).appendTo($el); 669 } 670 } 671 672 if(!$holders || !$holders.length) { 673 $holders = $el; 674 } 675 676 if(pagerClasses.length) { 677 $pagers = $holders.find('.' + pagerClasses.join('.')); 678 } 679 680 if(!$pagers || !$pagers.length) { 681 var finalPagersStr = []; 682 for(var i = instance.totalItems-1; i > -1; i--) { 683 finalPagersStr.push(options.pagers.markup); 684 }
685 $pagers = $(finalPagersStr.join('')).appendTo($holders); 686 instance.addDestroyable($pagers); 687 } 688 689 $pagers.eq(instance.activeItem).addClass(options.classes.active); 690 instance.$pagers = $pagers; 691 } 692 693 if(options.timer.enable && options.showBar) { 694 var $timerBar, 695 timerBarClasses = homebrew.getClassFromHTMLString(options.timer.barMarkup); 696 697 if(timerBarClasses.length) { 698 $timerBar = $el.find('.' + timerBarClasses.join('.')); 699 } 700 if(!$timerBar || !$timerBar.length) { 701 $timerBar = $(options.timer.barMarkup); 702 instance.addDestroyable($timerBar); 703 } 704 705 instance.$timerBar = $timerBar; 706 707 if(typeof options.timer.duration !== 'number') { 708 console.error('$.fn.carouselify(): Expecting a number for the timer duration. Please check:'); 709 console.log(options.timer.duration); 710 console.error('Reverting back to default.'); 711 712 options.timer.duration = homebrew.Carousel.prototype.options.timer.duration; 713 } 714 } 715 716 instance.enable(); 717 718 $.data(el, instance.name, instance); 719 720 return instance; 721 }, 722 723 enable : function() { 724 var instance = this; 725 726 if(instance.$nextSwitcher && instance.$prevSwitcher) { 727 instance.$nextSwitcher.on('click.' + instance.name, function() { 728 instance.switchTo('next'); 729 }); 730 731 instance.$prevSwitcher.on('click.' + instance.name, function() { 732 instance.switchTo('prev'); 733 }); 734 } 735 736 if(instance.$pagers) { 737 instance.$pagers.each(function(index) { 738 $(this).on('click.' + instance.name, function(e) { 739 e.preventDefault(); 740 instance.switchTo(index); 741 }); 742 }); 743 } 744 745 instance.runTimer(); 746 747 if(instance.options.timer.enable) { 748 $(window) 749 .on('focus.' + instance.name, function() { 750 instance.runTimer(); 751 }) 752 .on('blur.' + instance.name, function() { 753 clearTimeout(instance.timer); 754 }); 755 } 756 757 return instance; 758 }, 759 760 disable : function() { 761 var instance = this; 762 763 clearTimeout(instance.timer); 764 765 if(instance.$nextSwitcher && instance.$prevSwitcher) { 766 instance.$nextSwitcher.add(instance.$prevSwitcher).off('click.' + instance.name); 767 } 768 769 if(instance.$pagers) { 770 instance.$pagers.off('click.' + instance.name); 771 } 772 773 return instance; 774 }, 775 776 switchTo : function(itemIndex) { 777 var instance = this; 778 if(instance.isSwitching === true) return instance; 779 instance.isSwitching = true; 780 var activeItem = instance.activeItem, 781 options = instance.options; 782 783 if(typeof itemIndex === 'string') { 784 switch(itemIndex) { 785 case 'next' : 786 activeItem++; 787 788 if(activeItem >= instance.totalItems) { 789 activeItem = 0; 790 } 791 break; 792 793 case 'prev' : 794 activeItem--; 795 796 if(activeItem < 0) { 797 activeItem = instance.totalItems - 1; 798 } 799 break; 800 801 default: 802 console.error('Homebrew.Carousel.switchTo(): Unrecognised string method `' + itemIndex + '`.'); 803 return; 804 break; 805 } 806 } else if(typeof itemIndex === 'number') { 807 activeItem = itemIndex; 808 } else { 809 console.error('Homebrew.Carousel.switchTo(): Unsupported argument type: `' + typeof itemIndex + '`.'); 810 console.log(itemIndex); 811 return; 812 } 813 814 if(activeItem === instance.activeItem) return instance; 815 816 var $currentItem = instance.$items.eq(activeItem), 817 $prevItem = instance.$items.eq(instance.activeItem), 818 activeClass = options.classes.active; 819 820 if(typeof options.onSwitch === 'function') { 821 options.onSwitch.call(instance.$el[0], $currentItem, $prevItem); 822 } 823 824 instance.runTimer(); 825 826 if(options.transitions.enable) { 827 var transitionEvent = homebrew.events.transitionEnd, 828 transitionClass = homebrew.classes.transitionable, 829 transitionInClass = options.transitions.classes.transitionIn, 830 transitionOutClass = options.transitions.classes.transitionOut, 831 reverseClass = options.transitions.classes.reverse; 832 833 instance.$items.removeClass([ 834 reverseClass, 835 transitionClass 836 ].join(' '));
837 838 instance.$items.not($prevItem).removeClass(activeClass); 839 840 $currentItem 841 .one(transitionEvent, function() { 842 $currentItem 843 .off(transitionEvent) 844 .add($prevItem) 845 .removeClass([ 846 transitionInClass, 847 transitionOutClass, 848 reverseClass, 849 transitionClass, 850 activeClass 851 ].join(' ')) 852 .end() 853 .addClass(activeClass); 854 855 instance.isSwitching = false; 856 if(typeof options.transitions.onEnd === 'function') { 857 options.transitions.onEnd.call(instance.$el[0], $currentItem, $prevItem); 858 } 859 }); 860 861 if(itemIndex === 'prev' && instance.activeItem === 0 && activeItem === instance.totalItems-1 862 || itemIndex !== 'next' && instance.activeItem > activeItem) { 863 $prevItem.add($currentItem).addClass(reverseClass); 864 } 865 866 setTimeout(function() { 867 $prevItem.add($currentItem).addClass(transitionClass); 868 $prevItem.addClass(transitionOutClass); 869 $currentItem.addClass(transitionInClass); 870 871 if(typeof options.transitions.onStart === 'function') { 872 options.transitions.onStart.call(instance.$el[0], $currentItem, $prevItem); 873 } 874 }, 10); 875 } else { 876 instance.$items.removeClass(activeClass); 877 $currentItem.addClass(activeClass); 878 instance.isSwitching = false; 879 } 880 881 instance.$pagers.removeClass(activeClass) 882 .eq(activeItem) 883 .addClass(activeClass); 884 885 instance.activeItem = activeItem; 886 887 return instance; 888 }, 889 890 runTimer : function() { 891 var instance = this, 892 options = instance.options; 893 894 if(!options.timer.enable) return instance; 895 896 clearTimeout(instance.timer); 897 898 if(options.loop || activeItem < instance.totalItems-1) { 899 instance.timer = setTimeout(function() { 900 instance.switchTo('next'); 901 }, options.timer.duration); 902 } 903 904 return instance; 905 }, 906 907 addDestroyable : function($obj) { 908 var instance = this; 909 if(!instance.destroyables) instance.destroyables = []; 910 instance.destroyables.push($obj); 911 return instance; 912 }, 913 914 destroy : function() { 915 var instance = this; 916 917 instance.disable(); 918 919 if(instance.destroyable) { 920 while(instance.destroyable.length) { 921 instance.destoryable.shift().remove(); 922 } 923 } 924 925 $.removeData(instance.$el[0], instance.name); 926 } 927 }); 928 929 homebrew.makePlugin(homebrew.Carousel); 930 931 932 /**---- Height Syncer ----**\ 933 * Sync the height of a collection of items. 934 * 935 * Arguments: 936 * $('.items-holder').heightSyncify({ 937 * items : [ 938 * $('.items-holder').find('.item') 939 * ] 940 * }); 941 * 942 * - items 943 * |-- Type: Array 944 * |-- Pass in an Array of the items to sync. The Array can consist 945 * of either selector strings or jQuery Objects. If selector 946 * strings are provided, the plugin will try to select the targets 947 * within the currently iterated element. The sequence determines 948 * which set of items' height get synced first. This is important 949 * if you need to sync two items that are of parent-child relation 950 * (you would most likely want to sync the children's height first 951 * so that they also count towards the parents' height) 952 * 953 * Methods: 954 * - init 955 * |-- $('.items-holder').heightSyncify(); 956 * |-- Initialise the plugin. Once the plugin is initialised, you can 957 * pass in a string to trigger a specific method on it. 958 * |-- Accepts an arguments object. Refer to the above for the list 959 * of arguments available. 960 * - update 961 * |-- $('.items-holder').heightSyncify(); 962 * |-- Init the plugin again to update its options. 963 * |-- Accepts an arguments object. Refer to the above for the list 964 * of arguments available. 965 * - enable 966 * |-- $('.items-holder').heightSyncify('enable'); 967 * |-- Automatically re-sync the height when the window is resized. 968 * - disable 969 * |-- $('.items-holder').heightSyncify('disable'); 970 * |-- Stop re-syncing the height when the window is resized. 971 * - sync 972 * |-- $('.items-holder').heightSyncify('sync'); 973 * |-- Sync the items height. 974 * - destroy 975 * |-- $('.items-holder').heightSyncify('destroy'); 976 * |-- Removes the plugin. 977 */ 978 homebrew.HeightSyncer = function(el, args) { 979 if(!el) return; 980 this.init(el, args); 981 }; 982 $.extend(homebrew.HeightSyncer.prototype, { 983 name : 'heightSyncify', 984 985 init : function(el, args) { 986 args = args || {}; 987 var instance = this; 988 instance.$el = $(el); 989 instance.uniqueID = homebrew.generateUniqueID(); 990 instance.update(args) 991 .enable(); 992 $.data(el, instance.name, instance); 993 return instance; 994 }, 995 update : function(args) { 996 args = args || {}; 997 var instance = this; 998 instance.items = args.items.slice(0); 999 for(var i = instance.items.length-1; i > -1; i--) { 1000 if(typeof instance.items[i] === 'string') { 1001 instance.items[i] = instance.$el.find(instance.items[i]); 1002 } 1003 instance.items[i].find('img').each(function() { 1004 if(this.complete) return; 1005 $(this).one('load', function() { 1006 clearTimeout(instance.timer); 1007 instance.timer = setTimeout(function() { 1008 instance.sync(); 1009 }, 100); 1010 }); 1011 }); 1012 } 1013 if(args.options) { 1014 $.extend(instance, args.options); 1015 } 1016 instance.sync(); 1017 return instance; 1018 }, 1019 enable : function() { 1020 var instance = this; 1021 if(instance.enabled) return instance; 1022 instance.enabled = true; 1023 $(window).on('resize.' + instance.uniqueID, homebrew.utils.throttle(function() { 1024 instance.sync(); 1025 }, 30)); 1026 return instance; 1027 }, 1028 disable : function() { 1029 this.enabled = false; 1030 $(window).off('.' + this.uniqueID); 1031 return this; 1032 }, 1033 sync : function() { 1034 var instance = this, 1035 $currentCollection, 1036 $items = $(), 1037 leftOffset, 1038 currentLeftThreshold, 1039 heights, 1040 tallestHeight; 1041 1042 if(!instance.items || !instance.items.length) return instance; 1043 1044 for(var i = 0, ii = instance.items.length; i < ii; i++) { 1045 leftOffset = currentLeftThreshold = -9999; 1046 $currentCollection = instance.items[i]; 1047 1048 $currentCollection.each(function(index) { 1049 $items = $items.add($(this)); 1050 leftOffset = $(this).offset().left; 1051 if(!$currentCollection.eq(index+1).length 1052 || $currentCollection.eq(index+1).offset().left <= leftOffset) { 1053 heights = []; 1054 $items.css('height', ''); 1055 1056 $items.each(function() { 1057 heights.push($(this).outerHeight()); 1058 }); 1059 1060 tallestHeight = Math.max.apply(null, heights); 1061 1062 $items.outerHeight(tallestHeight); 1063 $items = $(); 1064 } 1065 }); 1066 } 1067 1068 instance.$el.trigger('afterSync'); 1069 1070 return instance; 1071 }, 1072 destroy : function() { 1073 var instance = this; 1074 while(instance.items.length) { 1075 instance.items.shift().css('height', ''); 1076 } 1077 instance.disable(); 1078 $.removeData(instance.$el[0], instance.name); 1079 } 1080 });
1081 homebrew.makePlugin(homebrew.HeightSyncer); 1082 /**---- Tooltipify ---**\ 1083 * Initialise a custom tooltip on the element. 1084 * 1085 * Arguments: 1086 * $('.my-element').tooltipify({ 1087 * appendTo : 'body', 1088 * classes : { 1089 * active : 'is-active' 1090 * }, 1091 * markups : { 1092 * tooltip : '<div class="tooltip" />' 1093 * }, 1094 * contents : function() { 1095 * var instance = this, 1096 * title = instance.$el.attr('title'); 1097 * 1098 * instance.$el.data(instance.name + '-title', title); 1099 * instance.$el.removeAttr('title'); 1100 * 1101 * return title; 1102 * }, 1103 * transitions : { 1104 * enable : true, 1105 * classes : { 1106 * transitionIn : 'is-transitioning-in', 1107 * transitionOut : 'is-transitioning-out' 1108 * } 1109 * }, 1110 * hoverDuration : 400 1111 * }); 1112 * 1113 * - appendTo 1114 * |-- Type: Object | String 1115 * |-- Default: 'body' 1116 * |-- Determines where the tooltip will be appended to when it is 1117 * is created to be shown. 1118 * |-- You can pass in either a string, a Node element or a jQuery 1119 * object. If what you passed in results in nothing being selected, 1120 * the plugin will fallback to appending to the <body> element. 1121 * - classes 1122 * |-- An Object that contains strings of general classes to be used 1123 * in the plugin. The classes are: 1124 * - active 1125 * |-- Default: 'is-active' 1126 * |-- The class that will be added to the tooltip after it is 1127 * created and shown. 1128 * - markups 1129 * |-- An Object that contains strings of markups to be used in 1130 * the plugin. The markups are: 1131 * - tooltip 1132 * |-- Default: '<div class="tooltip" />' 1133 * |-- The markup used to contain the tooltip. 1134 * - contents 1135 * |-- Type: Function | String 1136 * |-- Default: function() { 1137 * var instance = this, 1138 * title = instance.$el.attr('title'); 1139 * 1140 * instance.$el.data(instance.name + '-title', title); 1141 * instance.$el.removeAttr('title'); 1142 * 1143 * return title; 1144 * } 1145 * |-- Determines the content of the tooltip. There are two ways 1146 * to set the content: 1147 * (1) Use a function that returns the content string when it is 1148 * run. This is useful if the element itself has a title 1149 * attribute, as you can use this function to save the value 1150 * and then proceed to remove the attribute (to prevent the 1151 * default tooltip), 1152 * OR 1153 * (2) Directly pass in the content string itself. 1154 * |-- The content string is inserted using the `.html()` method. 1155 * - hoverDuration 1156 * |-- Type: Number 1157 * |-- Default: 400 1158 * |-- Determines how long the mouse needs to hover over the element 1159 * in order to trigger the tooltip. Lower number means a shorter 1160 * duration to trigger. 1161 * - transitions 1162 * |-- An Object that contains the various properties to be used 1163 * in the plugin. The properties are: 1164 * - enable 1165 * |-- Type: Boolean 1166 * |-- Default: true 1167 * |-- Determines whether or not the plugin should attempt 1168 * to leverage CSS transitions. 1169 * - classes 1170 * |-- An Object that contains strings of transition classes 1171 * to be used in the plugin. The classes are: 1172 * - transition 1173 * |-- Default: homebrew.classes.transition 1174 * |-- This class is used to enable the transition 1175 * effect on the element. 1176 * - transitionIn 1177 * |-- Default: 'is-transitioning-in' 1178 * |-- This class is used to make the tooltip 1179 * transition in. 1180 * - transitionOut 1181 * |-- Default: 'is-transitioning-out' 1182 * |-- This class is used to make the tooltip 1183 * transition out. 1184 */ 1185 homebrew.Tooltip = function(el, args) { 1186 if(!el) return; 1187 this.init(el, args); 1188 }; 1189 $.extend(homebrew.Tooltip.prototype, { 1190 name : 'tooltipify', 1191 options : { 1192 appendTo : 'body', 1193 markups : { 1194 tooltip : '<div class="tooltip" />', 1195 closer : '<a href="#/" class="tooltip__closer" />' 1196 }, 1197 classes : { 1198 active : 'is-active' 1199 }, 1200 contents : function() { 1201 var instance = this, 1202 title = instance.$el.attr('title'); 1203 instance.$el.data(instance.name + '-title', title); 1204 instance.$el.removeAttr('title'); 1205 return title; 1206 }, 1207 hoverDuration : 400, 1208 transitions : { 1209 enable : true, 1210 classes : { 1211 transition : homebrew.classes.transitionable, 1212 transitionIn : 'is-transitioning-in', 1213 transitionOut : 'is-transitioning-out' 1214 } 1215 } 1216 }, 1217 init : function(el, args) { 1218 var instance = this, 1219 options = $.extend({}, instance.options, args); 1220 instance.$el = $(el).addClass(instance.name); 1221 instance.uniqueID = homebrew.generateUniqueID(); 1222 instance.options = options; 1223 instance.$appendTo = $(options.appendTo); 1224 if(!instance.$appendTo.length) { 1225 instance.$appendTo = 'body'; 1226 } 1227 if(typeof options.contents === 'function') { 1228 options.contents = options.contents.call(instance); 1229 } 1230 instance.enable(); 1231 $.data(el, instance.name, instance); 1232 }, 1233 enable : function() { 1234 var instance = this, 1235 $el = instance.$el, 1236 options = instance.options; 1237 $el.on('click.' + instance.uniqueID, function(e) { 1238 e.preventDefault(); 1239 clearTimeout(instance.timer); 1240 instance.open(); 1241 }); 1242 $el.on('mouseenter.' + instance.uniqueID, function(e) { 1243 clearTimeout(instance.timer); 1244 instance.timer = setTimeout(function() { 1245 instance.open(); 1246 }, options.hoverDuration); 1247 }); 1248 $el.on('mouseleave.' + instance.uniqueID, function(e) { 1249 clearTimeout(instance.timer); 1250 instance.timer = setTimeout(function() { 1251 instance.close(); 1252 }, options.hoverDuration); 1253 }); 1254 return instance; 1255 }, 1256 disable : function() { 1257 this.$el.off('.' + instance.uniqueID); 1258 return this; 1259 }, 1260 getAltRender : function() { 1261 return Modernizr.touch && homebrew.screenSize.small; 1262 }, 1263 open : function() { 1264 var instance = this; 1265 if(instance.$tooltip) return; 1266 var $el = instance.$el, 1267 options = instance.options, 1268 $tooltip = $(options.markups.tooltip).appendTo(instance.$appendTo).html(options.contents), 1269 activeClass = options.classes.active, 1270 altRender = instance.getAltRender(); 1271 instance.$tooltip = $tooltip; 1272 if(altRender) { 1273 var $closer = $('.' + homebrew.getClassFromHTMLString(options.markups.closer).join('.')); 1274 if(!$closer.length) { 1275 $closer = $(options.markups.closer).prependTo($tooltip); 1276 } 1277 $closer.on('click', function(e) { 1278 e.preventDefault(); 1279 instance.close(); 1280 }); 1281 } else { 1282 if(homebrew.screenSize.small) { 1283 if($tooltip.outerWidth() + parseInt($tooltip.css('margin-left'), 10) === $(window).width()) { 1284 $tooltip.css({ 1285 left : '0px', 1286 marginLeft : '0px' 1287 }); 1288 } else if($el.offset().left + $tooltip.outerWidth() > $(window).width()) { 1289 $tooltip.css('right', '0px'); 1290 } else { 1291 $tooltip 1292 .css('left', $el.offset().left + 'px'); 1293 } 1294 } else { 1295 if($el.offset().left + $tooltip.outerWidth() > $(window).width()) { 1296 $tooltip 1297 .addClass('is-opposite') 1298 .css('right', '0px'); 1299 } else { 1300 $tooltip 1301 .css('left', $el.offset().left + 'px'); 1302 } 1303 } 1304 $tooltip.css('top', $el.offset().top - $('#mainContent').offset().top - $tooltip.outerHeight() + 'px') 1305 } 1306 if(options.transitions.enable) { 1307 var transitionEvent = homebrew.events.transitionEnd, 1308 transitionClass = homebrew.classes.transitionable, 1309 transitionInClass = options.transitions.classes.transitionIn; 1310 $tooltip 1311 .one(transitionEvent, function() { 1312 $tooltip.removeClass([transitionClass, transitionInClass].join(' ')) 1313 .addClass(activeClass); 1314 $(document).on([ 1315 'click.fauxBlur.', instance.uniqueID, 1316 ' touchstart.fauxBlur.', instance.uniqueID 1317 ].join(''), function(e) { 1318 if(!$tooltip.is(e.target) && $tooltip.has(e.target).length === 0) { 1319 instance.close(); 1320 } 1321 }); 1322 }) 1323 .on({ 1324 mouseenter : function() { 1325 clearTimeout(instance.timer); 1326 }, 1327 mouseleave : function() { 1328 clearTimeout(instance.timer); 1329 instance.timer = setTimeout(function() { 1330 instance.close(); 1331 }, options.hoverDuration); 1332 } 1333 }); 1334 setTimeout(function() { 1335 $tooltip.addClass(transitionClass); 1336 if(altRender) { 1337 $tooltip.css('margin-top', -$tooltip.outerHeight() + 'px'); 1338 } else { 1339 $tooltip.addClass(transitionInClass); 1340 } 1341 }, 10); 1342 } else { 1343 $tooltip.addClass(activeClass); 1344 } 1345 return instance; 1346 }, 1347 close : function(args) { 1348 args = args || {}; 1349 var instance = this, 1350 $tooltip = instance.$tooltip; 1351 if(!$tooltip) return; 1352 var options = instance.options, 1353 activeClass = options.classes.active, 1354 altRender = instance.getAltRender(); 1355 $(document).off('.fauxBlur.' + instance.uniqueID); 1356 if(options.transitions.enable) { 1357 var transitionEvent = homebrew.events.transitionEnd, 1358 transitionClass = homebrew.classes.transitionable, 1359 transitionOutClass = options.transitions.classes.transitionOut; 1360 $tooltip 1361 .trigger(transitionEvent) 1362 .off('mouseenter mouseleave') 1363 .one(transitionEvent, function() { 1364 $tooltip.removeClass([transitionClass, transitionOutClass].join(' ')) 1365 .removeClass(activeClass) 1366 .remove(); 1367 instance.$tooltip = null; 1368 if(typeof args.onCloseEnd === 'function') { 1369 args.onCloseEnd(); 1370 } 1371 }); 1372 setTimeout(function() { 1373 $tooltip.addClass(transitionClass); 1374 if(altRender) { 1375 $tooltip.css('margin-top', ''); 1376 } else { 1377 $tooltip.addClass(transitionOutClass); 1378 } 1379 }, 10); 1380 } else { 1381 $tooltip.removeClass(activeClass); 1382 instance.$tooltip = null; 1383 if(typeof args.onClose === 'function') { 1384 args.onClose(); 1385 } 1386 } 1387 return instance; 1388 }, 1389 destroy : function() { 1390 var instance = this, 1391 el = instance.$el[0], 1392 options = instance.options; 1393 instance.disable().close(); 1394 $.removeData(el, instance.name); 1395 if($.data(el, instance.name + '-title')) {
1396 el.title = $.data(el, instance.name + '-title'); 1397 $.removeData(el, instance.name + '-title'); 1398 } 1399 } 1400 }); 1401 homebrew.makePlugin(homebrew.Tooltip); 1402 /* Extend jQuery with our custom functions built in plugins format. */ 1403 $.fn.extend({ 1404 /**---- Dropdownify ----**\ 1405 * Call this `$('select').dropdownify()` function on the <select> nodes that 1406 * you want to have custom visuals on. 1407 1408 * Sample <select> node: 1409 1410 <select id="foobar"> 1411 <!-- All your options --> 1412 </select> 1413 1414 * This will result in: 1415 1416 <div class="dropdown"> 1417 <select id="foobar"> 1418 <!-- All your options --> 1419 </select> 1420 1421 <div class="dropdown-btn"> 1422 <div class="dropdown-arrow" /> 1423 <span>Label</span> 1424 </div> 1425 </div> 1426 1427 * If there are any attributes that you need to set onto the 1428 * resulting dropdown node, you can do it through 1429 * `data-dropdown-attributes`. The value has to be a valid 1430 * JSON format, as shown below: 1431 1432 <select data-dropdown-attributes='{ "class" : "sample-class", "style" : "width: 300px;", "id" : "sampleID" }' id="foobar"> 1433 <!-- All your options --> 1434 </select> 1435 1436 * Alternatively, you can wrap the <select> with a parent node 1437 * (preferably a <span> node) and add your desired attributes on 1438 * that node. Then, instead of running dropdownify on the <select> 1439 * node, you run it on the parent node instead. 1440 1441 * Example of wrapping with a parent node: 1442 1443 <span class="sample-class" style="width: 300px;" id="sampleID"> 1444 <select id="foobar"> 1445 <!-- All your options --> 1446 </select> 1447 </span> 1448 1449 $('#sampleID').dropdownify(); 1450 1451 * Both of the above methods will result in this: 1452 1453 <div class="sample-class dropdown" style="width: 300px;" id="sampleID"> 1454 <select id="foobar"> 1455 <!-- All your options --> 1456 </select> 1457 1458 <div class="dropdown-btn"> 1459 <div class="dropdown-arrow" /> 1460 <span>Label</span> 1461 </div> 1462 </div> 1463 */ 1464 dropdownify : function() { 1465 var pluginName = 'dropdownify', 1466 options = { 1467 markups : { 1468 holder : '<div />', 1469 button : '<div />', 1470 arrow : '<div><i /></div>', 1471 label : '<span />' 1472 }, 1473 classes : { 1474 holder : 'dropdown', 1475 button : 'dropdown-btn', 1476 arrow : 'dropdown-arrow', 1477 label : 'dropdown-label' 1478 } 1479 }, 1480 methods = { 1481 create: function() { 1482 /** 1483 * If these dropdowns are in <form> nodes, then we need to 1484 * attach a handler to adjust our input visuals whenever 1485 * the form is reset. 1486 */ 1487 var forms = this.parents('form'); 1488 1489 if(forms.length) { 1490 forms.off('.' + pluginName).on('reset.' + pluginName, function() { 1491 var _this = $(this); 1492 1493 setTimeout(function() { 1494 _this.find('select').trigger('change'); 1495 }, 25); 1496 }); 1497 } 1498 1499 this.each(function() { 1500 var $this = $(this), 1501 dropdown = ($this.is('select')) ? $this : $this.children('select'); 1502 if(!dropdown.length || typeof dropdown.data(pluginName) !== 'undefined') return; 1503 1504 var dropdownAttr; 1505 if($this.is('select')) { 1506 dropdownAttr = $this.data('dropdown-attributes'); 1507 } else { 1508 dropdownAttr = $this.getAttributes(); 1509 dropdown.unwrap(); 1510 } 1511 1512 var holder = dropdown.closest('.' + options.classes.holder); 1513 1514 if(holder.length < 1) { 1515 holder = $(options.markups.holder).addClass(options.classes.holder); 1516 dropdown.after(holder).prependTo(holder); 1517 } 1518 1519 if(typeof dropdownAttr === 'object') { 1520 $.each(dropdownAttr, function(key, value) { 1521 if(key === 'class') { 1522 holder.addClass(value); 1523 } else { 1524 holder.attr(key, value); 1525 } 1526 }); 1527 } 1528 1529 var btn = holder.find('.' + options.classes.button); 1530 if(btn.length < 1) btn = $(options.markups.button).addClass(options.classes.button).appendTo(holder); 1531 1532 var arrow = btn.find('.' + options.classes.arrow); 1533 if(arrow.length < 1) arrow = $(options.markups.arrow).addClass(options.classes.arrow).appendTo(btn); 1534 1535 var label = btn.find('.' + options.classes.label); 1536 if(label.length < 1) label = $(options.markups.label).addClass(options.classes.label).text(dropdown.find('option:checked').text()).appendTo(btn); 1537 1538 dropdown.data(pluginName, { 1539 activate: function() { 1540 var _this = this; 1541 1542 dropdown.on('change.' + pluginName, function() { 1543 _this.refresh();
1544 }); 1545 1546 return _this; 1547 }, 1548 1549 deactivate: function() { 1550 dropdown.off('.' + pluginName); 1551 return this; 1552 }, 1553 1554 refresh: function() { 1555 var checkedText = dropdown.find('option:checked').text(); 1556 if(!dropdown.data('ignore-asterisk') && checkedText.lastIndexOf('*') === checkedText.length-1) { 1557 checkedText = [ 1558 checkedText.substr(0, checkedText.length-1), 1559 '<span class="text-red">*</span>' 1560 ].join(''); 1561 } 1562 label.html(checkedText); 1563 btn.toggleClass('is-placeholder', Boolean(dropdown.val() === '')); 1564 return this; 1565 }, 1566 1567 destroy: function() { 1568 dropdown 1569 .insertBefore(holder) 1570 .off('.' + pluginName) 1571 .removeData(pluginName) 1572 .closest('form') 1573 .off('.' + pluginName); 1574 1575 holder.remove(); 1576 }, 1577 1578 $elems: { 1579 arrow: arrow, 1580 button: btn, 1581 holder: holder, 1582 label: label 1583 } 1584 }); 1585 1586 dropdown.data(pluginName).activate().refresh(); 1587 }); 1588 }, 1589 1590 activate: function() { 1591 runElemMethod.call(this, pluginName, 'activate'); 1592 }, 1593 1594 deactivate: function() { 1595 runElemMethod.call(this, pluginName, 'deactivate'); 1596 }, 1597 1598 refresh: function() { 1599 runElemMethod.call(this, pluginName, 'refresh'); 1600 }, 1601 1602 destroy: function() { 1603 runElemMethod.call(this, pluginName, 'destroy'); 1604 } 1605 }, 1606 _arguments = arguments; 1607 1608 for(var i = _arguments.length-1; i > -1; i--) { 1609 if(typeof _arguments[i] === 'object') $.extend(true, options, _arguments[i]); 1610 } 1611 1612 if(typeof _arguments[0] === 'string' && typeof methods[_arguments[0]] === 'function') { 1613 methods[_arguments[0]].call(this); 1614 } else { 1615 methods.create.call(this); 1616 } 1617 1618 return this; 1619 /* End dropdownify */ 1620 }, 1621 1622 1623 /**---- getAttributes ----**\ 1624 1625 * Call `$('.foo').getAttributes()` to get ALL attributes of the 1626 * element, which is returned as an Object Literal. 1627 1628 * This is added in to be used by the `$.dropdownify()` function, 1629 * but feel free to use it whenever you need it. 1630 **/ 1631 getAttributes : function() { 1632 var elem = this, 1633 attr = {}; 1634 1635 if(elem.length) { 1636 $.each(elem.get(0).attributes, function(v,n) { 1637 n = n.nodeName||n.name; 1638 v = elem.attr(n); // relay on $.fn.attr, it makes some filtering and checks 1639 if(v !== undefined && v !== false) attr[n] = v; 1640 }); 1641 } 1642 1643 return attr; 1644 }, 1645 1646 1647 /**---- Inputify ----**\ 1648 1649 * Call this `$('.foo').inputify()` function on the radio/checkboxes that
1650 * you want to have custom visuals on. 1651 1652 * Sample input node: 1653 1654 <label for="radio1"> 1655 <input type="radio" name="radioGroup" id="radio1" /> 1656 </label> 1657 1658 * Supported input types are `radio` and `checkbox`, as 1659 * only those two require custom visuals so far. 1660 1661 * `name` attribute is used for radio buttons to group 1662 * them up together, so that only one radio button in that 1663 * group can be checked at a time. 1664 1665 * Ideally, the input node should either (1) be wrapped inside 1666 * a <label> node, or (2) have a <label> that corresponds to the 1667 * ID of the input. If there is no <label> node to use, the script 1668 * will create its own. 1669 */ 1670 inputify : function(_options) { 1671 var pluginName = 'inputify', 1672 options = { 1673 inputIconClass : 'icon--input', 1674 radioClass : 'icon--radio-btn', 1675 checkboxClass : 'icon--checkbox', 1676 checkedClass : 'is-checked', 1677 hiddenClass : 'hidden-accessible', 1678 inputVisualMarkupArray : ['<div class="icon ', 'inputIconClass', ' ', 'inputTypeClass', '"><i /></div>'], 1679 labelMarkupArray : ['<label for="', 'inputID', '">', 'inputVisualMarkupArray', '</label>'] 1680 }, 1681 methods = { 1682 create: function() { 1683 var labels = $('label'); 1684 1685 /** 1686 * Setup the input visual DOM nodes 1687 */ 1688 this.each(function(index) { 1689 var input = $(this); 1690 if(typeof input.data(pluginName) !== 'undefined') return; 1691 1692 var inputType = input.attr('type'), 1693 inputTypeClass = getInputTypeClass(input.attr('type')), 1694 inputID = input.attr('id'), 1695 label = labels.filter('[for="' + inputID + '"]'); 1696 1697 if(!label.length) label = input.closest('label'); 1698 1699 if(!label.length) { 1700 if(typeof inputID !== 'string' || inputID === '') { 1701 input.attr('id', index + new Date().getTime()); 1702 inputID = input.attr('id'); 1703 } 1704 1705 options.labelMarkupArray[1] = inputID; 1706 options.labelMarkupArray[3] = options.inputVisualMarkupArray.join(''); 1707 label = $(options.labelMarkupArray.join('')); 1708 1709 label.data(pluginName, { 1710 destroyable: true 1711 }); 1712 1713 input.after(label); 1714 } 1715 1716 var inputVisual = label.find('.' + inputTypeClass); 1717 1718 if(!inputVisual.length) { 1719 options.inputVisualMarkupArray[1] = options.inputIconClass; 1720 options.inputVisualMarkupArray[3] = inputTypeClass; 1721 if(input.is(':checked')) options.inputVisualMarkupArray[3] += ' is-checked'; 1722 1723 if(input.parents('label').length) { 1724 $(options.inputVisualMarkupArray.join('')).insertAfter(input); 1725 } else { 1726 $(options.inputVisualMarkupArray.join('')).prependTo(label); 1727 } 1728 } 1729 1730 input 1731 .addClass(options.hiddenClass) 1732 .data(pluginName, { 1733 '$els' : { 1734 'label' : label 1735 }, 1736 1737 refresh : function() { 1738 var $inputVisual = this.$els.label.find('.' + options.inputIconClass); 1739 1740 if(input.is(':checked')) { 1741 $inputVisual.addClass(options.checkedClass); 1742 } else { 1743 $inputVisual.removeClass(options.checkedClass); 1744 } 1745 1746 return this; 1747 } 1748 }); 1749 }); 1750 1751 /** 1752 * Setup the change handler for radio buttons. 1753 * 1754 * It is more complicated for radios, because if the clicked 1755 * radio is in a group, only the clicked radio will trigger 1756 * the change event, the rest of the radios in the group won't. 1757 * We need to work around this in order to update the custom 1758 * radio visuals accordingly. 1759 */ 1760 var radioInputs = this.filter('[type="radio"]'); 1761 1762 if(radioInputs.length) { 1763 /** 1764 * First, split the radio inputs into groups. This is done by 1765 * pushing each group's name into an array, making sure 1766 * that there are no duplicates. 1767 */ 1768 var radioGroupNames = []; 1769 1770 radioInputs.each(function() { 1771 var radioInput = $(this), 1772 radioInputName = radioInput.attr('name'); 1773 1774 if(typeof radioInputName !== 'string') return; 1775 1776 if(radioGroupNames.length < 1 && radioInputName !== '') { 1777 radioGroupNames.push(radioInputName); 1778 } else { 1779 for(var i = radioGroupNames.length-1; i > -1; i--) { 1780 if(radioGroupNames[i] == radioInputName) { 1781 break; 1782 } else if(i === 0) { 1783 radioGroupNames.push(radioInputName); 1784 } 1785 } 1786 } 1787 }); 1788 1789 /** 1790 * Now we iterate through each group. 1791 * 1792 * If at anytime, a radio input in the group triggers a 1793 * change, we will loop through all the other radio inputs 1794 * in the same group to update their visuals accordingly. 1795 * 1796 * An IIFE (Immediately Invoked Function Expression) is used
1797 * so that the variable can be preserved to be used with the 1798 * event handler. 1799 */ 1800 for(var i=radioGroupNames.length; i > -1; i--) { 1801 (function() { 1802 var $radioGroupInputs = radioInputs.filter('[name="' + radioGroupNames[i] + '"]'); 1803 1804 $radioGroupInputs 1805 .off('change.' + pluginName) 1806 .on('change.' + pluginName, function() { 1807 $radioGroupInputs.each(function() { 1808 $(this).data(pluginName).refresh(); 1809 }); 1810 }); 1811 })(); 1812 } 1813 } 1814 1815 /** 1816 * Setup the change handler for checkboxes. 1817 */ 1818 var checkboxInputs = this.filter('[type="checkbox"]'); 1819 1820 if(checkboxInputs.length) { 1821 checkboxInputs 1822 .off('change.' + pluginName) 1823 .on('change.' + pluginName, function() { 1824 $(this).data(pluginName).refresh(); 1825 }); 1826 } 1827 1828 /** 1829 * If these inputs are in <form> nodes, then we need to attach 1830 * a handler to adjust our input visuals whenever the form 1831 * is reset. 1832 */ 1833 var forms = this.closest('form'), 1834 inputs = this; 1835 1836 if(forms.length) { 1837 forms 1838 .off('reset.inputify') 1839 .on('reset.inputify', function() { 1840 setTimeout(function() { 1841 inputs.trigger('change'); 1842 }, 25); 1843 }); 1844 } 1845 }, 1846 1847 destroy: function() { 1848 var labels = $('label'); 1849 1850 this.each(function() { 1851 var $this = $(this), 1852 inputId = $this.attr('id'); 1853 1854 if(typeof label.data(pluginName) !== 'object' 1855 && label.data(pluginName).destroyable) { 1856 label.remove(); 1857 } 1858 }); 1859 1860 var forms = this.closest('form'); 1861 1862 /* Remove all plugin event handlers from the input elements 1863 * and their corresponding forms. */ 1864 this.off('.' + pluginName); 1865 forms.off('.' + pluginName); 1866 }, 1867 1868 refresh: function() { 1869 runElemMethod.call(this, pluginName, 'refresh'); 1870 } 1871 }, 1872 _arguments = arguments; 1873 1874 function getInputTypeClass(type) { 1875 switch(type) { 1876 case 'radio': return options.radioClass; 1877 case 'checkbox': return options.checkboxClass; 1878 default: return undefined; 1879 } 1880 } 1881 1882 for(var i = _arguments.length-1; i > -1; i--) { 1883 if(typeof _arguments[i] === 'object') $.extend(options, _arguments[i]); 1884 } 1885 1886 if(typeof _arguments[0] === 'string' && typeof methods[_arguments[0]] === 'function') { 1887 methods[_arguments[0]].call(this); 1888 } else { 1889 methods.create.call(this); 1890 } 1891 1892 return this; 1893 /* End inputify */ 1894 }, 1895 1896 1897 /**---- Popupify ----**\ 1898 1899 * Call this `$('.foo').popupify()` function on your desired popups and 1900 * popup togglers. 1901 1902 * Sample popup toggler node: 1903 1904 <a href="#corporateProfile" class="foo">Toggle popup</a> 1905 1906 <div id="corporateProfile" /> 1907 1908 * `href` should be a hash, followed by the ID of the target popup. 1909 1910 * Alternatively, you may pass a function into the `content` parameter to 1911 * programmatically select the element whose contents will be used as the 1912 * popup contents. For example: 1913 1914 <button type="button" id="popupToggler">Click me to show popup</button> 1915 1916 <div class="foobar-con"> 1917 <div class="hello-world"> ... </div> 1918 <div class="popup-content"> ... </div> 1919 </div> 1920 1921 <script> 1922 $(function() { 1923 $('#popupToggler').popupify({ 1924 content : function() { 1925 // `this` refers to the element currently being iterated over 1926 return $(this).next().find('.popup-content'); 1927 } 1928 }); 1929 }); 1930 </script> 1931 1932 * While invoking this function, it is possible to specify a `height` 1933 * argument to create popups that have a set height: 1934 1935 $('.foobar').popupify({ height : 500 }); 1936 1937 * Alternatively, you may also specify the height through the 1938 * `data-popup-height` attribute: 1939 1940 <a href="#corporateProfile" data-popup-height="500">Toggle popup</a> 1941 1942 * The height value MUST be a NUMBER. 1943 1944 * If the popup contents is taller than its set height, default 1945 * scrollbars will appear on the popup. 1946 1947 * If the popup itself is taller than the page, then scrollbars 1948 * will appear on the popup holder. 1949 **/ 1950 popupify : function(_options) { 1951 var pluginName = 'popupify', 1952 $body = $('body'), 1953 options = { 1954 classes : { 1955 mainPopupHolder : 'main-popup-holder', 1956 shown : 'is-shown' 1957 }, 1958 ids : { 1959 mainPopupHolder : 'mainPopupHolder' 1960 }, 1961 $elems : { 1962 mainPopupHolder : null 1963 }, 1964 markups : { 1965 closeBtn : '<a href="#close" data-hide-popup="true" class="popup-closer">Back</a>' 1966 }, 1967 content : undefined, 1968 addCloseBtns : true, 1969 closeOnOverlay : true 1970 }, 1971 methods = { 1972 create : function() { 1973 var $mainPopupHolder = options.$elems.mainPopupHolder; 1974 1975 if(!thisObjectExists($mainPopupHolder)) $mainPopupHolder = $('#' + options.ids.mainPopupHolder); 1976 1977 if($mainPopupHolder.length < 1) { 1978 $mainPopupHolder = $([ 1979 '<div class="', options.classes.mainPopupHolder, '" id="', options.ids.mainPopupHolder, '" ></div>' 1980 ].join('')).appendTo($body); 1981 1982 options.$elems.mainPopupHolder = $mainPopupHolder; 1983 } else { 1984 $mainPopupHolder.appendTo('body'); 1985 } 1986 1987 /** 1988 * If the main popup holder doesn't have any of its popup 1989 * methods, add them in now. 1990 **/ 1991 if(!thisDataIsValidObject($mainPopupHolder, pluginName)) { 1992 $mainPopupHolder.data(pluginName, { 1993 reveal : function() { 1994 $body.addClass('popup-is-shown'); 1995 setTimeout(function() { 1996 $mainPopupHolder.addClass(options.classes.shown); 1997 }, 10); 1998 return this; 1999 }, 2000 2001 conceal : function() { 2002 $body.removeClass('popup-is-shown'); 2003 setTimeout(function() { 2004 $mainPopupHolder.removeClass(options.classes.shown); 2005 }, 10); 2006 return this; 2007 }, 2008 2009 activate : function() { 2010 var _dataMethods = this; 2011 2012 _dataMethods.deactivate(); 2013 2014 $mainPopupHolder.on("click." + pluginName, function() { 2015 if($.fn.popupify.closeOnOverlay === true) { 2016 _dataMethods.conceal(); 2017 } 2018 }); 2019 2020 $mainPopupHolder.on('click.' + pluginName, '.popup', function(e) { 2021 e.stopPropagation(); 2022 }); 2023 2024 $mainPopupHolder.on('click.' + pluginName, '[data-hide-popup]', function(e) { 2025 e.preventDefault(); 2026 _dataMethods.conceal(); 2027 }); 2028 2029 return _dataMethods; 2030 }, 2031 2032 deactivate : function() { 2033 $mainPopupHolder.off('.' + pluginName); 2034 return this; 2035 }, 2036 2037 destroy : function() { 2038 this.deactivate(); 2039 $mainPopupHolder.removeData(pluginName); 2040 } 2041 }); 2042 2043 /* Attach the event handlers to the main popup holder */ 2044 $mainPopupHolder.data(pluginName).activate(); 2045 2046 /* Apply pseudo-elements for IE7. */ 2047 if(homebrew.browser.ie7) $mainPopupHolder.pseudofy({ method : 'before' }); 2048 } 2049 2050 /** 2051 * If the `this` reference has nothing for us to iterate 2052 * through, exit the function. 2053 **/ 2054 if(this.length < 1) return; 2055 2056 this.each(function() { 2057 var $this = $(this), 2058 dataTargetPopup = $this.data('target-popup'), 2059 target; 2060 2061 /* See if a target content is available */ 2062 if(typeof options.content === 'function') { 2063 target = options.content.call(this); 2064 } else if(typeof dataTargetPopup === 'string') { 2065 target = $($this.data('target-popup')); 2066 } else if(typeof dataTargetPopup === 'object' && dataTargetPopup instanceof jQuery) { 2067 target = dataTargetPopup; 2068 } else if(typeof $this.attr('href') !== 'undefined') { 2069 target = $($this.attr('href')); 2070 } 2071 2072 /** 2073 * If a target content available, then what we're currently 2074 * iterating through should be the popup toggler. Proceed 2075 * to attach event handlers on the toggler.
2076 **/ 2077 if(thisObjectExists(target)) { 2078 if(!thisDataIsValidObject($this, pluginName)) { 2079 $this.data(pluginName, { 2080 reveal : function() { 2081 target.data(pluginName).reveal(); 2082 return this; 2083 }, 2084 2085 conceal : function() { 2086 $mainPopupHolder.data(pluginName).conceal(); 2087 return this; 2088 }, 2089 2090 activate : function() { 2091 var _dataMethods = this; 2092 2093 _dataMethods.deactivate(); 2094 2095 $this.on('click.' + pluginName, function(e) { 2096 e.preventDefault(); 2097 _dataMethods.reveal(); 2098 }); 2099 2100 return $this; 2101 }, 2102 2103 deactivate : function() { 2104 $this.off('.' + pluginName); 2105 return $this; 2106 }, 2107 2108 destroy : function() { 2109 this.deactivate(); 2110 target.data(pluginName).destroy(); 2111 $this.removeData(pluginName); 2112 } 2113 }); 2114 2115 $this.data(pluginName).activate(); 2116 } 2117 } else { 2118 /** 2119 * Otherwise, assume that what we're iterating through 2120 * is the target content itself. 2121 **/ 2122 target = $(this); 2123 } 2124 2125 if(!target.parent().is($mainPopupHolder)) { 2126 target.appendTo($mainPopupHolder); 2127 } 2128 2129 if(options.addCloseBtns && target.find('.popup-closer').length < 1) { 2130 target.prepend(options.markups.closeBtn); 2131 } 2132 2133 if(typeof target.data(pluginName) === 'undefined') { 2134 target.data(pluginName, { 2135 reveal : function() { 2136 $mainPopupHolder.find('.popup').removeClass(options.classes.shown); 2137 target.addClass(options.classes.shown); 2138 $mainPopupHolder.data(pluginName).reveal(); 2139 $.fn.popupify.closeOnOverlay = options.closeOnOverlay; 2140 2141 if(homebrew.browser.ie) { 2142 target.trigger('revealed'); 2143 } else { 2144 $mainPopupHolder.on(homebrew.events.transitionEnd, function() { 2145 $mainPopupHolder.off(homebrew.events.transitionEnd); 2146 target.trigger('revealed'); 2147 }); 2148 } 2149 2150 return this; 2151 }, 2152 2153 conceal : function() { 2154 $mainPopupHolder.data(pluginName).conceal(); 2155 return this; 2156 }, 2157 2158 destroy : function() { 2159 target.removeData(pluginName); 2160 } 2161 }); 2162 } 2163 }); 2164 }, 2165 2166 activate : function() { 2167 runElemMethod.call(this, pluginName, 'activate'); 2168 }, 2169 2170 deactivate : function() { 2171 runElemMethod.call(this, pluginName, 'deactivate'); 2172 }, 2173 2174 destroy : function() { 2175 var $mainPopupHolder = options.$elems.mainPopupHolder; 2176 2177 if(!thisObjectExists($mainPopupHolder)) $mainPopupHolder = $('#' + options.ids.mainPopupHolder); 2178 2179 if($mainPopupHolder.length && typeof $mainPopupHolder.data(pluginName) !== 'undefined') { 2180 $mainPopupHolder.data(pluginName).destroy(); 2181 } 2182 2183 runElemMethod.call(this, pluginName, 'destroy'); 2184 } 2185 }, 2186 _arguments = arguments; 2187 2188 /** 2189 * Run through the arguments. If an object is found, assume it's an 2190 * Object Literal with customised options for the function. 2191 **/ 2192 for(var i = _arguments.length-1; i > -1; i--) { 2193 if(typeof _arguments[i] === 'object') $.extend(options, _arguments[i]); 2194 } 2195 2196 if(typeof _arguments[0] === 'string' && typeof methods[_arguments[0]] === 'function') { 2197 methods[_arguments[0]].call(this); 2198 } else { 2199 methods.create.call(this); 2200 } 2201 2202 return this; 2203 /* End popupify */ 2204 }, 2205 2206 2207 /**---- Placeholderify ----**\ 2208 2209 * Call this `$('.foo').placeholderify()` function on any no
2209des that 2210 * require fallback for the `placeholder` HTML5 attribute. 2211 2212 * You are required to perform your own browser/feature 2213 * checking before calling this function. 2214 */ 2215 placeholderify : function(_options) { 2216 var options = $.extend({}, _options, { 2217 placeholderClass : 'is-placeholder' 2218 }); 2219 2220 return this.each(function() { 2221 var input = $(this), 2222 placeholderText = input.attr('placeholder'); 2223 2224 if(input.val() === '') { 2225 if(input.attr('type') === 'password') { 2226 input.attr('type', 'text'); 2227 input.data('was-password', true); 2228 } 2229 input.val(placeholderText).addClass(options.placeholderClass); 2230 } 2231 2232 input.on({ 2233 'focus.placeholder' : function() { 2234 if(input.val() === placeholderText) { 2235 if(input.data('was-password')) { 2236 input.attr('type', 'password'); 2237 } 2238 input.val('').removeClass(options.placeholderClass); 2239 } 2240 }, 2241 2242 'blur.placeholder' : function() { 2243 if(input.val() === '') { 2244 if(input.data('was-password')) { 2245 input.attr('type', 'text'); 2246 } 2247 input.val(placeholderText).addClass(options.placeholderClass); 2248 } 2249 } 2250 }); 2251 }); 2252 }, 2253 2254 2255 /**---- queryString ----**\ 2256 2257 * Function to get the query string. Let's say your URL is like this: 2258 2259 aaa.com?title=hello&desc=goodbye 2260 2261 * Calling `$.fn.queryStr()` would return you an Object Literal with all 2262 * the key-value pairs, as shown: 2263 2264 { 2265 title : 'hello', 2266 desc : 'goodbye' 2267 } 2268 2269 * If no query string is available, the function will return `false`. 2270 2271 * The function accepts a single argument, which should be the key 2272 * that you're searching for. If a matching key is found, its value is 2273 * returned. 2274 2275 * Using the above URL again, if you were to run `$.fn.queryStr('title')`, 2276 * then the function will return 'hello' to you. If the query string isn't 2277 * found, then the function will return `false`. 2278 **/ 2279 queryStr : function(key) { 2280 var searchStr = window.location.search; 2281 2282 if(searchStr === '') return false; 2283 2284 var queryArr = window.location.search.substr(1).split('&'), 2285 tempArr; 2286 2287 if(typeof key === 'string') { 2288 for(var i = 0, ii = queryArr.length; i < ii; i++) { 2289 tempArr = queryArr[i].split('='); 2290 if(tempArr[0] === key) return tempArr[1]; 2291 } 2292 return false; 2293 } else { 2294 var resultValue = {}; 2295 2296 for(var i = 0, ii = queryArr.length; i < ii; i++) { 2297 tempArr = queryArr[i].split('='); 2298 resultValue[tempArr[0]] = tempArr[1]; 2299 } 2300 2301 return resultValue; 2302 } 2303 }, 2304 2305 2306 /**---- Togglerify ----**\ 2307 2308 * `$('.togglers').togglerify()` turns its target nodes into 2309 * togglers; by default, it is a simple class toggler that 2310 * adds/removes a class on its corresponding target node. 2311 2312 * An example: 2313 2314 <a class="togglers" data-togglerify-target="togglerContentID">Toggler</a> 2315 2316 <div id="togglerContentID" /> 2317 2318 * `data-togglerify-target` determines which element the function 2319 * will toggle the class on. The value should be the ID of the 2320 * desired node. Alternatively, you can pass in a function to the 2321 * `content` argument, which will be used to select the desired 2322 * element. For example: 2323 2324 <a class="togglers">Toggler</a> 2325 <div class="contents" /> 2326 2327 $('.togglers').togglerify({ 2328 content: function(index) { 2329 return $(this).next('.contents); 2330 } 2331 }); 2332 2333 * The `this` keyword refers to the toggler element that's being 2334 * iterated over. You're also given the toggler's index to make use. 2335 2336 * By default, the class toggled is `is-toggled`. You may pass 2337 * in your own class to toggle, as follows: 2338 2339 $('.foo').togglerify({ toggledClass : 'is-triggered' }); 2340 2341 * The parameters that let you adjust the behaviour: 2342 * - `singleActive` 2343 * = Default value: false 2344 * = Setting this to `true` ensures that there is only 2345 * one active toggler at any given time in each 2346 * iterated group 2347 * - `selfToggleable` 2348 * = Default value: true 2349 * = Setting this to `false` removes the toggler's 2350 * ability to toggle itself off. 2351 * - `slide` 2352 * = Default value: false 2353 * = Setting this to `true` causes the function to 2354 * add a sliding transition effect, using CSS Transitions 2355 * if available, and falling back to jQuery's slide 2356 * methods otherwise. 2357 * - `pretoggle` 2358 * = Default value: undefined 2359 * = Assigning an index to this parameter causes the 2360 * function to immediately activate the toggler in 2361 * question. Index should be zero-based. 2362 * - `content` 2363 * = Default value: undefined 2364 * = Assigning a function to this parameter causes the 2365 * function to run this in order to find its target 2366 * content. If this parameter is provided, the function 2367 * will ignore the `data-togglerify-target` attribute. 2368 **/ 2369 togglerify : function() { 2370 var pluginName = 'togglerify', 2371 options = { 2372 toggledClass : 'is-toggled', 2373 singleActive : false, 2374 selfToggleable : true, 2375 2376 slide : false, 2377 slideDuration : 300, 2378 2379 pretoggle : undefined, 2380 content : undefined, 2381 2382 useCSSTransitions : !homebrew.browser.ie && root.hasClass('csstransitions'), 2383 2384 classes : { 2385 transitionable: homebrew.classes.transitionable 2386 } 2387 }, 2388 methods = { 2389 create: function() { 2390 var togglers = this; 2391 2392 togglers.each(function(index) { 2393 var thisToggler = $(this), 2394 targetData = thisToggler.data('togglerify-target'), 2395 target; 2396 2397 if(typeof options.content === 'function') { 2398 target = options.content.call(this, index); 2399 } else if(typeof targetData === 'string') { 2400 target = $('#' + targetData); 2401 } else { 2402 target = $(thisToggler.attr('href')); 2403 } 2404 2405 if(!target.length) return; 2406 2407 var togglerAndTarget = thisToggler.add(target); 2408 2409 togglerAndTarget.data(pluginName, { 2410 activate : function() { 2411 this.deactivate(); 2412 2413 thisToggler.on('click.' + pluginName, function(e) { 2414 e.preventDefault(); 2415 2416 if(thisToggler.hasClass(options.toggledClass)) { 2417 /** 2418 * If the selfToggleable boolean is false, it means that the 2419 * toggler is not able to toggle itself off. 2420 **/ 2421 if(options.selfToggleable === false) return; 2422 2423 /* Make this toggler inactive */ 2424 thisToggler.data(pluginName).toggleOff(); 2425 return; 2426 } 2427 2428 /* Make this toggler active */ 2429 thisToggler.data(pluginName).toggleOn(); 2430 }); 2431 2432 return this; 2433 }, 2434 2435 deactivate : function() { 2436 thisToggler.off('click.' + pluginName); 2437 return this; 2438 }, 2439 2440 toggleOn : function(methodSettings) { 2441 methodSettings = methodSettings || {}; 2442 2443 togglerAndTarget 2444 .trigger('toggleOn', [thisToggler, target]) 2445 .trigger('toggle', [thisToggler, target]); 2446 2447 thisToggler.addClass(options.toggledClass); 2448 target.addClass(options.toggledClass); 2449 2450 /** 2451 * If the singleActive boolean is true, it means that only 2452 * one toggler in the group can be active at any given time. 2453 **/ 2454 if(options.singleActive) { 2455 togglers.not(thisToggler).togglerify('toggleOff'); 2456 } 2457 2458 if(options.slide && !methodSettings.noSlide) { 2459 var targetHeight = methodSettings.contentHeight; 2460 2461 if(options.useCSSTransitions) { 2462 if(typeof targetHeight !== 'number') { 2463 target.css('height', 'auto'); 2464 targetHeight = target.get(0).clientHeight; 2465 } 2466 2467 target.css('height', '0px').addClass(options.classes.transitionable); 2468 2469 setTimeout(function() { 2470 target 2471 .off(homebrew.events.transitionEnd) 2472 .on(homebrew.events.transitionEnd, function() { 2473 target 2474 .off(homebrew.events.transitionEnd) 2475 .removeClass(options.classes.transitionable); 2476 2477 if(typeof methodSettings.contentHeight !== 'number') { 2478 target.css('height', ''); 2479 } 2480 2481 togglerAndTarget 2482 .trigger('afterToggleOn', [thisToggler, target]) 2483 .trigger('afterToggle', [thisToggler, target]); 2484 }) 2485 .css('height', targetHeight + 'px'); 2486 }, 5);
2487 } else { 2488 if(typeof targetHeight !== 'number') { 2489 target.show(); 2490 targetHeight = target.get(0).clientHeight; 2491 } 2492 target.hide(); 2493 target.slideDown(options.slideDuration, function() { 2494 togglerAndTarget 2495 .trigger('afterToggleOn', [thisToggler, target]) 2496 .trigger('afterToggle', [thisToggler, target]); 2497 }); 2498 } 2499 } else { 2500 togglerAndTarget 2501 .trigger('afterToggleOn', [thisToggler, target]) 2502 .trigger('afterToggle', [thisToggler, target]); 2503 } 2504 2505 return this; 2506 }, 2507 2508 toggleOff : function(methodSettings) { 2509 if(!thisToggler.hasClass(options.toggledClass)) return; 2510 methodSettings = methodSettings || {}; 2511 2512 togglerAndTarget 2513 .trigger('toggleOff', [thisToggler, target]) 2514 .trigger('toggle', [thisToggler, target]); 2515 2516 thisToggler.removeClass(options.toggledClass); 2517 2518 if(options.slide && !methodSettings.noSlide) { 2519 if(options.useCSSTransitions) { 2520 var targetHeight = target.get(0).clientHeight; 2521 2522 target 2523 .css('height', targetHeight + 'px') 2524 .removeClass(options.toggledClass); 2525 2526 setTimeout(function() { 2527 target 2528 .addClass(options.classes.transitionable) 2529 .off(homebrew.events.transitionEnd) 2530 .on(homebrew.events.transitionEnd, function() { 2531 target 2532 .off(homebrew.events.transitionEnd) 2533 .removeClass(options.classes.transitionable) 2534 .css('height', ''); 2535 2536 togglerAndTarget 2537 .trigger('afterToggleOff', [thisToggler, target]) 2538 .trigger('afterToggle', [thisToggler, target]); 2539 }) 2540 .css('height', '0px'); 2541 }, 5); 2542 } else { 2543 target 2544 .slideUp(options.slideDuration, function() { 2545 togglerAndTarget 2546 .trigger('afterToggleOff', [thisToggler, target]) 2547 .trigger('afterToggle', [thisToggler, target]); 2548 }) 2549 .removeClass(options.toggledClass); 2550 } 2551 } else { 2552 target.removeClass(options.toggledClass); 2553 2554 togglerAndTarget 2555 .trigger('afterToggleOff', [thisToggler, target]) 2556 .trigger('afterToggle', [thisToggler, target]); 2557 } 2558 2559 return this; 2560 }, 2561 2562 set : function(key, value) { 2563 if(typeof arguments[0] === 'object') { 2564 $.extend(true, options, arguments[0]); 2565 } else { 2566 options[key] = value; 2567 } 2568 return this; 2569 }, 2570 2571 destroy : function() { 2572 this.deactivate(); 2573 togglerAndTarget.removeData(pluginName); 2574 } 2575 }); 2576 2577 thisToggler.data(pluginName).activate(); 2578 2579 /* If need to pretoggle a set, this is the time to do it. */ 2580 if(typeof options.pretoggle === 'number' && index === options.pretoggle) { 2581 thisToggler.addClass(options.toggledClass); 2582 if(options.slide) target.addClass(options.toggledClass); 2583 if(!options.useCSSTransitions) target.show(); 2584 } 2585 }); 2586 }, 2587 2588 activate: function() { 2589 runElemMethod.call(this, pluginName, 'activate', arguments); 2590 }, 2591 2592 deactivate: function() { 2593 runElemMethod.call(this, pluginName, 'deactivate', arguments); 2594 }, 2595 2596 toggleOn: function() { 2597 runElemMethod.call(this, pluginName, 'toggleOn', arguments); 2598 }, 2599 2600 toggleOff: function() { 2601 runElemMethod.call(this, pluginName, 'toggleOff', arguments); 2602 }, 2603 2604 set: function() { 2605 runElemMethod.call(this, pluginName, 'set', arguments); 2606 }, 2607 2608 destroy : function() { 2609 runElemMethod.call(this, pluginName, 'destroy', arguments); 2610 } 2611 }, 2612 _arguments = arguments; 2613 2614 if(typeof _arguments[0] === 'string' && _arguments[0] !== 'create' && typeof methods[_arguments[0]] === 'function') { 2615 methods[_arguments[0]].apply(this, $.makeArray(_arguments).slice(1)); 2616 } else { 2617 for(var i = _arguments.length-1; i > -1; i--) { 2618 if(typeof _arguments[i] === 'object') $.extend(options, _arguments[i]); 2619 } 2620 2621 methods.create.call(this); 2622 } 2623 2624 return this; 2625 /* End togglerify */ 2626 }, 2627 2628 2629 /**---- Validify ----**\ 2630 2631 * Call this `$('form').validify()` on form nodes that require 2632 * validation. 2633 2634 * The reason we are using JS to do validation instead of the 2635 * built-in HTML5 validation attributes is because javascript 2636 * validation is capable of providing real-time feedback, 2637 * whereas HTML5 validation only provides feedback when the user 2638 * tries to submit their form. Additionally, JS validation will 2639 * work in older browsers that do not support HTML5 validation. 2640 2641 * To initialise the field with validation, you must explicitly 2642 * set the `data-validify` attribute on the element, as 2643 * shown below: 2644 2645 (A) <input type="text" data-validify="required: true, pattern: numbers, minlength: 6" /> 2646 (B) <input type="text" data-validify /> 2647 2648 * The field will NOT be initialised by the validation function if 2649 * it doesn't have the custom data attribute. 2650 2651 * On line (A), a comma-separated list of key-value pairs containing 2652 * validation criteria is set as the attribute's value. This 2653 * determines how the field should be validated. If omitted, 2654 * the field is simply initialised with the minimum validation method, 2655 * which checks whether or not the field is empty. 2656 2657 * List of criteria available properties in the JSON Object so far is: 2658 2659 required : true 2660 pattern : numbers 2661 valid : true 2662 minlength : 6 2663 compareWith : #yourOtherInput 2664 noSuccess : true 2665 response : #yourResponseElem 2666 floater : #yourFloaterElem 2667 2668 * It is possible to group fields together using the `data-validify-group` 2669 * attribute. 2670 2671 <fieldset data-validify-group-data="required: true, pattern: numbers"> 2672 (A) <input data-validify /> 2673 (B) <input data-validify="pattern: alphanumeric" /> 2674 </fieldset> 2675 2676 * Any criteria set on the group element will be applied onto the fields 2677 * in the group. However, the criteria set on the fields themselves will 2678 * always take precedence. For example, for (A) above, its criteria 2679 * will be following the set declared on the group, which is 2680 * "required: true, pattern: numbers", while for (B), it will be 2681 * "required: true, pattern: alphanumeric" instead. 2682 2683 * Being in a group also results in slightly different response 2684 * behaviour, whereby the success/failed messages are only shown once 2685 * the entire group of fields have been validated to be successful or 2686 * failed. 2687 2688 * Majority of the useful functions and properties are exposed through 2689 * `$('input').data('validify')`, on both the form element and the field 2690 * elements. You will need to make use of these to determine whether or 2691 * not the form is valid, and then allow users to proceed based on that. 2692 2693 * To add your own regex pattern, scroll down to the function options below 2694 * for more information. 2695 */ 2696 validify : function(_options) { 2697 var pluginName = 'validify', 2698 options = { 2699 selectors : { 2700 all : 'input[data-validify], textarea[data-validify], select[data-validify]', 2701 fields : 'input[data-validify], textarea[data-validify], select[data-validify]', 2702 groups : '[data-validify-group]', 2703 textFields : 'input[type="text"], input[type="tel"], input[type="number"], input[type="email"], input[type="password"], textarea', 2704 checkButtons : 'input[type="radio"], input[type="checkbox"]', 2705 dropdowns : 'select' 2706 }, 2707 2708 classes : [ 2709 { key : 'success', className : 'is-success' }, 2710 { key : 'hovered', className : 'is-hovered' }, 2711 { key : 'focused', className : 'is-focused' }, 2712 { key : 'failed', className : 'is-failed' }, 2713 { key : 'pattern', className : 'is-failed-pattern' }, 2714 { key : 'minlength', className : 'is-failed-minlength' }, 2715 { key : 'required', className : 'is-failed-required' }, 2716 { key : 'compare-with', className : 'is-failed-compare-with' }, 2717 { key : 'minchecked', className : 'is-failed-minchecked' }, 2718 { key : 'custom', className : 'is-failed-cu
2718stom' } 2719 ], 2720 2721 regexes : [ 2722 /** 2723 * To add your own reusable regex pattern, just duplicate 2724 * and modify as needed. 2725 2726 { key : 'your-keyword', pattern : /your-pattern/ } 2727 2728 * Replace `your-keyword` with the desired String value to 2729 * use in the JSON data object, and `/your-pattern/` with 2730 * your desired regex pattern. 2731 2732 * Once added in, you can immediately start using it like below: 2733 2734 <input type="text" data-validify='pattern: your-keyword' /> 2735 2736 **/ 2737 { key : 'name', pattern: /^[^\d<>!@#$%^&*()=+|{}:;"',.`~_\/\\\]]+$/i }, 2738 { key : 'alphabet', pattern: /^[a-z\s]+$/i }, 2739 { key : 'alphanumeric', pattern : /^[a-z0-9\s]+$/i }, 2740 { key : 'email', pattern : /^[^\s@]+@[^\s@]+\.[^\s@]+$/i }, 2741 { key : 'dob', pattern : /^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[1,3-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/ }, 2742 { key : 'numbers', pattern : /^\s*\d+\s*$/ }, 2743 { key : 'phone', pattern : /^[\+\-0-9\s]+$/ }, 2744 { key : 'mobile-number', pattern : /^01(|[\d]+)$/ }, 2745 { key : 'full-mobile-number', pattern : /^601(|[\d]+)$/ }, 2746 { key : 'basic', pattern : /^[^<>]+$/i } 2747 ], 2748 2749 validationTimerDuration : 250, 2750 debug: false 2751 }, 2752 allClasses = (function() { 2753 var tempArr = []; 2754 2755 for(var i = options.classes.length-1; i > -1; i--) { 2756 if(options.classes[i].key === 'focused') continue; 2757 tempArr.push(options.classes[i].className); 2758 } 2759 2760 return tempArr.join(' '); 2761 })(), 2762 methods = { 2763 create: function() { 2764 this.each(function() { 2765 var thisForm = $(this), 2766 allInputs = thisForm.find(options.selectors.all); 2767 2768 thisForm.data(pluginName, { 2769 valid: false, 2770 strict: false, 2771 debug: options.debug, 2772 allInputs: allInputs, 2773 2774 /** 2775 * Force the relevant input nodes in this form to update their 2776 * own validation status, then make the form refresh itself 2777 * to see if it becomes valid based on the latest results. 2778 **/ 2779 validate: function() { 2780 var _this = this; 2781 2782 debugMsg.call(thisForm, [ 2783 ' /*===================================================*\\ ', 2784 ' Updating form validation status ', 2785 ' ------------------------------------------------------- ' 2786 ], 'start'); 2787 2788 debugMsg.call(thisForm, [ 2789 'Form: ', 2790 thisForm, 2791 ' ' 2792 ]); 2793 2794 /*----*/ 2795 2796 allInputs.each(function() { 2797 $(this).data(pluginName).validate(); 2798 }); 2799 2800 _this.refresh();
2801 2802 /*----*/ 2803 2804 debugMsg.call(thisForm, 'Form validation: ' + _this.valid); 2805 2806 debugMsg.call(thisForm, [ 2807 ' ------------------------------------------------------- ', 2808 ' Form validation status update complete ', 2809 ' \\*===================================================*/ ', 2810 '' 2811 ], 'end'); 2812 }, 2813 2814 /** 2815 * Check the form's validation status without forcing each 2816 * of its input children to update itself. 2817 * 2818 * This is used after an input element updates its own 2819 * status, so that we can see whether or not the form 2820 * becomes valid. 2821 **/ 2822 refresh: function() { 2823 var _this = this; 2824 2825 _this.valid = true; 2826 2827 allInputs.each(function() { 2828 if($(this).data(pluginName).valid === false) { 2829 _this.valid = false; 2830 return false; 2831 } 2832 }); 2833 }, 2834 2835 set: function(key, value) { 2836 this[key] = value; 2837 return this; 2838 }, 2839 2840 destroy: function() { 2841 runElemMethod.call(this.allInputs, pluginName, 'destroy'); 2842 thisForm.removeData(pluginName); 2843 } 2844 }); 2845 2846 if(thisForm.is('form')) { 2847 thisForm.off('.' + pluginName).on('reset.' + pluginName, function() { 2848 setTimeout(thisForm.data(pluginName).validate, 100); 2849 }); 2850 } 2851 2852 allInputs.each(function(index) { 2853 var thisInput = $(this), 2854 validifyData = typeCast(convertCSVtoObject(thisInput.data(pluginName))), 2855 inputPluginObj = { 2856 group : thisInput.closest('[data-validify-group]') 2857 }, 2858 triggerEvents; 2859 2860 inputPluginObj.isInHolder = Boolean(thisInput.parent('.field-holder').length); 2861 2862 /* If the input is in a group, get the entire group's data first */
2863 if(thisObjectExists(inputPluginObj.group)) { 2864 var nestedGroupInputs = inputPluginObj.group.find(options.selectors.groups).find(options.selectors.all), 2865 groupValidifyData = typeCast(convertCSVtoObject(inputPluginObj.group.data(pluginName + '-group'))); 2866 2867 if(thisIsJSON(groupValidifyData)) { 2868 if(typeof groupValidifyData === 'string') { 2869 $.extend(inputPluginObj, JSON.parse(groupValidifyData)); 2870 } else { 2871 $.extend(inputPluginObj, groupValidifyData); 2872 } 2873 } 2874 2875 inputPluginObj.groupInputs = inputPluginObj.group.find(options.selectors.all).not(nestedGroupInputs); 2876 } 2877 2878 /* If the input itself has data, then get it and overwrite the data 2879 * inferred from the group */ 2880 if(thisIsJSON(validifyData)) { 2881 if(typeof validifyData === 'string') { 2882 $.extend(true, inputPluginObj, JSON.parse(validifyData)); 2883 } else { 2884 $.extend(true, inputPluginObj, validifyData); 2885 } 2886 } 2887 2888 if(thisInput.data('validify-pattern')) { 2889 inputPluginObj.pattern = thisInput.data('validify-pattern'); 2890 } 2891 2892 if(typeof inputPluginObj.pattern === 'undefined') { 2893 if(thisInput.attr('type') === 'email') { 2894 inputPluginObj.pattern = 'email'; 2895 } else { 2896 inputPluginObj.pattern = 'basic'; 2897 } 2898 } 2899 2900 /* If an ID to compare with is provided, then select the element and 2901 * store it in the data object. */ 2902 if(typeof inputPluginObj.compareWith === 'string') { 2903 inputPluginObj.$compareWith = $(inputPluginObj.compareWith); 2904 } 2905 2906 /* Similar to above: if an ID to respond with is provided, then 2907 * select the element and store it in the data object. */ 2908 if(typeof inputPluginObj.response === 'string') { 2909 inputPluginObj.$response = $(inputPluginObj.response); 2910 } 2911 2912 /* Similar to above. */ 2913 if(typeof inputPluginObj.groupResponse === 'string') { 2914 inputPluginObj.$groupResponse = $(inputPluginObj.groupResponse); 2915 } 2916 2917 /* Similar to above, but with additional methods for the floater. */ 2918 if(typeof inputPluginObj.floater === 'string') { 2919 inputPluginObj.$floater = $(inputPluginObj.floater); 2920 2921 if(inputPluginObj.$floater.length) { 2922 inputPluginObj.$floater.data('floater', { 2923 $popup : inputPluginObj.$floater.closest('.popup') 2924 }); 2925 2926 if(inputPluginObj.$floater.data('floater').$popup.length) { 2927 inputPluginObj.$floater.appendTo($('#mainPopupHolder')); 2928 } else { 2929 inputPluginObj.$floater.appendTo('body'); 2930 } 2931 2932 $.extend(inputPluginObj.$floater.data('floater'), { 2933 refresh : function() { 2934 var topPos = thisInput.offset().top - inputPluginObj.$floater.outerHeight(); 2935 if(this.$popup.length) topPos += $('#mainPopupHolder').scrollTop() - $(window).scrollTop(); 2936 2937 inputPluginObj.$floater 2938 .off(FE.events.transitionEnd) 2939 .css({ 2940 width : thisInput.outerWidth() + 'px', 2941 marginTop : '', 2942 top : topPos + 'px', 2943 left : thisInput.offset().left + 'px' 2944 }) 2945 .addClass('is-shown'); 2946 2947 return this; 2948 }, 2949 2950 reveal : function() { 2951 var self = this; 2952 2953 self.refresh();
2954 inputPluginObj.$floater.addClass('is-shown'); 2955 2956 $(window) 2957 .off('.' + inputPluginObj.eventID) 2958 //.off(FE.events.transitionEnd) 2959 .on('resize.' + pluginName + '.' + inputPluginObj.eventID, homebrew.utils.throttle(function() { 2960 self.refresh(); 2961 }, 100)); 2962 2963 return self; 2964 }, 2965 2966 conceal : function() { 2967 /*inputPluginObj.$floater 2968 .on(FE.events.transitionEnd, function() { 2969 inputPluginObj.$floater 2970 .off(FE.events.transitionEnd) 2971 .css({ 2972 top : '', 2973 left : '' 2974 }); 2975 }) 2976 .removeClass('is-shown') 2977 .css('margin-top', '');*/ 2978 2979 inputPluginObj.$floater 2980 .removeClass('is-shown') 2981 .css({ 2982 marginTop : '', 2983 top : '', 2984 left : '' 2985 }); 2986 2987 $(window).off('resize.' + inputPluginObj.eventID); 2988 2989 return this; 2990 } 2991 }); 2992 } else { 2993 debugMsg.call(thisForm, 'A floater ID `' + inputPluginObj.floater + '` has been provided, but selection returns nothing.', 'error'); 2994 inputPluginObj.$floater = undefined; 2995 } 2996 } 2997 2998 /** 2999 * Adjust what event to attach the handler to, depending on 3000 * what kind of element we're currently iterating over. 3001 **/ 3002 if(thisInput.is(options.selectors.checkButtons)) { 3003 triggerEvents = 'change.' + pluginName; 3004 } else if(thisInput.is(options.selectors.textFields)) { 3005 triggerEvents = ['input.' + pluginName, 'blur.' + pluginName].join(' '); 3006 } else if(thisInput.is(options.selectors.dropdowns)) { 3007 triggerEvents = ['change.' + pluginName, 'blur.' + pluginName].join(' '); 3008 } 3009 3010 thisInput.data(pluginName, $.extend(validifyData, { 3011 eventID : new Date().getTime() + index, 3012 parentForm: thisForm, 3013 3014 validate: function() { 3015 var _this = this; 3016 debugMsg.call(thisForm, thisInput); 3017 3018 $.extend(_this, validateInput(thisInput, thisForm)); 3019 reflectValidity(thisInput, thisForm); 3020 3021 debugMsg.call(thisForm, ' '); 3022 3023 thisInput.trigger('validate'); 3024 thisForm.data(pluginName).refresh();
3025 3026 return _this; 3027 }, 3028 3029 activate: function() { 3030 var _this = this; 3031 3032 _this.deactivate(); 3033 3034 thisInput.on(triggerEvents, function() { 3035 clearTimeout(_this.timer); 3036 3037 /** 3038 * validate() is run in an anonymous function, so that the 3039 * scope of the `this` keyword in the validate() function 3040 * will be preserved as the input plugin object. 3041 * 3042 * If you were to assign the function straight to the timeout 3043 * by using setTimeout(_this.validate, 250), the `this` 3044 * keyword becomes the Window object, which is NOT what we want. 3045 */ 3046 _this.timer = setTimeout(function() { 3047 if(thisInput.attr('type') === 'radio' || thisInput.attr('type') === 'checkbox') { 3048 allInputs.filter('[name="' + thisInput.attr('name') + '"]').each(function() { 3049 $(this).data('validify').validate(); 3050 }); 3051 } else { 3052 _this.validate(); 3053 } 3054 }, options.validationTimerDuration); 3055 }); 3056 3057 if(thisObjectExists(_this.$response)) { 3058 /* Trigger focus state on the response container. */ 3059 thisInput.on('focus.' + pluginName, function() { 3060 _this.$response.addClass(getClassFromString('focused')); 3061 }); 3062 3063 thisInput.on('blur.' + pluginName, function() { 3064 _this.$response.removeClass(getClassFromString('focused')); 3065 }); 3066 } 3067 3068 if(thisObjectExists(_this.$groupResponse)) { 3069 /* Trigger focus state on the response container. */ 3070 thisInput.on('focus.' + pluginName, function() { 3071 _this.$groupResponse.addClass(getClassFromString('focused')); 3072 }); 3073 3074 thisInput.on('blur.' + pluginName, function() { 3075 _this.$groupResponse.removeClass(getClassFromString('focused')); 3076 }); 3077 } 3078 3079 if(thisObjectExists(_this.$compareWith)) { 3080 /* Make sure that when the target node has 3081 * changed, we revalidate this node again. */ 3082 _this.$compareWith.on('validate.' + pluginName, function() { 3083 _this.validate(); 3084 }); 3085 } 3086 3087 if(thisObjectExists(_this.$floater)) { 3088 thisInput 3089 .on('focus.' + pluginName, function() { 3090 _this.$floater.addClass(getClassFromString('focused')); 3091 toggleFloater(_this); 3092 }) 3093 .on('blur.' + pluginName, function() { 3094 _this.$floater 3095 .removeClass(getClassFromString('focused')) 3096 .data('floater').conceal(); 3097 }); 3098 3099 var mouseTarget = (_this.isInHolder) ? thisInput.parent('.field-holder') : thisInput; 3100 3101 mouseTarget
3102 .on('mouseenter.' + pluginName, function() { 3103 _this.$floater.addClass(getClassFromString('hovered')); 3104 toggleFloater(_this); 3105 }) 3106 .on('mouseleave.' + pluginName, function() { 3107 _this.$floater.removeClass(getClassFromString('hovered')); 3108 if(_this.$floater.hasClass(getClassFromString('focused'))) return; 3109 _this.$floater.data('floater').conceal(); 3110 }); 3111 } 3112 3113 return _this; 3114 }, 3115 3116 deactivate: function() { 3117 var _this = this; 3118 3119 thisInput.off('.' + pluginName); 3120 3121 if(thisObjectExists(_this.$compareWith)) { 3122 _this.$compareWith.off('validate.' + pluginName); 3123 } 3124 3125 return _this; 3126 }, 3127 3128 forceValidate: function() { 3129 this.set('valid', true).set('error', false); 3130 clearTimeout(this.timer); 3131 reflectValidity(thisInput, thisForm); 3132 return this; 3133 }, 3134 3135 forceInvalidate: function(errorCode) { 3136 this.set('valid', false).set('error', errorCode); 3137 clearTimeout(this.timer); 3138 reflectValidity(thisInput, thisForm); 3139 return this; 3140 }, 3141 3142 forceNeutral: function() { 3143 this.set('valid', 'neutral').set('error', false); 3144 clearTimeout(this.timer); 3145 reflectValidity(thisInput, thisForm); 3146 return this; 3147 }, 3148 3149 excludeFromValidation: function() { 3150 return this.set('required', false); 3151 }, 3152 3153 includeInValidation: function() { 3154 return this.set('required', true); 3155 }, 3156 3157 destroy: function() { 3158 thisInput.removeData(pluginName); 3159 }, 3160 3161 set: function(key, value) { 3162 this[key] = value; 3163 return this; 3164 } 3165 }, inputPluginObj)); 3166 }); 3167 3168 allInputs.each(function() { 3169 var thisInput = $(this), 3170 dataObj = thisInput.data(pluginName); 3171 3172 if(typeof dataObj.valid === 'undefined' || dataObj.valid === null || dataObj.valid === 'neutral') { 3173 dataObj.validate(); 3174 } else { 3175 dataObj.originalState = { 3176 val : thisInput.val(), 3177 valid : dataObj.valid, 3178 error : dataObj.error 3179 }; 3180 } 3181 3182 reflectValidity(thisInput, thisForm); 3183 3184 dataObj.activate(); 3185 }); 3186 }); 3187 }, 3188 3189 activate: function() { 3190 runElemMethod.call(this, pluginName, 'activate'); 3191 }, 3192 3193 deactivate: function() { 3194 runElemMethod.call(this, pluginName, 'deactivate'); 3195 }, 3196 3197 forceValidate: function() { 3198 runElemMethod.call(this, pluginName, 'forceValidate'); 3199 }, 3200 3201 forceInvalidate: function() { 3202 runElemMethod.call(this, pluginName, 'forceInvalidate'); 3203 }, 3204 3205 forceNeutral: function() { 3206 runElemMethod.call(this, pluginName, 'forceNeutral'); 3207 }, 3208 3209 excludeFromValidation: function() { 3210 runElemMethod.call(this, pluginName, 'excludeFromValidation'); 3211 }, 3212 3213 includeInValidation: function() { 3214 runElemMethod.call(this, pluginName, 'includeInValidation'); 3215 }, 3216 3217 destroy: function() { 3218 runElemMethod.call(this, pluginName, 'destroy'); 3219 }, 3220 3221 set: function() { 3222 runElemMethod.call(this, pluginName, 'set', arguments); 3223 } 3224 }, 3225 _arguments = arguments; 3226 3227 for(var i = _arguments.length-1; i > -1; i--) { 3228 if(typeof _arguments[i] === 'object') $.extend(options, _arguments[i]); 3229 } 3230 3231 if(typeof _arguments[0] === 'string' && typeof methods[_arguments[0]] === 'function') { 3232 methods[_arguments[0]].call(this); 3233 } else { 3234 methods.create.call(this); 3235 } 3236 3237 function validateInput(targetInput, targetForm) { 3238 var dataObj = targetInput.data(pluginName), 3239 formDataObj = targetForm.data(pluginName), 3240 inputValue = targetInput.val(); 3241 3242 if(targetInput.is(options.selectors.checkButtons)) { 3243 debugMsg.call(targetForm, 'Input is a :checked element (radio button / checkbox.)'); 3244 3245 /** 3246 * If the node is in a group, then see if we need to 3247 * validate it any differently. 3248 */ 3249 if(thisObjectExists(dataObj.group)) { 3250 debugMsg.call(targetForm, ':checked element is in a group.'); 3251 3252 var inputName = targetInput.attr('name'), 3253 inputGroup = dataObj.groupInputs.filter('[name="' + inputName + '"]'), 3254 minChecked = dataObj.minChecked, 3255 maxChecked = dataObj.maxChecked; 3256 3257 /**
3258 * If a maximum amount of checked nodes has been 3259 * specified, then disable the unchecked boxes when 3260 * the limit is reached. 3261 */ 3262 var maxChecked = getDataAttr(dataObj.group, 'validify-max-checked'); 3263 3264 if(typeof maxChecked === 'number') { 3265 targetInput.on('change.' + pluginName, function() { 3266 var isMaxed = (inputGroup.filter(':checked').length >= maxChecked); 3267 3268 inputGroup.not(':checked').prop('disabled', isMaxed); 3269 }); 3270 } 3271 3272 /** 3273 * If a minimum amount is specified, then see how 3274 * many checked nodes are there. If it doesn't meet 3275 * the requirements, then immediately fail this input. 3276 */ 3277 if(typeof minChecked === 'number') { 3278 debugMsg.call(targetForm, [ 3279 indentStr(0) + 'The group requires at least ' + minChecked + ':checked elements.', 3280 indentStr(1) + 'Number of :checked elements in the group: ' + inputGroup.filter(':checked').length 3281 ]); 3282 3283 if(inputGroup.filter(':checked').length < minChecked) { 3284 debugMsg.call(targetForm, indentStr(2) + 'Group does not meet the minimum number of :checked elements.'); 3285 3286 if(formDataObj.strict) { 3287 debugMsg.call(targetForm, indentStr(3) + 'Strict mode on. Input fails validation.', 'error'); 3288 return { valid: false, error: 'min-checked' }; 3289 } else { 3290 debugMsg.call(targetForm, indentStr(3) + 'Strict mode off. Input is in neutral validation.', 'neutral'); 3291 return { valid: 'neutral', error: false }; 3292 } 3293 } else { 3294 debugMsg.call(targetForm, indentStr(2) + 'Group meets the minimm number of :checked elements.'); 3295 } 3296 } 3297 } 3298 3299 if(!targetInput.is(':checked')) { 3300 debugMsg.call(targetForm, [ 3301 ":checked element isn't checked.", 3302 indentStr(0) + 'Validation type: required.' 3303 ]); 3304 3305 if(typeof dataObj.required === 'boolean' && dataObj.required === true) { 3306 debugMsg.call(targetForm, indentStr(1) + ":checked element is required."); 3307 3308 debugMsg.call(targetForm, indentStr(2) + 'Check to see if any other buttons in its group has been checked.'); 3309 3310 if(targetForm.data('validify').allInputs.filter('[name="' + targetInput.attr('name') + '"]').is(':checked')) { 3311 debugMsg.call(targetForm, indentStr(3) + 'There is a checked input in the group. Validation passes.', 'success'); 3312 return { valid: true, error: false }; 3313 } else { 3314 debugMsg.call(targetForm, indentStr(3) + 'There are no checked inputs in the group.'); 3315 3316 if(formDataObj.strict) { 3317 debugMsg.call(targetForm, indentStr(4) + 'Strict mode on; Validation fails.', 'error'); 3318 return { valid: false, error: 'required' }; 3319 } else { 3320 debugMsg.call(targetForm, indentStr(4) + 'Strict mode off; node is in neutral validation.', 'neutral'); 3321 return { valid: 'neutral', error: false }; 3322 } 3323 } 3324 } else { 3325 debugMsg.call(targetForm, indentStr(1) + ":checked element isn't required."); 3326 debugMsg.call(targetForm, indentStr(2) + "Neutral validation.", 'neutral'); 3327 3328 return { valid: 'neutral', error: false }; 3329 } 3330 } 3331 3332 debugMsg.call(targetForm, ":checked element is checked."); 3333 } else { 3334 if(typeof dataObj.originalState !== 'undefined') { 3335 debugMsg.call(targetForm, "Input has an original state cached, checking to see if its value matches its original state."); 3336 if(inputValue === dataObj.originalState.val) { 3337 debugMsg.call(targetForm, indentStr(1) + "Value matches its original state. Returning to how it was."); 3338 return { valid: dataObj.originalState.valid, error: dataObj.originalState.error }; 3339 } 3340 } 3341 3342 if(inputValue === '') { 3343 debugMsg.call(targetForm, [ 3344 "Input is empty.", 3345 indentStr(0) + 'Validation type: required.' 3346 ]); 3347 3348 /** 3349 * If required, and field is blank, it should only fail validation 3350 * when the form is about to be submitted. Otherwise, it should 3351 * just pass off as neutral. This is because when the user is still 3352 * filling the form, it is likely that they are aware of the required 3353 * field being empty. 3354 * 3355 * `Strict Mode` is enabled when you call `.set('strict', true)` on the form 3356 * data object. 3357 */ 3358 if(typeof dataObj.required === 'boolean' && dataObj.required === true) { 3359 if(formDataObj.strict) { 3360 debugMsg.call(targetForm, indentStr(1) + 'Input is required. Strict mode on; input has failed validation', 'error'); 3361 return { valid: false, error: 'required' }; 3362 } else { 3363 debugMsg.call(targetForm, indentStr(1) + 'Input is required. Strict mode off; input is in neutral validation', 'neutral'); 3364 return { valid: 'neutral', error: false }; 3365 } 3366 } else { 3367 /* If not required, then the blank field should pass as neutral validation. */ 3368 debugMsg.call(targetForm, indentStr(1) + "Input isn't required. Neutral validation.", 'neutral'); 3369 return { valid: 'neutral', error: false }; 3370 } 3371 } else if(thisObjectExists(dataObj.$compareWith)) { 3372 debugMsg.call(targetForm, "Input needs to be compared to: " + dataObj.compareWith); 3373 3374 var targetComparison = dataObj.$compareWith; 3375 /** 3376 * I believe it is beneficial to be very strict with 3377 * comparison nodes: if the script can't find the 3378 * desired node to compare to, then throw an error 3379 * so that we will know that this needs to be fixed. 3380 */ 3381 if(targetComparison.length < 1) { 3382 throw new Error('$.validify(): A target for comparison has been provided, but selection has returned with
3382nothing. Please check your spelling, or make sure both this input and the target comparison input exist on the same page at the same time.'); 3383 } 3384 3385 if(targetComparison.data(pluginName).valid !== true) { 3386 debugMsg.call(targetForm, indentStr(0) + "The input to compare with hasn't passed validation yet. Neutral validation.", 'neutral'); 3387 return { valid: 'neutral', error: false }; 3388 } else if(targetInput.val() !== targetComparison.val()) { 3389 debugMsg.call(targetForm, indentStr(0) + "Comparison failed. Input has failed validation.", 'error'); 3390 return { valid: false, error: 'compare-with' }; 3391 } 3392 /* If the input is NOT validated through a comparison, 3393 * then we proceed to attach the regular validation. */ 3394 } else { 3395 debugMsg.call(targetForm, "Input isn't empty."); 3396 3397 /** 3398 * If a pattern has been provided, then we will need to 3399 * test it against the corresponding regex pattern. 3400 * 3401 * If the pattern value is a string, then we will obtain 3402 * the regex pattern from the function's default options. 3403 * Otherwise, if the pattern value is an object, then 3404 * we simply test with it. 3405 */ 3406 if(typeof dataObj.pattern === 'string') { 3407 debugMsg.call(targetForm, [ 3408 indentStr(0) + 'Validation type: pattern.', 3409 indentStr(1) + 'Pattern provided: ' + dataObj.pattern 3410 ]); 3411 3412 var regExp; 3413 3414 regExp = getRegexFromString(dataObj.pattern); 3415 3416 if(typeof regExp === 'string') { 3417 var patternArray = dataObj.pattern.split('/'); 3418 3419 if(patternArray.length > 2) { 3420 regExp = new RegExp(patternArray[1], patternArray[2]); 3421 } else { 3422 regExp = new RegExp(dataObj.pattern); 3423 } 3424 } 3425 3426 if(typeof regExp === 'object') { 3427 if(regExp.test(inputValue)) { 3428 debugMsg.call(targetForm, indentStr(1) + "Input value matches the requierd pattern.", 'success'); 3429 } else { 3430 debugMsg.call(targetForm, indentStr(1) + "Input value doesn't match the required pattern. Input has failed validation.", 'error'); 3431 return { valid: false, error: 'pattern' }; 3432 } 3433 } else { 3434 debugMsg.call(targetForm, indentStr(1) + "A pattern has been provided, but it doesn't seem to result in a valid RegExp object. Please check again.", 'error'); 3435 } 3436 } 3437 3438 /** 3439 * Similar to above, this should only fail validation 3440 * when the form is about to be submitted, hence the 3441 * use of `Strict Mode`. 3442 */ 3443 if(typeof dataObj.minlength === 'number') { 3444 debugMsg.call(targetForm, indentStr(0) + 'Validation type: minlength.'); 3445 3446 var minlengthNum = parseInt(dataObj.minlength, 10); 3447 3448 if(inputValue.length < minlengthNum) { 3449 if(formDataObj.strict) { 3450 debugMsg.call(targetForm, indentStr(1) + 'Input value hasn\'t reached the minimum length. Strict mode on; input has failed validation.', 'error'); 3451 return { valid: false, error: 'minlength' }; 3452 } else { 3453 debugMsg.call(targetForm, indentStr(1) + 'Input value hasn\'t reached the minimum length. Strict mode off; input is in neutral validation.', 'neutral'); 3454 return { valid: 'neutral', error: false }; 3455 } 3456 } else { 3457 debugMsg.call(targetForm, indentStr(1) + "Input value has reached the minimum length."); 3458 } 3459 } 3460 } 3461 } 3462 3463 debugMsg.call(targetForm, 'Input has passed validation.', 'success'); 3464 return { valid: true, error: false }; 3465 } 3466 3467 function reflectValidity(targetInput, targetForm) { 3468 var dataObj = targetInput.data(pluginName), 3469 isInGroup = thisObjectExists(dataObj.group), 3470 $response = $(dataObj.$response), 3471 $floater = $(dataObj.$floater), 3472 $groupResponse = $(dataObj.$groupResponse); 3473 3474 targetInput.add($response).add($floater).add($groupResponse).removeClass(allClasses); 3475 3476 if(isInGroup) { 3477 var groupStatus = true; 3478 3479 debugMsg.call(targetForm, 'Input is in a group. Checking through all inputs in the group.'); 3480 3481 dataObj.groupInputs.each(function() { 3482 var thisInput = $(this), 3483 thisDataObj = thisInput.data(pluginName), 3484 $validifyResponses = thisInput.add($(thisDataObj.$response)).add($(thisDataObj.$floater)), 3485 thisInputStatus = thisDataObj.valid, 3486 thisIsCheckInput = (targetInput.is(options.selectors.checkButtons)); 3487 3488 if(thisInputStatus === true) { 3489 if(thisIsCheckInput && !thisInput.is(':checked')) { 3490 $validifyResponses.removeClass(getClassFromString('success')); 3491 return; 3492 } 3493 $validifyResponses.addClass(getClassFromString('success')); 3494 } else if(thisInputStatus === false) { 3495 $validifyResponses.addClass(getClassFromString('failed')); 3496 3497 if(typeof thisDataObj.error === 'string') { 3498 $validifyResponses.add($groupResponse).addClass(getClassFromString(thisDataObj.error)); 3499 } 3500 3501 groupStatus = false; 3502 } else if(typeof thisInputStatus === 'undefined' || thisInputStatus === 'neutral') { 3503 if(groupStatus !== false) groupStatus = 'neutral'; 3504 } 3505 3506 toggleFloater.call(thisInput, thisDataObj); 3507 }); 3508 3509 if(groupStatus === true) { 3510 debugMsg.call(targetForm, indentStr(0) + 'Input group has passed validation. Applying success classes onto the group response element.', 'success'); 3511 $groupResponse.addClass(getClassFromString('success')); 3512 } else if(groupStatus === false) { 3513 debugMsg.call(targetForm, indentStr(0) + 'Input group has failed validation. Applying failed classes onto the group response element.', 'error'); 3514 $groupResponse.addClass(getClassFromString('failed')); 3515 } else if(groupStatus === 'neutral') { 3516 debugMsg.call(targetForm, indentStr(0) + 'Input group has neutral validation. Group response element will not be modified.', 'neutral'); 3517 } 3518 } else { 3519 var $validifyResponses = targetInput.add($response).add($floater); 3520 3521 if(dataObj.valid === false) { 3522 $validifyResponses.addClass(getClassFromString('failed')); 3523 3524 if(typeof dataObj.error === 'string') { 3525 $validifyResponses.addClass(getClassFromString(dataObj.error)); 3526 } 3527 } else if(dataObj.valid === true) { 3528 if(dataObj.noSuccess) { 3529 debugMsg.call(targetForm, 'Input has `noSuccess` set to true. Leaving response in its neutral state.', 'neutral'); 3530 } else { 3531 $validifyResponses.addClass(getClassFromString('success')); 3532 } 3533 } 3534 3535 toggleFloater.call(targetInput, dataObj); 3536 } 3537 } 3538 3539 function getRegexFromString(string) { 3540 /* Helper function to help us translate the pattern 3541 * attribute's string value into a regex object. */ 3542 for(var i = options.regexes.length-1; i > -1; i--) { 3543 if(string === options.regexes[i].key) return options.regexes[i].pattern; 3544 } 3545 return ''; 3546 } 3547 3548 function getClassFromString(string) { 3549 /* Helper function to help us translate the status 3550 * string value into its corresponding class. */ 3551 var returnClass; 3552 for(var i = options.classes.length-1; i > -1; i--) { 3553 if(string === options.classes[i].key) return options.classes[i].className; 3554 } 3555 return ''; 3556 } 3557 3558 function getDataAttr(target, key) { 3559 if(typeof target === 'undefined' || target === null) return; 3560 3561 return target.data(key, null).data(key); 3562 } 3563 3564 function toggleFloater(dataObj) { 3565 var $theFloater = dataObj.$floater; 3566 if(typeof $theFloater === 'undefined') return; 3567 3568 if($theFloater.hasClass(getClassFromString('failed'))) { 3569 if($theFloater.hasClass(getClassFromString('focused')) || $theFloater.hasClass(getClassFromString('hovered'))) { 3570 $theFloater.data('floater').reveal(); 3571 } 3572 } else { 3573 $theFloater.data('floater').conceal(); 3574 } 3575 } 3576 3577 function indentStr(levels) { 3578 var indentationStrArr = []; 3579 3580 if(typeof levels === 'number' && levels > 0) { 3581 for(var i = 0, ii = levels; i < ii; i++) { 3582 indentationStrArr.push(' '); 3583 } 3584 } 3585 3586 indentationStrArr.push('`- '); 3587 3588 return indentationStrArr.join(''); 3589 } 3590 3591 function thisIsJSON(value) { 3592 if(typeof value === 'string') { 3593 return (value.substr(0,1) === '{' && value.substr(value.length-1, value.length) === '}'); 3594 } else { 3595 return (typeof value === 'object' && value !== null && value !== undefined); 3596 } 3597 } 3598 3599 /* Helper function to show console logs only when 3600 * debug mode is set to true. */ 3601 function debugMsg(message, styles) { 3602 if(!this.data(pluginName).debug) return; 3603 3604 switch(styles) { 3605 case 'error': 3606 styles = 'color: #ff8201;'; 3607 break; 3608 3609 case 'neutral': 3610 styles = 'color: #999;'; 3611 break; 3612 3613 case 'success': 3614 styles = 'color: #5cb21d;'; 3615 break; 3616 3617 case 'start': 3618 styles = 'background-color: #6b3269; color: #fff;'; 3619 break; 3620 3621 case 'end': 3622 styles = 'background-color: #444; color: #fff;'; 3623 break; 3624 3625 default: 3626 styles = ''; 3627 break; 3628 } 3629 3630 if(message instanceof Array) { 3631 while(message.length) { 3632 if(typeof message[0] === 'string') { 3633 console.log('%c' + message[0], styles); 3634 } else { 3635 console.log(message[0]); 3636 } 3637 message.shift(); 3638 } 3639 } else { 3640 if(typeof message[0] === 'string') { 3641 console.log('%c' + message, styles); 3642 } else { 3643 console.log(message); 3644 } 3645 } 3646 } 3647 3648 function typeCast(keyValueObj) { 3649 $.each(keyValueObj, function(key, value) { 3650 switch(key) { 3651 case 'valid': 3652 case 'required': 3653 keyValueObj[key] = (value === 'true') ? true : false; 3654 break; 3655 3656 case 'minlength': 3657 case 'maxlength': 3658 case 'minchecked': 3659 case 'maxchecked': 3660 keyValueObj[key] = parseInt(value, 10); 3661 break; 3662 } 3663 }); 3664 3665 return keyValueObj; 3666 } 3667 3668 return this; 3669 3670 /* Removes all data and events related to validify. */ 3671 /* function destroyValidify() { 3672 forms.each(function() { 3673 var form = $(this), 3674 allInputs = thisForm.find(options.selectors.all); 3675 3676 allInputs.each(function() { 3677 var currentInput = $(this); 3678 3679 currentInput 3680 .off('.validify') 3681 .removeData('validify-status') 3682 .removeClass(allClasses) 3683 .prop('disabled', false); // For disabled checkboxes 3684 3685 $('#' + currentInput.data('validify-response-id')).removeClass(allClasses); 3686 }); 3687 3688 thisForm.find('fieldset').each(function() { 3689 $('#' + $(this).data('validify-response-id')).removeClass(allClasses); 3690 }); 3691 3692 thisForm.off('.validify'); 3693 }); 3694 } */ 3695 /* End validify */ 3696 } 3697 });
3698 3699 function runElemMethod(pluginName, methodName, _args) { 3700 this.each(function() { 3701 var $this = $(this), 3702 pluginData = $this.data(pluginName); 3703 3704 if(typeof pluginData === 'object' 3705 && typeof pluginData[methodName] === 'function') { 3706 pluginData[methodName].apply(pluginData, _args); 3707 } 3708 }); 3709 } 3710 3711 function thisObjectExists(target) { 3712 return (typeof target === 'object' && target !== null && target.length); 3713 } 3714 3715 function thisDataIsValidObject($elem, dataName) { 3716 var elemData = $elem.data(dataName); 3717 return (typeof elemData === 'object' && elemData !== null && elemData !== undefined); 3718 } 3719 3720 function convertCSVtoObject(csv) { 3721 if(csv === '') return {}; 3722 3723 var splitArray = csv.split(','), 3724 keyValuesArray = [], 3725 keyValuesObj = {}; 3726 3727 while(splitArray.length) { 3728 keyValuesArray.push($.trim(splitArray[0]).split(':')); 3729 splitArray.shift(); 3730 } 3731 3732 while(keyValuesArray.length) { 3733 keyValuesObj[$.trim(keyValuesArray[0][0])] = $.trim(keyValuesArray[0][1]); 3734 keyValuesArray.shift(); 3735 } 3736 3737 return keyValuesObj; 3738 } 3739})(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.