kube.js (66525B)
1 (function ($) { 2 /* 3 Kube. CSS & JS Framework 4 Version 6.5.2 5 Updated: February 2, 2017 6 7 http://imperavi.com/kube/ 8 9 Copyright (c) 2009-2017, Imperavi LLC. 10 License: MIT 11 */ 12 if (typeof jQuery === 'undefined') { 13 throw new Error('Kube\'s requires jQuery') 14 } 15 16 ;(function ($) { 17 var version = $.fn.jquery.split('.'); 18 if (version[0] == 1 && version[1] < 8) { 19 throw new Error('Kube\'s requires at least jQuery v1.8'); 20 } 21 })(jQuery); 22 23 ;(function () { 24 // Inherits 25 Function.prototype.inherits = function (parent) { 26 var F = function () { 27 }; 28 F.prototype = parent.prototype; 29 var f = new F(); 30 31 for (var prop in this.prototype) f[prop] = this.prototype[prop]; 32 this.prototype = f; 33 this.prototype.super = parent.prototype; 34 }; 35 36 // Core Class 37 var Kube = function (element, options) { 38 options = (typeof options === 'object') ? options : {}; 39 40 this.$element = $(element); 41 this.opts = $.extend(true, this.defaults, $.fn[this.namespace].options, this.$element.data(), options); 42 this.$target = (typeof this.opts.target === 'string') ? $(this.opts.target) : null; 43 }; 44 45 // Core Functionality 46 Kube.prototype = { 47 getInstance: function () { 48 return this.$element.data('fn.' + this.namespace); 49 }, 50 hasTarget: function () { 51 return !(this.$target === null); 52 }, 53 callback: function (type) { 54 var args = [].slice.call(arguments).splice(1); 55 56 // on element callback 57 if (this.$element) { 58 args = this._fireCallback($._data(this.$element[0], 'events'), type, this.namespace, args); 59 } 60 61 // on target callback 62 if (this.$target) { 63 args = this._fireCallback($._data(this.$target[0], 'events'), type, this.namespace, args); 64 } 65 66 // opts callback 67 if (this.opts && this.opts.callbacks && $.isFunction(this.opts.callbacks[type])) { 68 return this.opts.callbacks[type].apply(this, args); 69 } 70 71 return args; 72 }, 73 _fireCallback: function (events, type, eventNamespace, args) { 74 if (events && typeof events[type] !== 'undefined') { 75 var len = events[type].length; 76 for (var i = 0; i < len; i++) { 77 var namespace = events[type][i].namespace; 78 if (namespace === eventNamespace) { 79 var value = events[type][i].handler.apply(this, args); 80 } 81 } 82 } 83 84 return (typeof value === 'undefined') ? args : value; 85 } 86 }; 87 88 // Scope 89 window.MaterialisKube = Kube; 90 91 })(); 92 /** 93 * @library Kube Plugin 94 * @author Imperavi LLC 95 * @license MIT 96 */ 97 98 var Kube = window.MaterialisKube; 99 (function (Kube) { 100 Kube.Plugin = { 101 create: function (classname, pluginname) { 102 pluginname = (typeof pluginname === 'undefined') ? classname.toLowerCase() : pluginname; 103 104 $.fn[pluginname] = function (method, options) { 105 var args = Array.prototype.slice.call(arguments, 1); 106 var name = 'fn.' + pluginname; 107 var val = []; 108 109 this.each(function () { 110 var $this = $(this), data = $this.data(name); 111 options = (typeof method === 'object') ? method : options; 112 113 if (!data) { 114 // Initialization 115 $this.data(name, {}); 116 $this.data(name, (data = new Kube[classname](this, options))); 117 } 118 119 // Call methods 120 if (typeof method === 'string') { 121 if ($.isFunction(data[method])) { 122 var methodVal = data[method].apply(data, args); 123 if (methodVal !== undefined) { 124 val.push(methodVal); 125 } 126 } 127 else { 128 $.error('No such method "' + method + '" for ' + classname); 129 } 130 } 131 132 }); 133 134 return (val.length === 0 || val.length === 1) ? ((val.length === 0) ? this : val[0]) : val; 135 }; 136 137 $.fn[pluginname].options = {}; 138 139 return this; 140 }, 141 autoload: function (pluginname) { 142 var arr = pluginname.split(','); 143 var len = arr.length; 144 145 for (var i = 0; i < len; i++) { 146 var name = arr[i].toLowerCase().split(',').map(function (s) { 147 return s.trim() 148 }).join(','); 149 this.autoloadQueue.push(name); 150 } 151 152 return this; 153 }, 154 autoloadQueue: [], 155 startAutoload: function () { 156 if (!window.MutationObserver || this.autoloadQueue.length === 0) { 157 return; 158 } 159 160 var self = this; 161 var observer = new MutationObserver(function (mutations) { 162 mutations.forEach(function (mutation) { 163 var newNodes = mutation.addedNodes; 164 if (newNodes.length === 0 || (newNodes.length === 1 && newNodes.nodeType === 3)) { 165 return; 166 } 167 168 self.startAutoloadOnce(); 169 }); 170 }); 171 172 // pass in the target node, as well as the observer options 173 observer.observe(document, { 174 subtree: true, 175 childList: true 176 }); 177 }, 178 startAutoloadOnce: function () { 179 var self = this; 180 var $nodes = $('[data-component]').not('[data-loaded]'); 181 $nodes.each(function () { 182 var $el = $(this); 183 var pluginname = $el.data('component'); 184 185 if (self.autoloadQueue.indexOf(pluginname) !== -1) { 186 $el.attr('data-loaded', true); 187 $el[pluginname](); 188 } 189 }); 190 191 }, 192 watch: function () { 193 Kube.Plugin.startAutoloadOnce(); 194 Kube.Plugin.startAutoload(); 195 } 196 }; 197 198 $(window).on('load', function () { 199 Kube.Plugin.watch(); 200 }); 201 202 }(Kube)); 203 /** 204 * @library Kube Animation 205 * @author Imperavi LLC 206 * @license MIT 207 */ 208 (function (Kube) { 209 Kube.Animation = function (element, effect, callback) { 210 this.namespace = 'animation'; 211 this.defaults = {}; 212 213 // Parent Constructor 214 Kube.apply(this, arguments); 215 216 // Initialization 217 this.effect = effect; 218 this.completeCallback = (typeof callback === 'undefined') ? false : callback; 219 this.prefixes = ['', '-moz-', '-o-animation-', '-webkit-']; 220 this.queue = []; 221 222 this.start(); 223 }; 224 225 Kube.Animation.prototype = { 226 start: function () { 227 if (this.isSlideEffect()) this.setElementHeight(); 228 229 this.addToQueue(); 230 this.clean(); 231 this.animate(); 232 }, 233 addToQueue: function () { 234 this.queue.push(this.effect); 235 }, 236 setElementHeight: function () { 237 this.$element.height(this.$element.height()); 238 }, 239 removeElementHeight: function () { 240 this.$element.css('height', ''); 241 }, 242 isSlideEffect: function () { 243 return (this.effect === 'slideDown' || this.effect === 'slideUp'); 244 }, 245 isHideableEffect: function () { 246 var effects = ['fadeOut', 'slideUp', 'flipOut', 'zoomOut', 'slideOutUp', 'slideOutRight', 'slideOutLeft']; 247 248 return ($.inArray(this.effect, effects) !== -1); 249 }, 250 isToggleEffect: function () { 251 return (this.effect === 'show' || this.effect === 'hide'); 252 }, 253 storeHideClasses: function () { 254 if (this.$element.hasClass('hide-sm')) this.$element.data('hide-sm-class', true); 255 else if (this.$element.hasClass('hide-md')) this.$element.data('hide-md-class', true); 256 }, 257 revertHideClasses: function () { 258 if (this.$element.data('hide-sm-class')) this.$element.addClass('hide-sm').removeData('hide-sm-class'); 259 else if (this.$element.data('hide-md-class')) this.$element.addClass('hide-md').removeData('hide-md-class'); 260 else this.$element.addClass('hide'); 261 }, 262 removeHideClass: function () { 263 if (this.$element.data('hide-sm-class')) this.$element.removeClass('hide-sm'); 264 else if (this.$element.data('hide-md-class')) this.$element.removeClass('hide-md'); 265 else this.$element.removeClass('hide'); 266 }, 267 animate: function () { 268 this.storeHideClasses(); 269 if (this.isToggleEffect()) { 270 return this.makeSimpleEffects(); 271 } 272 273 this.$element.addClass('kubeanimated'); 274 this.$element.addClass(this.queue[0]); 275 this.removeHideClass(); 276 277 var _callback = (this.queue.length > 1) ? null : this.completeCallback; 278 this.complete('AnimationEnd', $.proxy(this.makeComplete, this), _callback); 279 }, 280 makeSimpleEffects: function () { 281 if (this.effect === 'show') this.removeHideClass(); 282 else if (this.effect === 'hide') this.revertHideClasses(); 283 284 if (typeof this.completeCallback === 'function') this.completeCallback(this); 285 }, 286 makeComplete: function () { 287 if (this.$element.hasClass(this.queue[0])) { 288 this.clean(); 289 this.queue.shift(); 290 291 if (this.queue.length) this.animate(); 292 } 293 }, 294 complete: function (type, make, callback) { 295 var events = type.split(' ').map(function (type) { 296 return type.toLowerCase() + ' webkit' + type + ' o' + type + ' MS' + type; 297 }); 298 299 this.$element.one(events.join(' '), $.proxy(function () { 300 if (typeof make === 'function') make(); 301 if (this.isHideableEffect()) this.revertHideClasses(); 302 if (this.isSlideEffect()) this.removeElementHeight(); 303 if (typeof callback === 'function') callback(this); 304 305 this.$element.off(event); 306 307 }, this)); 308 }, 309 clean: function () { 310 this.$element.removeClass('kubeanimated').removeClass(this.queue[0]); 311 } 312 }; 313 314 // Inheritance 315 Kube.Animation.inherits(Kube); 316 317 }(Kube)); 318 319 // Plugin 320 (function ($) { 321 $.fn.animation = function (effect, callback) { 322 var name = 'fn.animation'; 323 324 return this.each(function () { 325 var $this = $(this), data = $this.data(name); 326 327 $this.data(name, {}); 328 $this.data(name, (data = new Kube.Animation(this, effect, callback))); 329 }); 330 }; 331 332 $.fn.animation.options = {}; 333 334 })(jQuery); 335 /** 336 * @library Kube Detect 337 * @author Imperavi LLC 338 * @license MIT 339 */ 340 (function (Kube) { 341 Kube.Detect = function () { 342 }; 343 344 Kube.Detect.prototype = { 345 isMobile: function () { 346 return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent); 347 }, 348 isDesktop: function () { 349 return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent); 350 }, 351 isMobileScreen: function () { 352 return ($(window).width() <= 768); 353 }, 354 isTabletScreen: function () { 355 return ($(window).width() >= 768 && $(window).width() <= 1024); 356 }, 357 isDesktopScreen: function () { 358 return ($(window).width() > 1024); 359 } 360 }; 361 362 363 }(Kube)); 364 /** 365 * @library Kube FormData 366 * @author Imperavi LLC 367 * @license MIT 368 */ 369 (function (Kube) { 370 Kube.FormData = function (app) { 371 this.opts = app.opts; 372 }; 373 374 Kube.FormData.prototype = { 375 set: function (data) { 376 this.data = data; 377 }, 378 get: function (formdata) { 379 this.formdata = formdata; 380 381 if (this.opts.appendForms) this.appendForms(); 382 if (this.opts.appendFields) this.appendFields(); 383 384 return this.data; 385 }, 386 appendFields: function () { 387 var $fields = $(this.opts.appendFields); 388 if ($fields.length === 0) { 389 return; 390 } 391 392 var self = this; 393 var str = ''; 394 395 if (this.formdata) { 396 $fields.each(function () { 397 self.data.append($(this).attr('name'), $(this).val()); 398 }); 399 } 400 else { 401 $fields.each(function () { 402 str += '&' + $(this).attr('name') + '=' + $(this).val(); 403 }); 404 405 this.data = (this.data === '') ? str.replace(/^&/, '') : this.data + str; 406 } 407 }, 408 appendForms: function () { 409 var $forms = $(this.opts.appendForms); 410 if ($forms.length === 0) { 411 return; 412 } 413 414 if (this.formdata) { 415 var self = this; 416 var formsData = $(this.opts.appendForms).serializeArray(); 417 $.each(formsData, function (i, s) { 418 self.data.append(s.name, s.value); 419 }); 420 } 421 else { 422 var str = $forms.serialize(); 423 424 this.data = (this.data === '') ? str : this.data + '&' + str; 425 } 426 } 427 }; 428 429 430 }(Kube)); 431 /** 432 * @library Kube Response 433 * @author Imperavi LLC 434 * @license MIT 435 */ 436 (function (Kube) { 437 Kube.Response = function (app) { 438 }; 439 440 Kube.Response.prototype = { 441 parse: function (str) { 442 if (str === '') return false; 443 444 var obj = {}; 445 446 try { 447 obj = JSON.parse(str); 448 } catch (e) { 449 return false; 450 } 451 452 if (obj[0] !== undefined) { 453 for (var item in obj) { 454 this.parseItem(obj[item]); 455 } 456 } 457 else { 458 this.parseItem(obj); 459 } 460 461 return obj; 462 }, 463 parseItem: function (item) { 464 if (item.type === 'value') { 465 $.each(item.data, $.proxy(function (key, val) { 466 val = (val === null || val === false) ? 0 : val; 467 val = (val === true) ? 1 : val; 468 469 $(key).val(val); 470 471 }, this)); 472 } 473 else if (item.type === 'html') { 474 $.each(item.data, $.proxy(function (key, val) { 475 val = (val === null || val === false) ? '' : val; 476 477 $(key).html(this.stripslashes(val)); 478 479 }, this)); 480 } 481 else if (item.type === 'addClass') { 482 $.each(item.data, function (key, val) { 483 $(key).addClass(val); 484 }); 485 } 486 else if (item.type === 'removeClass') { 487 $.each(item.data, function (key, val) { 488 $(key).removeClass(val); 489 }); 490 } 491 else if (item.type === 'command') { 492 $.each(item.data, function (key, val) { 493 $(val)[key](); 494 }); 495 } 496 else if (item.type === 'animation') { 497 $.each(item.data, function (key, data) { 498 data.opts = (typeof data.opts === 'undefined') ? {} : data.opts; 499 500 $(key).animation(data.name, data.opts); 501 }); 502 } 503 else if (item.type === 'location') { 504 top.location.href = item.data; 505 } 506 else if (item.type === 'notify') { 507 $.notify(item.data); 508 } 509 510 return item; 511 }, 512 stripslashes: function (str) { 513 return (str + '').replace(/\0/g, '0').replace(/\\([\\'"])/g, '$1'); 514 } 515 }; 516 517 518 }(Kube)); 519 /** 520 * @library Kube Utils 521 * @author Imperavi LLC 522 * @license MIT 523 */ 524 (function (Kube) { 525 Kube.Utils = function () { 526 }; 527 528 Kube.Utils.prototype = { 529 disableBodyScroll: function () { 530 var $body = $('html'); 531 var windowWidth = window.innerWidth; 532 533 if (!windowWidth) { 534 var documentElementRect = document.documentElement.getBoundingClientRect(); 535 windowWidth = documentElementRect.right - Math.abs(documentElementRect.left); 536 } 537 538 var isOverflowing = document.body.clientWidth < windowWidth; 539 var scrollbarWidth = this.measureScrollbar(); 540 541 $body.css('overflow', 'hidden'); 542 if (isOverflowing) $body.css('padding-right', scrollbarWidth); 543 }, 544 measureScrollbar: function () { 545 var $body = $('body'); 546 var scrollDiv = document.createElement('div'); 547 scrollDiv.className = 'scrollbar-measure'; 548 549 $body.append(scrollDiv); 550 var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; 551 $body[0].removeChild(scrollDiv); 552 return scrollbarWidth; 553 }, 554 enableBodyScroll: function () { 555 $('html').css({'overflow': '', 'padding-right': ''}); 556 } 557 }; 558 559 560 }(Kube)); 561 /** 562 * @library Kube Message 563 * @author Imperavi LLC 564 * @license MIT 565 */ 566 (function (Kube) { 567 Kube.Message = function (element, options) { 568 this.namespace = 'message'; 569 this.defaults = { 570 closeSelector: '.close', 571 closeEvent: 'click', 572 animationOpen: 'fadeIn', 573 animationClose: 'fadeOut', 574 callbacks: ['open', 'opened', 'close', 'closed'] 575 }; 576 577 // Parent Constructor 578 Kube.apply(this, arguments); 579 580 // Initialization 581 this.start(); 582 }; 583 584 // Functionality 585 Kube.Message.prototype = { 586 start: function () { 587 this.$close = this.$element.find(this.opts.closeSelector); 588 this.$close.on(this.opts.closeEvent + '.' + this.namespace, $.proxy(this.close, this)); 589 this.$element.addClass('open'); 590 }, 591 stop: function () { 592 this.$close.off('.' + this.namespace); 593 this.$element.removeClass('open'); 594 }, 595 open: function (e) { 596 if (e) e.preventDefault(); 597 598 if (!this.isOpened()) { 599 this.callback('open'); 600 this.$element.animation(this.opts.animationOpen, $.proxy(this.onOpened, this)); 601 } 602 }, 603 isOpened: function () { 604 return this.$element.hasClass('open'); 605 }, 606 onOpened: function () { 607 this.callback('opened'); 608 this.$element.addClass('open'); 609 }, 610 close: function (e) { 611 if (e) e.preventDefault(); 612 613 if (this.isOpened()) { 614 this.callback('close'); 615 this.$element.animation(this.opts.animationClose, $.proxy(this.onClosed, this)); 616 } 617 }, 618 onClosed: function () { 619 this.callback('closed'); 620 this.$element.removeClass('open'); 621 } 622 }; 623 624 // Inheritance 625 Kube.Message.inherits(Kube); 626 627 // Plugin 628 Kube.Plugin.create('Message'); 629 Kube.Plugin.autoload('Message'); 630 631 }(Kube)); 632 /** 633 * @library Kube Sticky 634 * @author Imperavi LLC 635 * @license MIT 636 */ 637 (function (Kube) { 638 Kube.Sticky = function (element, options) { 639 this.namespace = 'sticky'; 640 this.defaults = { 641 classname: 'fixed', 642 offset: 0, // pixels 643 callbacks: ['fixed', 'unfixed'] 644 }; 645 646 // Parent Constructor 647 Kube.apply(this, arguments); 648 649 // Initialization 650 this.start(); 651 }; 652 653 // Functionality 654 Kube.Sticky.prototype = { 655 start: function () { 656 this.offsetTop = this.getOffsetTop(); 657 658 this.load(); 659 $(window).scroll($.proxy(this.load, this)); 660 }, 661 getOffsetTop: function () { 662 return this.$element.offset().top; 663 }, 664 load: function () { 665 return (this.isFix()) ? this.fixed() : this.unfixed(); 666 }, 667 isFix: function () { 668 return ($(window).scrollTop() > (this.offsetTop + this.opts.offset)); 669 }, 670 fixed: function () { 671 this.$element.addClass(this.opts.classname).css('top', this.opts.offset + 'px'); 672 this.callback('fixed'); 673 }, 674 unfixed: function () { 675 this.$element.removeClass(this.opts.classname).css('top', ''); 676 this.callback('unfixed'); 677 } 678 }; 679 680 // Inheritance 681 Kube.Sticky.inherits(Kube); 682 683 // Plugin 684 Kube.Plugin.create('Sticky'); 685 Kube.Plugin.autoload('Sticky'); 686 687 }(Kube)); 688 /** 689 * @library Kube Toggleme 690 * @author Imperavi LLC 691 * @license MIT 692 */ 693 (function (Kube) { 694 Kube.Toggleme = function (element, options) { 695 this.namespace = 'toggleme'; 696 this.defaults = { 697 toggleEvent: 'click', 698 target: null, 699 text: '', 700 animationOpen: 'slideDown', 701 animationClose: 'slideUp', 702 callbacks: ['open', 'opened', 'close', 'closed'] 703 }; 704 705 // Parent Constructor 706 Kube.apply(this, arguments); 707 708 // Initialization 709 this.start(); 710 }; 711 712 // Functionality 713 Kube.Toggleme.prototype = { 714 start: function () { 715 if (!this.hasTarget()) return; 716 717 this.$element.on(this.opts.toggleEvent + '.' + this.namespace, $.proxy(this.toggle, this)); 718 }, 719 stop: function () { 720 this.$element.off('.' + this.namespace); 721 this.revertText(); 722 }, 723 toggle: function (e) { 724 if (this.isOpened()) this.close(e); 725 else this.open(e); 726 }, 727 open: function (e) { 728 if (e) e.preventDefault(); 729 730 if (!this.isOpened()) { 731 this.storeText(); 732 this.callback('open'); 733 this.$target.animation('slideDown', $.proxy(this.onOpened, this)); 734 735 // changes the text of $element with a less delay to smooth 736 setTimeout($.proxy(this.replaceText, this), 100); 737 } 738 }, 739 close: function (e) { 740 if (e) e.preventDefault(); 741 742 if (this.isOpened()) { 743 this.callback('close'); 744 this.$target.animation('slideUp', $.proxy(this.onClosed, this)); 745 } 746 }, 747 isOpened: function () { 748 return (this.$target.hasClass('open')); 749 }, 750 onOpened: function () { 751 this.$target.addClass('open'); 752 this.callback('opened'); 753 }, 754 onClosed: function () { 755 this.$target.removeClass('open'); 756 this.revertText(); 757 this.callback('closed'); 758 }, 759 storeText: function () { 760 this.$element.data('replacement-text', this.$element.html()); 761 }, 762 revertText: function () { 763 var text = this.$element.data('replacement-text'); 764 if (text) this.$element.html(text); 765 766 this.$element.removeData('replacement-text'); 767 }, 768 replaceText: function () { 769 if (this.opts.text !== '') { 770 this.$element.html(this.opts.text); 771 } 772 } 773 }; 774 775 // Inheritance 776 Kube.Toggleme.inherits(Kube); 777 778 // Plugin 779 Kube.Plugin.create('Toggleme'); 780 Kube.Plugin.autoload('Toggleme'); 781 782 }(Kube)); 783 /** 784 * @library Kube Offcanvas 785 * @author Imperavi LLC 786 * @license MIT 787 */ 788 (function (Kube) { 789 Kube.Offcanvas = function (element, options) { 790 this.namespace = 'offcanvas'; 791 this.defaults = { 792 target: null, // selector 793 push: true, // boolean 794 width: '250px', // string 795 direction: 'left', // string: left or right 796 toggleEvent: 'click', 797 clickOutside: true, // boolean 798 animationOpen: 'slideInLeft', 799 animationClose: 'slideOutLeft', 800 callbacks: ['open', 'opened', 'close', 'closed'] 801 }; 802 803 // Parent Constructor 804 Kube.apply(this, arguments); 805 806 // Services 807 this.utils = new Kube.Utils(); 808 this.detect = new Kube.Detect(); 809 810 // Initialization 811 this.start(); 812 }; 813 814 // Functionality 815 Kube.Offcanvas.prototype = { 816 start: function () { 817 if (!this.hasTarget()) return; 818 819 this.buildTargetWidth(); 820 this.buildAnimationDirection(); 821 822 this.$close = this.getCloseLink(); 823 this.$element.on(this.opts.toggleEvent + '.' + this.namespace, $.proxy(this.toggle, this)); 824 this.$target.addClass('offcanvas'); 825 this.$target.trigger('kube.offcanvas.ready'); 826 }, 827 stop: function () { 828 this.closeAll(); 829 830 this.$element.off('.' + this.namespace); 831 this.$close.off('.' + this.namespace); 832 $(document).off('.' + this.namespace); 833 }, 834 toggle: function (e) { 835 if (this.isOpened()) this.close(e); 836 else this.open(e); 837 }, 838 buildTargetWidth: function () { 839 this.opts.width = ($(window).width() < parseInt(this.opts.width)) ? '100%' : this.opts.width; 840 }, 841 buildAnimationDirection: function () { 842 if (this.opts.direction === 'right') { 843 this.opts.animationOpen = 'slideInRight'; 844 this.opts.animationClose = 'slideOutRight'; 845 } 846 }, 847 getCloseLink: function () { 848 return this.$target.find('.close'); 849 }, 850 open: function (e) { 851 if (e) e.preventDefault(); 852 853 if (!this.isOpened()) { 854 this.closeAll(); 855 this.callback('open'); 856 857 this.$target.addClass('offcanvas-' + this.opts.direction); 858 this.$target.css('width', Math.min(parseInt(this.opts.width), window.innerWidth - 100)); 859 this.$target.css('right', '-' + Math.min(parseInt(this.opts.width), window.innerWidth - 100)); 860 861 this.pushBody(); 862 863 this.$target.trigger('kube.offcanvas.open'); 864 this.$target.animation(this.opts.animationOpen, $.proxy(this.onOpened, this)); 865 866 867 } 868 }, 869 closeAll: function () { 870 var $elms = $(document).find('.offcanvas'); 871 if ($elms.length !== 0) { 872 $elms.each(function () { 873 var $el = $(this); 874 875 if ($el.hasClass('open')) { 876 $el.css('width', '').animation('hide'); 877 $el.removeClass('open offcanvas-left offcanvas-right'); 878 } 879 880 }); 881 882 $(document).off('.' + this.namespace); 883 $('body').css('left', ''); 884 } 885 }, 886 close: function (e) { 887 if (e) { 888 var $el = $(e.target); 889 var tagName = $el[0].tagName; 890 var isTag = (tagName === 'A' || tagName === 'BUTTON' || tagName === 'I' || $el.parents('a').length); 891 if (isTag && $el.closest('.offcanvas').length !== 0 && !$el.hasClass('close')) { 892 return; 893 } 894 895 e.preventDefault(); 896 } 897 898 if (this.isOpened()) { 899 this.utils.enableBodyScroll(); 900 this.callback('close'); 901 this.pullBody(); 902 this.$target.trigger('kube.offcanvas.close'); 903 this.$target.animation(this.opts.animationClose, $.proxy(this.onClosed, this)); 904 } 905 }, 906 isOpened: function () { 907 return (this.$target.hasClass('open')); 908 }, 909 onOpened: function () { 910 if (this.opts.clickOutside) $(document).on('click.' + this.namespace + ' tap.' + this.namespace, $.proxy(this.close, this)); 911 if (this.detect.isMobileScreen()) $('html').addClass('no-scroll'); 912 913 $(document).on('keyup.' + this.namespace, $.proxy(this.handleKeyboard, this)); 914 this.$close.on('click.' + this.namespace, $.proxy(this.close, this)); 915 916 this.utils.disableBodyScroll(); 917 this.$target.addClass('open'); 918 this.callback('opened'); 919 920 921 }, 922 onClosed: function () { 923 if (this.detect.isMobileScreen()) $('html').removeClass('no-scroll'); 924 925 this.$target.css('width', '').removeClass('offcanvas-' + this.opts.direction); 926 927 this.$close.off('.' + this.namespace); 928 $(document).off('.' + this.namespace); 929 930 this.$target.removeClass('open'); 931 this.callback('closed'); 932 933 this.$target.trigger('kube.offcanvas.closed'); 934 }, 935 handleKeyboard: function (e) { 936 if (e.which === 27) this.close(); 937 }, 938 pullBody: function () { 939 if (this.opts.push) { 940 $('body').animate({left: 0}, 350, function () { 941 $(this).removeClass('offcanvas-push-body'); 942 }); 943 } 944 }, 945 pushBody: function () { 946 if (this.opts.push) { 947 var properties = (this.opts.direction === 'left') ? {'left': this.opts.width} : {'left': '-' + this.opts.width}; 948 $('body').addClass('offcanvas-push-body').animate(properties, 200); 949 } 950 } 951 }; 952 953 // Inheritance 954 Kube.Offcanvas.inherits(Kube); 955 956 // Plugin 957 Kube.Plugin.create('Offcanvas'); 958 Kube.Plugin.autoload('Offcanvas'); 959 960 }(Kube)); 961 /** 962 * @library Kube Collapse 963 * @author Imperavi LLC 964 * @license MIT 965 */ 966 (function (Kube) { 967 Kube.Collapse = function (element, options) { 968 this.namespace = 'collapse'; 969 this.defaults = { 970 target: null, 971 toggle: true, 972 active: false, // string (hash = tab id selector) 973 toggleClass: 'collapse-toggle', 974 boxClass: 'collapse-box', 975 callbacks: ['open', 'opened', 'close', 'closed'], 976 977 // private 978 hashes: [], 979 currentHash: false, 980 currentItem: false 981 }; 982 983 // Parent Constructor 984 Kube.apply(this, arguments); 985 986 // Initialization 987 this.start(); 988 }; 989 990 // Functionality 991 Kube.Collapse.prototype = { 992 start: function () { 993 // items 994 this.$items = this.getItems(); 995 this.$items.each($.proxy(this.loadItems, this)); 996 997 // boxes 998 this.$boxes = this.getBoxes(); 999 1000 // active 1001 this.setActiveItem(); 1002 }, 1003 getItems: function () { 1004 return this.$element.find('.' + this.opts.toggleClass); 1005 }, 1006 getBoxes: function () { 1007 return this.$element.find('.' + this.opts.boxClass); 1008 }, 1009 loadItems: function (i, el) { 1010 var item = this.getItem(el); 1011 1012 // set item identificator 1013 item.$el.attr('rel', item.hash); 1014 1015 // active 1016 if (!$(item.hash).hasClass('hide')) { 1017 this.opts.currentItem = item; 1018 this.opts.active = item.hash; 1019 1020 item.$el.addClass('active'); 1021 } 1022 1023 // event 1024 item.$el.on('click.collapse', $.proxy(this.toggle, this)); 1025 1026 }, 1027 setActiveItem: function () { 1028 if (this.opts.active !== false) { 1029 this.opts.currentItem = this.getItemBy(this.opts.active); 1030 this.opts.active = this.opts.currentItem.hash; 1031 } 1032 1033 if (this.opts.currentItem !== false) { 1034 this.addActive(this.opts.currentItem); 1035 this.opts.currentItem.$box.removeClass('hide'); 1036 } 1037 }, 1038 addActive: function (item) { 1039 item.$box.removeClass('hide').addClass('open'); 1040 item.$el.addClass('active'); 1041 1042 if (item.$caret !== false) item.$caret.removeClass('down').addClass('up'); 1043 if (item.$parent !== false) item.$parent.addClass('active'); 1044 1045 this.opts.currentItem = item; 1046 }, 1047 removeActive: function (item) { 1048 item.$box.removeClass('open'); 1049 item.$el.removeClass('active'); 1050 1051 if (item.$caret !== false) item.$caret.addClass('down').removeClass('up'); 1052 if (item.$parent !== false) item.$parent.removeClass('active'); 1053 1054 this.opts.currentItem = false; 1055 }, 1056 toggle: function (e) { 1057 if (e) e.preventDefault(); 1058 1059 var target = $(e.target).closest('.' + this.opts.toggleClass).get(0) || e.target; 1060 var item = this.getItem(target); 1061 1062 if (this.isOpened(item.hash)) this.close(item.hash); 1063 else this.open(e) 1064 }, 1065 openAll: function () { 1066 this.$items.addClass('active'); 1067 this.$boxes.addClass('open').removeClass('hide'); 1068 }, 1069 open: function (e, push) { 1070 if (typeof e === 'undefined') return; 1071 if (typeof e === 'object') e.preventDefault(); 1072 1073 var target = $(e.target).closest('.' + this.opts.toggleClass).get(0) || e.target; 1074 var item = (typeof e === 'object') ? this.getItem(target) : this.getItemBy(e); 1075 1076 if (item.$box.hasClass('open')) { 1077 return; 1078 } 1079 1080 if (this.opts.toggle) this.closeAll(); 1081 1082 this.callback('open', item); 1083 this.addActive(item); 1084 1085 item.$box.animation('slideDown', $.proxy(this.onOpened, this)); 1086 }, 1087 onOpened: function () { 1088 this.callback('opened', this.opts.currentItem); 1089 }, 1090 closeAll: function () { 1091 this.$items.removeClass('active').closest('li').removeClass('active'); 1092 this.$boxes.removeClass('open').addClass('hide'); 1093 }, 1094 close: function (num) { 1095 var item = this.getItemBy(num); 1096 1097 this.callback('close', item); 1098 1099 this.opts.currentItem = item; 1100 1101 item.$box.animation('slideUp', $.proxy(this.onClosed, this)); 1102 }, 1103 onClosed: function () { 1104 var item = this.opts.currentItem; 1105 1106 this.removeActive(item); 1107 this.callback('closed', item); 1108 }, 1109 isOpened: function (hash) { 1110 return $(hash).hasClass('open'); 1111 }, 1112 getItem: function (element) { 1113 var item = {}; 1114 1115 item.$el = $(element); 1116 item.hash = item.$el.attr('href'); 1117 item.$box = $(item.hash); 1118 1119 var $parent = item.$el.parent(); 1120 item.$parent = ($parent[0].tagName === 'LI') ? $parent : false; 1121 1122 var $caret = item.$el.find('.caret'); 1123 item.$caret = ($caret.length !== 0) ? $caret : false; 1124 1125 return item; 1126 }, 1127 getItemBy: function (num) { 1128 var element = (typeof num === 'number') ? this.$items.eq(num - 1) : this.$element.find('[rel="' + num + '"]'); 1129 1130 return this.getItem(element); 1131 } 1132 }; 1133 1134 // Inheritance 1135 Kube.Collapse.inherits(Kube); 1136 1137 // Plugin 1138 Kube.Plugin.create('Collapse'); 1139 Kube.Plugin.autoload('Collapse'); 1140 1141 }(Kube)); 1142 /** 1143 * @library Kube Dropdown 1144 * @author Imperavi LLC 1145 * @license MIT 1146 */ 1147 (function (Kube) { 1148 Kube.Dropdown = function (element, options) { 1149 this.namespace = 'dropdown'; 1150 this.defaults = { 1151 target: null, 1152 toggleEvent: 'click', 1153 height: false, // integer 1154 width: false, // integer 1155 animationOpen: 'slideDown', 1156 animationClose: 'slideUp', 1157 caretUp: false, 1158 callbacks: ['open', 'opened', 'close', 'closed'] 1159 }; 1160 1161 // Parent Constructor 1162 Kube.apply(this, arguments); 1163 1164 // Services 1165 this.utils = new Kube.Utils(); 1166 this.detect = new Kube.Detect(); 1167 1168 // Initialization 1169 this.start(); 1170 }; 1171 1172 // Functionality 1173 Kube.Dropdown.prototype = { 1174 start: function () { 1175 this.buildClose(); 1176 this.buildCaret(); 1177 1178 if (this.detect.isMobile()) this.buildMobileAnimation(); 1179 1180 this.$target.addClass('hide'); 1181 this.$element.on(this.opts.toggleEvent + '.' + this.namespace, $.proxy(this.toggle, this)); 1182 1183 }, 1184 stop: function () { 1185 this.$element.off('.' + this.namespace); 1186 this.$target.removeClass('open').addClass('hide'); 1187 this.disableEvents(); 1188 }, 1189 buildMobileAnimation: function () { 1190 this.opts.animationOpen = 'fadeIn'; 1191 this.opts.animationClose = 'fadeOut'; 1192 }, 1193 buildClose: function () { 1194 this.$close = this.$target.find('.close'); 1195 }, 1196 buildCaret: function () { 1197 this.$caret = this.getCaret(); 1198 this.buildCaretPosition(); 1199 }, 1200 buildCaretPosition: function () { 1201 var height = this.$element.offset().top + this.$element.innerHeight() + this.$target.innerHeight(); 1202 1203 if ($(document).height() > height) { 1204 return; 1205 } 1206 1207 this.opts.caretUp = true; 1208 this.$caret.addClass('up'); 1209 }, 1210 getCaret: function () { 1211 return this.$element.find('.caret'); 1212 }, 1213 toggleCaretOpen: function () { 1214 if (this.opts.caretUp) this.$caret.removeClass('up').addClass('down'); 1215 else this.$caret.removeClass('down').addClass('up'); 1216 }, 1217 toggleCaretClose: function () { 1218 if (this.opts.caretUp) this.$caret.removeClass('down').addClass('up'); 1219 else this.$caret.removeClass('up').addClass('down'); 1220 }, 1221 toggle: function (e) { 1222 if (this.isOpened()) this.close(e); 1223 else this.open(e); 1224 }, 1225 open: function (e) { 1226 if (e) e.preventDefault(); 1227 1228 this.callback('open'); 1229 $('.dropdown').removeClass('open').addClass('hide'); 1230 1231 if (this.opts.height) this.$target.css('min-height', this.opts.height + 'px'); 1232 if (this.opts.width) this.$target.width(this.opts.width); 1233 1234 this.setPosition(); 1235 this.toggleCaretOpen(); 1236 1237 this.$target.animation(this.opts.animationOpen, $.proxy(this.onOpened, this)); 1238 }, 1239 close: function (e) { 1240 if (!this.isOpened()) { 1241 return; 1242 } 1243 1244 if (e) { 1245 if (this.shouldNotBeClosed(e.target)) { 1246 return; 1247 } 1248 1249 e.preventDefault(); 1250 } 1251 1252 this.utils.enableBodyScroll(); 1253 this.callback('close'); 1254 this.toggleCaretClose(); 1255 1256 this.$target.animation(this.opts.animationClose, $.proxy(this.onClosed, this)); 1257 }, 1258 onClosed: function () { 1259 this.$target.removeClass('open'); 1260 this.disableEvents(); 1261 this.callback('closed'); 1262 }, 1263 onOpened: function () { 1264 this.$target.addClass('open'); 1265 this.enableEvents(); 1266 this.callback('opened'); 1267 }, 1268 isOpened: function () { 1269 return (this.$target.hasClass('open')); 1270 }, 1271 enableEvents: function () { 1272 if (this.detect.isDesktop()) { 1273 this.$target.on('mouseover.' + this.namespace, $.proxy(this.utils.disableBodyScroll, this.utils)) 1274 .on('mouseout.' + this.namespace, $.proxy(this.utils.enableBodyScroll, this.utils)); 1275 } 1276 1277 $(document).on('scroll.' + this.namespace, $.proxy(this.setPosition, this)); 1278 $(window).on('resize.' + this.namespace, $.proxy(this.setPosition, this)); 1279 $(document).on('click.' + this.namespace + ' touchstart.' + this.namespace, $.proxy(this.close, this)); 1280 $(document).on('keydown.' + this.namespace, $.proxy(this.handleKeyboard, this)); 1281 this.$target.find('[data-action="dropdown-close"]').on('click.' + this.namespace, $.proxy(this.close, this)); 1282 }, 1283 disableEvents: function () { 1284 this.$target.off('.' + this.namespace); 1285 $(document).off('.' + this.namespace); 1286 $(window).off('.' + this.namespace); 1287 }, 1288 handleKeyboard: function (e) { 1289 if (e.which === 27) this.close(e); 1290 }, 1291 shouldNotBeClosed: function (el) { 1292 if ($(el).attr('data-action') === 'dropdown-close' || el === this.$close[0]) { 1293 return false; 1294 } 1295 else if ($(el).closest('.dropdown').length === 0) { 1296 return false; 1297 } 1298 1299 return true; 1300 }, 1301 isNavigationFixed: function () { 1302 return (this.$element.closest('.fixed').length !== 0); 1303 }, 1304 getPlacement: function (height) { 1305 return ($(document).height() < height) ? 'top' : 'bottom'; 1306 }, 1307 getOffset: function (position) { 1308 return (this.isNavigationFixed()) ? this.$element.position() : this.$element.offset(); 1309 }, 1310 getPosition: function () { 1311 return (this.isNavigationFixed()) ? 'fixed' : 'absolute'; 1312 }, 1313 setPosition: function () { 1314 if (this.detect.isMobile()) { 1315 this.$target.addClass('dropdown-mobile'); 1316 return; 1317 } 1318 1319 var position = this.getPosition(); 1320 var coords = this.getOffset(position); 1321 var height = this.$target.innerHeight(); 1322 var width = this.$target.innerWidth(); 1323 var placement = this.getPlacement(coords.top + height + this.$element.innerHeight()); 1324 var leftFix = ($(window).width() < (coords.left + width)) ? (width - this.$element.innerWidth()) : 0; 1325 var top, left = coords.left - leftFix; 1326 1327 if (placement === 'bottom') { 1328 if (!this.isOpened()) this.$caret.removeClass('up').addClass('down'); 1329 1330 this.opts.caretUp = false; 1331 top = coords.top + this.$element.outerHeight() + 1; 1332 } 1333 else { 1334 this.opts.animationOpen = 'show'; 1335 this.opts.animationClose = 'hide'; 1336 1337 if (!this.isOpened()) this.$caret.addClass('up').removeClass('down'); 1338 1339 this.opts.caretUp = true; 1340 top = coords.top - height - 1; 1341 } 1342 1343 this.$target.css({position: position, top: top + 'px', left: left + 'px'}); 1344 } 1345 }; 1346 1347 // Inheritance 1348 Kube.Dropdown.inherits(Kube); 1349 1350 // Plugin 1351 Kube.Plugin.create('Dropdown'); 1352 Kube.Plugin.autoload('Dropdown'); 1353 1354 }(Kube)); 1355 /** 1356 * @library Kube Tabs 1357 * @author Imperavi LLC 1358 * @license MIT 1359 */ 1360 (function (Kube) { 1361 Kube.Tabs = function (element, options) { 1362 this.namespace = 'tabs'; 1363 this.defaults = { 1364 equals: false, 1365 active: false, // string (hash = tab id selector) 1366 live: false, // class selector 1367 hash: true, //boolean 1368 callbacks: ['init', 'next', 'prev', 'open', 'opened', 'close', 'closed'] 1369 }; 1370 1371 // Parent Constructor 1372 Kube.apply(this, arguments); 1373 1374 // Initialization 1375 this.start(); 1376 }; 1377 1378 // Functionality 1379 Kube.Tabs.prototype = { 1380 start: function () { 1381 if (this.opts.live !== false) this.buildLiveTabs(); 1382 1383 this.tabsCollection = []; 1384 this.hashesCollection = []; 1385 this.currentHash = []; 1386 this.currentItem = false; 1387 1388 // items 1389 this.$items = this.getItems(); 1390 this.$items.each($.proxy(this.loadItems, this)); 1391 1392 // tabs 1393 this.$tabs = this.getTabs(); 1394 1395 // location hash 1396 this.currentHash = this.getLocationHash(); 1397 1398 // close all 1399 this.closeAll(); 1400 1401 // active & height 1402 this.setActiveItem(); 1403 this.setItemHeight(); 1404 1405 // callback 1406 this.callback('init'); 1407 1408 }, 1409 getTabs: function () { 1410 return $(this.tabsCollection).map(function () { 1411 return this.toArray(); 1412 }); 1413 }, 1414 getItems: function () { 1415 return this.$element.find('a'); 1416 }, 1417 loadItems: function (i, el) { 1418 var item = this.getItem(el); 1419 1420 // set item identificator 1421 item.$el.attr('rel', item.hash); 1422 1423 // collect item 1424 this.collectItem(item); 1425 1426 // active 1427 if (item.$parent.hasClass('active')) { 1428 this.currentItem = item; 1429 this.opts.active = item.hash; 1430 } 1431 1432 // event 1433 item.$el.on('click.tabs', $.proxy(this.open, this)); 1434 1435 }, 1436 collectItem: function (item) { 1437 this.tabsCollection.push(item.$tab); 1438 this.hashesCollection.push(item.hash); 1439 }, 1440 buildLiveTabs: function () { 1441 var $layers = $(this.opts.live); 1442 1443 if ($layers.length === 0) { 1444 return; 1445 } 1446 1447 this.$liveTabsList = $('<ul />'); 1448 $layers.each($.proxy(this.buildLiveItem, this)); 1449 1450 this.$element.html('').append(this.$liveTabsList); 1451 1452 }, 1453 buildLiveItem: function (i, tab) { 1454 var $tab = $(tab); 1455 var $li = $('<li />'); 1456 var $a = $('<a />'); 1457 var index = i + 1; 1458 1459 $tab.attr('id', this.getLiveItemId($tab, index)); 1460 1461 var hash = '#' + $tab.attr('id'); 1462 var title = this.getLiveItemTitle($tab); 1463 1464 $a.attr('href', hash).attr('rel', hash).text(title); 1465 $li.append($a); 1466 1467 this.$liveTabsList.append($li); 1468 }, 1469 getLiveItemId: function ($tab, index) { 1470 return (typeof $tab.attr('id') === 'undefined') ? this.opts.live.replace('.', '') + index : $tab.attr('id'); 1471 }, 1472 getLiveItemTitle: function ($tab) { 1473 return (typeof $tab.attr('data-title') === 'undefined') ? $tab.attr('id') : $tab.attr('data-title'); 1474 }, 1475 setActiveItem: function () { 1476 if (this.currentHash) { 1477 this.currentItem = this.getItemBy(this.currentHash); 1478 this.opts.active = this.currentHash; 1479 } 1480 else if (this.opts.active === false) { 1481 this.currentItem = this.getItem(this.$items.first()); 1482 this.opts.active = this.currentItem.hash; 1483 } 1484 1485 this.addActive(this.currentItem); 1486 }, 1487 addActive: function (item) { 1488 item.$parent.addClass('active'); 1489 item.$tab.removeClass('hide').addClass('open'); 1490 1491 this.currentItem = item; 1492 }, 1493 removeActive: function (item) { 1494 item.$parent.removeClass('active'); 1495 item.$tab.addClass('hide').removeClass('open'); 1496 1497 this.currentItem = false; 1498 }, 1499 next: function (e) { 1500 if (e) e.preventDefault(); 1501 1502 var item = this.getItem(this.fetchElement('next')); 1503 1504 this.open(item.hash); 1505 this.callback('next', item); 1506 1507 }, 1508 prev: function (e) { 1509 if (e) e.preventDefault(); 1510 1511 var item = this.getItem(this.fetchElement('prev')); 1512 1513 this.open(item.hash); 1514 this.callback('prev', item); 1515 }, 1516 fetchElement: function (type) { 1517 var element; 1518 if (this.currentItem !== false) { 1519 // prev or next 1520 element = this.currentItem.$parent[type]().find('a'); 1521 1522 if (element.length === 0) { 1523 return; 1524 } 1525 } 1526 else { 1527 // first 1528 element = this.$items[0]; 1529 } 1530 1531 return element; 1532 }, 1533 open: function (e, push) { 1534 if (typeof e === 'undefined') return; 1535 if (typeof e === 'object') e.preventDefault(); 1536 1537 var item = (typeof e === 'object') ? this.getItem(e.target) : this.getItemBy(e); 1538 this.closeAll(); 1539 1540 this.callback('open', item); 1541 this.addActive(item); 1542 1543 // push state (doesn't need to push at the start) 1544 this.pushStateOpen(push, item); 1545 this.callback('opened', item); 1546 }, 1547 pushStateOpen: function (push, item) { 1548 if (push !== false && this.opts.hash !== false) { 1549 history.pushState(false, false, item.hash); 1550 } 1551 }, 1552 close: function (num) { 1553 var item = this.getItemBy(num); 1554 1555 if (!item.$parent.hasClass('active')) { 1556 return; 1557 } 1558 1559 this.callback('close', item); 1560 this.removeActive(item); 1561 this.pushStateClose(); 1562 this.callback('closed', item); 1563 1564 }, 1565 pushStateClose: function () { 1566 if (this.opts.hash !== false) { 1567 history.pushState(false, false, ' '); 1568 } 1569 }, 1570 closeAll: function () { 1571 this.$tabs.removeClass('open').addClass('hide'); 1572 this.$items.parent().removeClass('active'); 1573 }, 1574 getItem: function (element) { 1575 var item = {}; 1576 1577 item.$el = $(element); 1578 item.hash = item.$el.attr('href'); 1579 item.$parent = item.$el.parent(); 1580 item.$tab = $(item.hash); 1581 1582 return item; 1583 }, 1584 getItemBy: function (num) { 1585 var element = (typeof num === 'number') ? this.$items.eq(num - 1) : this.$element.find('[rel="' + num + '"]'); 1586 1587 return this.getItem(element); 1588 }, 1589 getLocationHash: function () { 1590 if (this.opts.hash === false) { 1591 return false; 1592 } 1593 1594 return (this.isHash()) ? top.location.hash : false; 1595 }, 1596 isHash: function () { 1597 return !(top.location.hash === '' || $.inArray(top.location.hash, this.hashesCollection) === -1); 1598 }, 1599 setItemHeight: function () { 1600 if (this.opts.equals) { 1601 var minHeight = this.getItemMaxHeight() + 'px'; 1602 this.$tabs.css('min-height', minHeight); 1603 } 1604 }, 1605 getItemMaxHeight: function () { 1606 var max = 0; 1607 this.$tabs.each(function () { 1608 var h = $(this).height(); 1609 max = h > max ? h : max; 1610 }); 1611 1612 return max; 1613 } 1614 }; 1615 1616 // Inheritance 1617 Kube.Tabs.inherits(Kube); 1618 1619 // Plugin 1620 Kube.Plugin.create('Tabs'); 1621 Kube.Plugin.autoload('Tabs'); 1622 1623 }(Kube)); 1624 /** 1625 * @library Kube Modal 1626 * @author Imperavi LLC 1627 * @license MIT 1628 */ 1629 (function ($) { 1630 $.modalcurrent = null; 1631 $.modalwindow = function (options) { 1632 var opts = $.extend({}, options, {show: true}); 1633 var $element = $('<span />'); 1634 1635 $element.modal(opts); 1636 }; 1637 1638 })(jQuery); 1639 1640 (function (Kube) { 1641 Kube.Modal = function (element, options) { 1642 this.namespace = 'modal'; 1643 this.defaults = { 1644 target: null, 1645 show: false, 1646 url: false, 1647 header: false, 1648 width: '600px', // string 1649 height: false, // or string 1650 maxHeight: false, 1651 position: 'center', // top or center 1652 overlay: true, 1653 appendForms: false, 1654 appendFields: false, 1655 animationOpen: 'show', 1656 animationClose: 'hide', 1657 callbacks: ['open', 'opened', 'close', 'closed'] 1658 }; 1659 1660 // Parent Constructor 1661 Kube.apply(this, arguments); 1662 1663 // Services 1664 this.utils = new Kube.Utils(); 1665 this.detect = new Kube.Detect(); 1666 1667 // Initialization 1668 this.start(); 1669 }; 1670 1671 // Functionality 1672 Kube.Modal.prototype = { 1673 start: function () { 1674 if (!this.hasTarget()) { 1675 return; 1676 } 1677 1678 if (this.opts.show) this.load(); 1679 else this.$element.on('click.' + this.namespace, $.proxy(this.load, this)); 1680 }, 1681 buildModal: function () { 1682 this.$modal = this.$target.find('.modal'); 1683 this.$header = this.$target.find('.modal-header'); 1684 this.$close = this.$target.find('.close'); 1685 this.$body = this.$target.find('.modal-body'); 1686 }, 1687 buildOverlay: function () { 1688 if (this.opts.overlay === false) { 1689 return; 1690 } 1691 1692 if ($('#modal-overlay').length !== 0) { 1693 this.$overlay = $('#modal-overlay'); 1694 } 1695 else { 1696 this.$overlay = $('<div id="modal-overlay">').addClass('hide'); 1697 $('body').prepend(this.$overlay); 1698 } 1699 1700 this.$overlay.addClass('overlay'); 1701 }, 1702 buildHeader: function () { 1703 if (this.opts.header) this.$header.html(this.opts.header); 1704 }, 1705 load: function (e) { 1706 this.buildModal(); 1707 this.buildOverlay(); 1708 this.buildHeader(); 1709 1710 if (this.opts.url) this.buildContent(); 1711 else this.open(e); 1712 }, 1713 open: function (e) { 1714 if (e) e.preventDefault(); 1715 1716 if (this.isOpened()) { 1717 return; 1718 } 1719 1720 if (this.detect.isMobile()) this.opts.width = '96%'; 1721 if (this.opts.overlay) this.$overlay.removeClass('hide'); 1722 1723 this.$target.removeClass('hide'); 1724 this.$modal.removeClass('hide'); 1725 1726 this.enableEvents(); 1727 this.findActions(); 1728 1729 this.resize(); 1730 $(window).on('resize.' + this.namespace, $.proxy(this.resize, this)); 1731 1732 if (this.detect.isDesktop()) this.utils.disableBodyScroll(); 1733 1734 // enter 1735 this.$modal.find('input[type=text],input[type=url],input[type=email]').on('keydown.' + this.namespace, $.proxy(this.handleEnter, this)); 1736 1737 this.callback('open'); 1738 this.$modal.animation(this.opts.animationOpen, $.proxy(this.onOpened, this)); 1739 }, 1740 close: function (e) { 1741 if (!this.$modal || !this.isOpened()) { 1742 return; 1743 } 1744 1745 if (e) { 1746 if (this.shouldNotBeClosed(e.target)) { 1747 return; 1748 } 1749 1750 e.preventDefault(); 1751 } 1752 1753 this.callback('close'); 1754 this.disableEvents(); 1755 1756 this.$modal.animation(this.opts.animationClose, $.proxy(this.onClosed, this)); 1757 1758 if (this.opts.overlay) this.$overlay.animation(this.opts.animationClose); 1759 }, 1760 onOpened: function () { 1761 this.$modal.addClass('open'); 1762 this.callback('opened'); 1763 1764 $.modalcurrent = this; 1765 }, 1766 onClosed: function () { 1767 this.callback('closed'); 1768 1769 this.$target.addClass('hide'); 1770 this.$modal.removeClass('open'); 1771 1772 if (this.detect.isDesktop()) this.utils.enableBodyScroll(); 1773 1774 this.$body.css('height', ''); 1775 $.modalcurrent = null; 1776 }, 1777 isOpened: function () { 1778 return (this.$modal.hasClass('open')); 1779 }, 1780 getData: function () { 1781 var formdata = new Kube.FormData(this); 1782 formdata.set(''); 1783 1784 return formdata.get(); 1785 }, 1786 buildContent: function () { 1787 $.ajax({ 1788 url: this.opts.url + '?' + new Date().getTime(), 1789 cache: false, 1790 type: 'post', 1791 data: this.getData(), 1792 success: $.proxy(function (data) { 1793 this.$body.html(data); 1794 this.open(); 1795 1796 }, this) 1797 }); 1798 }, 1799 buildWidth: function () { 1800 var width = this.opts.width; 1801 var top = '2%'; 1802 var bottom = '2%'; 1803 var percent = width.match(/%$/); 1804 1805 if ((parseInt(this.opts.width) > $(window).width()) && !percent) { 1806 width = '96%'; 1807 } 1808 else if (!percent) { 1809 top = '16px'; 1810 bottom = '16px'; 1811 } 1812 1813 this.$modal.css({'width': width, 'margin-top': top, 'margin-bottom': bottom}); 1814 1815 }, 1816 buildPosition: function () { 1817 if (this.opts.position !== 'center') { 1818 return; 1819 } 1820 1821 var windowHeight = $(window).height(); 1822 var height = this.$modal.outerHeight(); 1823 var top = (windowHeight / 2 - height / 2) + 'px'; 1824 1825 if (this.detect.isMobile()) top = '2%'; 1826 else if (height > windowHeight) top = '16px'; 1827 1828 this.$modal.css('margin-top', top); 1829 }, 1830 buildHeight: function () { 1831 var windowHeight = $(window).height(); 1832 1833 if (this.opts.maxHeight) { 1834 var padding = parseInt(this.$body.css('padding-top')) + parseInt(this.$body.css('padding-bottom')); 1835 var margin = parseInt(this.$modal.css('margin-top')) + parseInt(this.$modal.css('margin-bottom')); 1836 var height = windowHeight - this.$header.innerHeight() - padding - margin; 1837 1838 this.$body.height(height); 1839 } 1840 else if (this.opts.height !== false) { 1841 this.$body.css('height', this.opts.height); 1842 } 1843 1844 var modalHeight = this.$modal.outerHeight(); 1845 if (modalHeight > windowHeight) { 1846 this.opts.animationOpen = 'show'; 1847 this.opts.animationClose = 'hide'; 1848 } 1849 }, 1850 resize: function () { 1851 this.buildWidth(); 1852 this.buildPosition(); 1853 this.buildHeight(); 1854 }, 1855 enableEvents: function () { 1856 this.$close.on('click.' + this.namespace, $.proxy(this.close, this)); 1857 $(document).on('keyup.' + this.namespace, $.proxy(this.handleEscape, this)); 1858 this.$target.on('click.' + this.namespace, $.proxy(this.close, this)); 1859 }, 1860 disableEvents: function () { 1861 this.$close.off('.' + this.namespace); 1862 $(document).off('.' + this.namespace); 1863 this.$target.off('.' + this.namespace); 1864 $(window).off('.' + this.namespace); 1865 }, 1866 findActions: function () { 1867 this.$body.find('[data-action="modal-close"]').on('mousedown.' + this.namespace, $.proxy(this.close, this)); 1868 }, 1869 setHeader: function (header) { 1870 this.$header.html(header); 1871 }, 1872 setContent: function (content) { 1873 this.$body.html(content); 1874 }, 1875 setWidth: function (width) { 1876 this.opts.width = width; 1877 this.resize(); 1878 }, 1879 getModal: function () { 1880 return this.$modal; 1881 }, 1882 getBody: function () { 1883 return this.$body; 1884 }, 1885 getHeader: function () { 1886 return this.$header; 1887 }, 1888 handleEnter: function (e) { 1889 if (e.which === 13) { 1890 e.preventDefault(); 1891 this.close(false); 1892 } 1893 }, 1894 handleEscape: function (e) { 1895 return (e.which === 27) ? this.close(false) : true; 1896 }, 1897 shouldNotBeClosed: function (el) { 1898 if ($(el).attr('data-action') === 'modal-close' || el === this.$close[0]) { 1899 return false; 1900 } 1901 else if ($(el).closest('.modal').length === 0) { 1902 return false; 1903 } 1904 1905 return true; 1906 } 1907 }; 1908 1909 // Inheritance 1910 Kube.Modal.inherits(Kube); 1911 1912 // Plugin 1913 Kube.Plugin.create('Modal'); 1914 Kube.Plugin.autoload('Modal'); 1915 1916 }(Kube)); 1917 1918 })(jQuery)