1/**! 2 * MixItUp v3.3.1 3 * A high-performance, dependency-free library for animated filtering, sorting and more 4 * Build 94e0fbf6-cd0b-4987-b3c0-14b59b67b8a0 5 * 6 * @copyright Copyright 2014-2018 KunkaLabs Limited. 7 * @author KunkaLabs Limited. 8 * @link https://www.kunkalabs.com/mixitup/ 9 * 10 * @license Commercial use requires a commercial license. 11 * https://www.kunkalabs.com/mixitup/licenses/ 12 * 13 * Non-commercial use permitted under same terms as CC BY-NC 3.0 license. 14 * http://creativecommons.org/licenses/by-nc/3.0/
vendor: 52,211 bytes, lines 14-1794
14 15 */ 16 17(function(window) { 18 'use strict'; 19 20 var mixitup = null, 21 h = null; 22 23 (function() { 24 var VENDORS = ['webkit', 'moz', 'o', 'ms'], 25 canary = window.document.createElement('div'), 26 i = -1; 27 28 // window.requestAnimationFrame 29 30 for (i = 0; i < VENDORS.length && !window.requestAnimationFrame; i++) { 31 window.requestAnimationFrame = window[VENDORS[i] + 'RequestAnimationFrame']; 32 } 33 34 // Element.nextElementSibling 35 36 if (typeof canary.nextElementSibling === 'undefined') { 37 Object.defineProperty(window.Element.prototype, 'nextElementSibling', { 38 get: function() { 39 var el = this.nextSibling; 40 41 while (el) { 42 if (el.nodeType === 1) { 43 return el; 44 } 45 46 el = el.nextSibling; 47 } 48 49 return null; 50 } 51 }); 52 } 53 54 // Element.matches 55 56 (function(ElementPrototype) { 57 ElementPrototype.matches = 58 ElementPrototype.matches || 59 ElementPrototype.machesSelector || 60 ElementPrototype.mozMatchesSelector || 61 ElementPrototype.msMatchesSelector || 62 ElementPrototype.oMatchesSelector || 63 ElementPrototype.webkitMatchesSelector || 64 function (selector) { 65 return Array.prototype.indexOf.call(this.parentElement.querySelectorAll(selector), this) > -1; 66 }; 67 })(window.Element.prototype); 68 69 // Object.keys 70 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys 71 72 if (!Object.keys) { 73 Object.keys = (function() { 74 var hasOwnProperty = Object.prototype.hasOwnProperty, 75 hasDontEnumBug = false, 76 dontEnums = [], 77 dontEnumsLength = -1; 78 79 hasDontEnumBug = !({ 80 toString: null 81 }) 82 .propertyIsEnumerable('toString'); 83 84 dontEnums = [ 85 'toString', 86 'toLocaleString', 87 'valueOf', 88 'hasOwnProperty', 89 'isPrototypeOf', 90 'propertyIsEnumerable', 91 'constructor' 92 ]; 93 94 dontEnumsLength = dontEnums.length; 95 96 return function(obj) { 97 var result = [], 98 prop = '', 99 i = -1; 100 101 if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) { 102 throw new TypeError('Object.keys called on non-object'); 103 } 104 105 for (prop in obj) { 106 if (hasOwnProperty.call(obj, prop)) { 107 result.push(prop); 108 } 109 } 110 111 if (hasDontEnumBug) { 112 for (i = 0; i < dontEnumsLength; i++) { 113 if (hasOwnProperty.call(obj, dontEnums[i])) { 114 result.push(dontEnums[i]); 115 } 116 } 117 } 118 119 return result; 120 }; 121 }()); 122 } 123 124 // Array.isArray 125 // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray 126 127 if (!Array.isArray) { 128 Array.isArray = function(arg) { 129 return Object.prototype.toString.call(arg) === '[object Array]'; 130 }; 131 } 132 133 // Object.create 134 // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create 135 136 if (typeof Object.create !== 'function') { 137 Object.create = (function(undefined) { 138 var Temp = function() {}; 139 140 return function (prototype, propertiesObject) { 141 if (prototype !== Object(prototype) && prototype !== null) { 142 throw TypeError('Argument must be an object, or null'); 143 } 144 145 Temp.prototype = prototype || {}; 146 147 var result = new Temp(); 148 149 Temp.prototype = null; 150 151 if (propertiesObject !== undefined) { 152 Object.defineProperties(result, propertiesObject); 153 } 154 155 if (prototype === null) { 156 /* jshint ignore:start */ 157 result.__proto__ = null; 158 /* jshint ignore:end */ 159 } 160 161 return result; 162 }; 163 })(); 164 } 165 166 // String.prototyoe.trim 167 168 if (!String.prototype.trim) { 169 String.prototype.trim = function() { 170 return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 171 }; 172 } 173 174 // Array.prototype.indexOf 175 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf 176 177 if (!Array.prototype.indexOf) { 178 Array.prototype.indexOf = function(searchElement) { 179 var n, k, t, len; 180 181 if (this === null) { 182 throw new TypeError(); 183 } 184 185 t = Object(this); 186 187 len = t.length >>> 0; 188 189 if (len === 0) { 190 return -1; 191 } 192 193 n = 0; 194 195 if (arguments.length > 1) { 196 n = Number(arguments[1]); 197 198 if (n !== n) { 199 n = 0; 200 } else if (n !== 0 && n !== Infinity && n !== -Infinity) { 201 n = (n > 0 || -1) * Math.floor(Math.abs(n)); 202 } 203 } 204 205 if (n >= len) { 206 return -1; 207 } 208 209 for (k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); k < len; k++) { 210 if (k in t && t[k] === searchElement) { 211 return k; 212 } 213 } 214 215 return -1; 216 }; 217 } 218 219 // Function.prototype.bind 220 // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind 221 222 if (!Function.prototype.bind) { 223 Function.prototype.bind = function(oThis) { 224 var aArgs, self, FNOP, fBound; 225 226 if (typeof this !== 'function') { 227 throw new TypeError(); 228 } 229 230 aArgs = Array.prototype.slice.call(arguments, 1); 231 232 self = this; 233 234 FNOP = function() {}; 235 236 fBound = function() { 237 return self.apply(this instanceof FNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); 238 }; 239 240 if (this.prototype) { 241 FNOP.prototype = this.prototype; 242 } 243 244 fBound.prototype = new FNOP(); 245 246 return fBound; 247 }; 248 } 249 250 // Element.prototype.dispatchEvent 251 252 if (!window.Element.prototype.dispatchEvent) { 253 window.Element.prototype.dispatchEvent = function(event) { 254 try { 255 return this.fireEvent('on' + event.type, event); 256 } catch (err) {} 257 }; 258 } 259 })(); 260 261 /** 262 * The `mixitup()` "factory" function creates and returns individual instances 263 * of MixItUp, known as "mixers", on which API methods can be called. 264 * 265 * When loading MixItUp via a script tag, the factory function is accessed 266 * via the global variable `mixitup`. When using a module loading 267 * system (e.g. ES2015, CommonJS, RequireJS), the factory function is 268 * exported into your module when you require the MixItUp library. 269 * 270 * @example 271 * mixitup(container [,config] [,foreignDoc]) 272 * 273 * @example <caption>Example 1: Creating a mixer instance with an element reference</caption> 274 * var containerEl = document.querySelector('.container'); 275 * 276 * var mixer = mixitup(containerEl); 277 * 278 * @example <caption>Example 2: Creating a mixer instance with a selector string</caption> 279 * var mixer = mixitup('.container'); 280 * 281 * @example <caption>Example 3: Passing a configuration object</caption> 282 * var mixer = mixitup(containerEl, { 283 * animation: { 284 * effects: 'fade scale(0.5)' 285 * } 286 * }); 287 * 288 * @example <caption>Example 4: Passing an iframe reference</caption> 289 * var mixer = mixitup(containerEl, config, foreignDocument); 290 * 291 * @global 292 * @namespace 293 * @public 294 * @kind function 295 * @since 3.0.0 296 * @param {(Element|string)} container 297 * A DOM element or selector string representing the container(s) on which to instantiate MixItUp. 298 * @param {object} [config] 299 * An optional "configuration object" used to customize the behavior of the MixItUp instance. 300 * @param {object} [foreignDoc] 301 * An optional reference to a `document`, which can be used to control a MixItUp instance in an iframe. 302 * @return {mixitup.Mixer} 303 * A "mixer" object holding the MixItUp instance. 304 */ 305 306 mixitup = function(container, config, foreignDoc) { 307 var el = null, 308 returnCollection = false, 309 instance = null, 310 facade = null, 311 doc = null, 312 output = null, 313 instances = [], 314 id = '', 315 elements = [], 316 i = -1; 317 318 doc = foreignDoc || window.document; 319 320 if (returnCollection = arguments[3]) { 321 // A non-documented 4th paramater enabling control of multiple instances 322 323 returnCollection = typeof returnCollection === 'boolean'; 324 } 325 326 if (typeof container === 'string') { 327 elements = doc.querySelectorAll(container); 328 } else if (container && typeof container === 'object' && h.isElement(container, doc)) { 329 elements = [container]; 330 } else if (container && typeof container === 'object' && container.length) { 331 // Although not documented, the container may also be an array-like list of 332 // elements such as a NodeList or jQuery collection, is returnCollection is true 333 334 elements = container; 335 } else { 336 throw new Error(mixitup.messages.errorFactoryInvalidContainer()); 337 } 338 339 if (elements.length < 1) { 340 throw new Error(mixitup.messages.errorFactoryContainerNotFound()); 341 } 342 343 for (i = 0; el = elements[i]; i++) { 344 if (i > 0 && !returnCollection) break; 345 346 if (!el.id) { 347 id = 'MixItUp' + h.randomHex(); 348 349 el.id = id; 350 } else { 351 id = el.id; 352 } 353 354 if (mixitup.instances[id] instanceof mixitup.Mixer) { 355 instance = mixitup.instances[id]; 356 357 if (!config || (config && config.debug && config.debug.showWarnings !== false)) { 358 console.warn(mixitup.messages.warningFactoryPreexistingInstance()); 359 } 360 } else { 361 instance = new mixitup.Mixer(); 362 363 instance.attach(el, doc, id, config); 364 365 mixitup.instances[id] = instance; 366 } 367 368 facade = new mixitup.Facade(instance); 369 370 if (config && config.debug && config.debug.enable) { 371 instances.push(instance); 372 } else { 373 instances.push(facade); 374 } 375 } 376 377 if (returnCollection) { 378 output = new mixitup.Collection(instances); 379 } else { 380 // Return the first instance regardless 381 382 output = instances[0]; 383 } 384 385 return output; 386 }; 387 388 /** 389 * The `.use()` static method is used to extend the functionality of mixitup with compatible 390 * extensions and libraries in an environment with modular scoping e.g. ES2015, CommonJS, or RequireJS. 391 * 392 * You need only call the `.use()` function once per project, per extension, as module loaders 393 * will cache a single reference to MixItUp inclusive of all changes made. 394 * 395 * @example 396 * mixitup.use(extension) 397 * 398 * @example <caption>Example 1: Extending MixItUp with the Pagination Extension</caption> 399 * 400 * import mixitup from 'mixitup'; 401 * import mixitupPagination from 'mixitup-pagination'; 402 * 403 * mixitup.use(mixitupPagination); 404 * 405 * // All mixers created by the factory function in all modules will now 406 * // have pagination functionality 407 * 408 * var mixer = mixitup('.container'); 409 * 410 * @public 411 * @name use 412 * @memberof mixitup 413 * @kind function 414 * @static 415 * @since 3.0.0 416 * @param {*} extension A reference to the extension or library to be used. 417 * @return {void} 418 */ 419 420 mixitup.use = function(extension) { 421 mixitup.Base.prototype.callActions.call(mixitup, 'beforeUse', arguments); 422 423 // Call the extension's factory function, passing 424 // the mixitup factory as a paramater 425 426 if (typeof extension === 'function' && extension.TYPE === 'mixitup-extension') { 427 // Mixitup extension 428 429 if (typeof mixitup.extensions[extension.NAME] === 'undefined') { 430 extension(mixitup); 431 432 mixitup.extensions[extension.NAME] = extension; 433 } 434 } else if (extension.fn && extension.fn.jquery) { 435 // jQuery 436 437 mixitup.libraries.$ = extension; 438 } 439 440 mixitup.Base.prototype.callActions.call(mixitup, 'afterUse', arguments); 441 }; 442 443 mixitup.instances = {}; 444 mixitup.extensions = {}; 445 mixitup.libraries = {}; 446 447 /** 448 * @private 449 */ 450 451 h = { 452 453 /** 454 * @private 455 * @param {HTMLElement} el 456 * @param {string} cls 457 * @return {boolean} 458 */ 459 460 hasClass: function(el, cls) { 461 return !!el.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); 462 }, 463 464 /** 465 * @private 466 * @param {HTMLElement} el 467 * @param {string} cls 468 * @return {void} 469 */ 470 471 addClass: function(el, cls) { 472 if (!this.hasClass(el, cls)) el.className += el.className ? ' ' + cls : cls; 473 }, 474 475 /** 476 * @private 477 * @param {HTMLElement} el 478 * @param {string} cls 479 * @return {void} 480 */ 481 482 removeClass: function(el, cls) { 483 if (this.hasClass(el, cls)) { 484 var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); 485 486 el.className = el.className.replace(reg, ' ').trim(); 487 } 488 }, 489 490 /** 491 * Merges the properties of the source object onto the 492 * target object. Alters the target object. 493 * 494 * @private 495 * @param {object} destination 496 * @param {object} source 497 * @param {boolean} [deep=false] 498 * @param {boolean} [handleErrors=false] 499 * @return {void} 500 */ 501 502 extend: function(destination, source, deep, handleErrors) { 503 var sourceKeys = [], 504 key = '', 505 i = -1; 506 507 deep = deep || false; 508 handleErrors = handleErrors || false; 509 510 try { 511 if (Array.isArray(source)) { 512 for (i = 0; i < source.length; i++) { 513 sourceKeys.push(i); 514 } 515 } else if (source) { 516 sourceKeys = Object.keys(source); 517 } 518 519 for (i = 0; i < sourceKeys.length; i++) { 520 key = sourceKeys[i]; 521 522 if (!deep || typeof source[key] !== 'object' || this.isElement(source[key])) { 523 // All non-object properties, or all properties if shallow extend 524 525 destination[key] = source[key]; 526 } else if (Array.isArray(source[key])) { 527 // Arrays 528 529 if (!destination[key]) { 530 destination[key] = []; 531 } 532 533 this.extend(destination[key], source[key], deep, handleErrors); 534 } else { 535 // Objects 536 537 if (!destination[key]) { 538 destination[key] = {}; 539 } 540 541 this.extend(destination[key], source[key], deep, handleErrors); 542 } 543 } 544 } catch(err) { 545 if (handleErrors) { 546 this.handleExtendError(err, destination); 547 } else { 548 throw err; 549 } 550 } 551 552 return destination; 553 }, 554 555 /** 556 * @private 557 * @param {Error} err 558 * @param {object} destination 559 * @return {void} 560 */ 561 562 handleExtendError: function(err, destination) { 563 var re = /property "?(\w*)"?[,:] object/i, 564 matches = null, 565 erroneous = '', 566 message = '', 567 suggestion = '', 568 probableMatch = '', 569 key = '', 570 mostMatchingChars = -1, 571 i = -1; 572 573 if (err instanceof TypeError && (matches = re.exec(err.message))) { 574 erroneous = matches[1]; 575 576 for (key in destination) { 577 i = 0; 578 579 while (i < erroneous.length && erroneous.charAt(i) === key.charAt(i)) { 580 i++; 581 } 582 583 if (i > mostMatchingChars) { 584 mostMatchingChars = i; 585 probableMatch = key; 586 } 587 } 588 589 if (mostMatchingChars > 1) { 590 suggestion = mixitup.messages.errorConfigInvalidPropertySuggestion({ 591 probableMatch: probableMatch 592 }); 593 } 594 595 message = mixitup.messages.errorConfigInvalidProperty({ 596 erroneous: erroneous, 597 suggestion: suggestion 598 }); 599 600 throw new TypeError(message); 601 } 602 603 throw err; 604 }, 605 606 /** 607 * @private 608 * @param {string} str 609 * @return {function} 610 */ 611 612 template: function(str) { 613 var re = /\${([\w]*)}/g, 614 dynamics = {}, 615 matches = null; 616 617 while ((matches = re.exec(str))) { 618 dynamics[matches[1]] = new RegExp('\\${' + matches[1] + '}', 'g'); 619 } 620 621 return function(data) { 622 var key = '', 623 output = str; 624 625 data = data || {}; 626 627 for (key in dynamics) { 628 output = output.replace(dynamics[key], typeof data[key] !== 'undefined' ? data[key] : ''); 629 } 630 631 return output; 632 }; 633 }, 634 635 /** 636 * @private 637 * @param {HTMLElement} el 638 * @param {string} type 639 * @param {function} fn 640 * @param {boolean} useCapture 641 * @return {void} 642 */ 643 644 on: function(el, type, fn, useCapture) { 645 if (!el) return; 646 647 if (el.addEventListener) { 648 el.addEventListener(type, fn, useCapture); 649 } else if (el.attachEvent) { 650 el['e' + type + fn] = fn; 651 652 el[type + fn] = function() { 653 el['e' + type + fn](window.event); 654 }; 655 656 el.attachEvent('on' + type, el[type + fn]); 657 } 658 }, 659 660 /** 661 * @private 662 * @param {HTMLElement} el 663 * @param {string} type 664 * @param {function} fn 665 * @return {void} 666 */ 667 668 off: function(el, type, fn) { 669 if (!el) return; 670 671 if (el.removeEventListener) { 672 el.removeEventListener(type, fn, false); 673 } else if (el.detachEvent) { 674 el.detachEvent('on' + type, el[type + fn]); 675 el[type + fn] = null; 676 } 677 }, 678 679 /** 680 * @private 681 * @param {string} eventType 682 * @param {object} detail 683 * @param {Document} [doc] 684 * @return {CustomEvent} 685 */ 686 687 getCustomEvent: function(eventType, detail, doc) { 688 var event = null; 689 690 doc = doc || window.document; 691 692 if (typeof window.CustomEvent === 'function') { 693 event = new window.CustomEvent(eventType, { 694 detail: detail, 695 bubbles: true, 696 cancelable: true 697 }); 698 } else if (typeof doc.createEvent === 'function') { 699 event = doc.createEvent('CustomEvent'); 700 event.initCustomEvent(eventType, true, true, detail); 701 } else { 702 event = doc.createEventObject(), 703 event.type = eventType; 704 705 event.returnValue = false; 706 event.cancelBubble = false; 707 event.detail = detail; 708 } 709 710 return event; 711 }, 712 713 /** 714 * @private 715 * @param {Event} e 716 * @return {Event} 717 */ 718 719 getOriginalEvent: function(e) { 720 if (e.touches && e.touches.length) { 721 return e.touches[0]; 722 } else if (e.changedTouches && e.changedTouches.length) { 723 return e.changedTouches[0]; 724 } else { 725 return e; 726 } 727 }, 728 729 /** 730 * @private 731 * @param {HTMLElement} el 732 * @param {string} selector 733 * @return {Number} 734 */ 735 736 index: function(el, selector) { 737 var i = 0; 738 739 while ((el = el.previousElementSibling) !== null) { 740 if (!selector || el.matches(selector)) { 741 ++i; 742 } 743 } 744 745 return i; 746 }, 747 748 /** 749 * Converts a dash or snake-case string to camel case. 750 * 751 * @private 752 * @param {string} str 753 * @param {boolean} [isPascal] 754 * @return {string} 755 */ 756 757 camelCase: function(str) { 758 return str.toLowerCase().replace(/([_-][a-z])/g, function($1) { 759 return $1.toUpperCase().replace(/[_-]/, ''); 760 }); 761 }, 762 763 /** 764 * Converts a dash or snake-case string to pascal case. 765 * 766 * @private 767 * @param {string} str 768 * @param {boolean} [isPascal] 769 * @return {string} 770 */ 771 772 pascalCase: function(str) { 773 return (str = this.camelCase(str)).charAt(0).toUpperCase() + str.slice(1); 774 }, 775 776 /** 777 * Converts a camel or pascal-case string to dash case. 778 * 779 * @private 780 * @param {string} str 781 * @return {string} 782 */ 783 784 dashCase: function(str) { 785 return str.replace(/([A-Z])/g, '-$1').replace(/^-/, '').toLowerCase(); 786 }, 787 788 /** 789 * @private 790 * @param {HTMLElement} el 791 * @param {HTMLHtmlElement} [doc] 792 * @return {boolean} 793 */ 794 795 isElement: function(el, doc) { 796 doc = doc || window.document; 797 798 if ( 799 window.HTMLElement && 800 el instanceof window.HTMLElement 801 ) { 802 return true; 803 } else if ( 804 doc.defaultView && 805 doc.defaultView.HTMLElement && 806 el instanceof doc.defaultView.HTMLElement 807 ) { 808 return true; 809 } else { 810 return ( 811 el !== null && 812 el.nodeType === 1 && 813 typeof el.nodeName === 'string' 814 ); 815 } 816 }, 817 818 /** 819 * @private 820 * @param {string} htmlString 821 * @param {HTMLHtmlElement} [doc] 822 * @return {DocumentFragment} 823 */ 824 825 createElement: function(htmlString, doc) { 826 var frag = null, 827 temp = null; 828 829 doc = doc || window.document; 830 831 frag = doc.createDocumentFragment(); 832 temp = doc.createElement('div'); 833 834 temp.innerHTML = htmlString.trim(); 835 836 while (temp.firstChild) { 837 frag.appendChild(temp.firstChild); 838 } 839 840 return frag; 841 }, 842 843 /** 844 * @private 845 * @param {Node} node 846 * @return {void} 847 */ 848 849 removeWhitespace: function(node) { 850 var deleting; 851 852 while (node && node.nodeName === '#text') { 853 deleting = node; 854 855 node = node.previousSibling; 856 857 deleting.parentElement && deleting.parentElement.removeChild(deleting); 858 } 859 }, 860 861 /** 862 * @private 863 * @param {Array<*>} a 864 * @param {Array<*>} b 865 * @return {boolean} 866 */ 867 868 isEqualArray: function(a, b) { 869 var i = a.length; 870 871 if (i !== b.length) return false; 872 873 while (i--) { 874 if (a[i] !== b[i]) return false; 875 } 876 877 return true; 878 }, 879 880 /** 881 * @private 882 * @param {object} a 883 * @param {object} b 884 * @return {boolean} 885 */ 886 887 deepEquals: function(a, b) { 888 var key; 889 890 if (typeof a === 'object' && a && typeof b === 'object' && b) { 891 if (Object.keys(a).length !== Object.keys(b).length) return false; 892 893 for (key in a) { 894 if (!b.hasOwnProperty(key) || !this.deepEquals(a[key], b[key])) return false; 895 } 896 } else if (a !== b) { 897 return false; 898 } 899 900 return true; 901 }, 902 903 /** 904 * @private 905 * @param {Array<*>} oldArray 906 * @return {Array<*>} 907 */ 908 909 arrayShuffle: function(oldArray) { 910 var newArray = oldArray.slice(), 911 len = newArray.length, 912 i = len, 913 p = -1, 914 t = []; 915 916 while (i--) { 917 p = ~~(Math.random() * len); 918 t = newArray[i]; 919 920 newArray[i] = newArray[p]; 921 newArray[p] = t; 922 } 923 924 return newArray; 925 }, 926 927 /** 928 * @private 929 * @param {object} list 930 */ 931 932 arrayFromList: function(list) { 933 var output, i; 934 935 try { 936 return Array.prototype.slice.call(list); 937 } catch(err) { 938 output = []; 939 940 for (i = 0; i < list.length; i++) { 941 output.push(list[i]); 942 } 943 944 return output; 945 } 946 }, 947 948 /** 949 * @private 950 * @param {function} func 951 * @param {Number} wait 952 * @param {boolean} immediate 953 * @return {function} 954 */ 955 956 debounce: function(func, wait, immediate) { 957 var timeout; 958 959 return function() { 960 var self = this, 961 args = arguments, 962 callNow = immediate && !timeout, 963 later = null; 964 965 later = function() { 966 timeout = null; 967 968 if (!immediate) { 969 func.apply(self, args); 970 } 971 }; 972 973 clearTimeout(timeout); 974 975 timeout = setTimeout(later, wait); 976 977 if (callNow) func.apply(self, args); 978 }; 979 }, 980 981 /** 982 * @private 983 * @param {HTMLElement} element 984 * @return {object} 985 */ 986 987 position: function(element) { 988 var xPosition = 0, 989 yPosition = 0, 990 offsetParent = element; 991 992 while (element) { 993 xPosition -= element.scrollLeft; 994 yPosition -= element.scrollTop; 995 996 if (element === offsetParent) { 997 xPosition += element.offsetLeft; 998 yPosition += element.offsetTop; 999 1000 offsetParent = element.offsetParent; 1001 } 1002 1003 element = element.parentElement; 1004 } 1005 1006 return { 1007 x: xPosition, 1008 y: yPosition 1009 }; 1010 }, 1011 1012 /** 1013 * @private 1014 * @param {object} node1 1015 * @param {object} node2 1016 * @return {Number} 1017 */ 1018 1019 getHypotenuse: function(node1, node2) { 1020 var distanceX = node1.x - node2.x, 1021 distanceY = node1.y - node2.y; 1022 1023 distanceX = distanceX < 0 ? distanceX * -1 : distanceX, 1024 distanceY = distanceY < 0 ? distanceY * -1 : distanceY; 1025 1026 return Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2)); 1027 }, 1028 1029 /** 1030 * Calcuates the area of intersection between two rectangles and expresses it as 1031 * a ratio in comparison to the area of the first rectangle. 1032 * 1033 * @private 1034 * @param {Rect} box1 1035 * @param {Rect} box2 1036 * @return {number} 1037 */ 1038 1039 getIntersectionRatio: function(box1, box2) { 1040 var controlArea = box1.width * box1.height, 1041 intersectionX = -1, 1042 intersectionY = -1, 1043 intersectionArea = -1, 1044 ratio = -1; 1045 1046 intersectionX = 1047 Math.max(0, Math.min(box1.left + box1.width, box2.left + box2.width) - Math.max(box1.left, box2.left)); 1048 1049 intersectionY = 1050 Math.max(0, Math.min(box1.top + box1.height, box2.top + box2.height) - Math.max(box1.top, box2.top)); 1051 1052 intersectionArea = intersectionY * intersectionX; 1053 1054 ratio = intersectionArea / controlArea; 1055 1056 return ratio; 1057 }, 1058 1059 /** 1060 * @private 1061 * @param {object} el 1062 * @param {string} selector 1063 * @param {boolean} [includeSelf] 1064 * @param {HTMLHtmlElement} [doc] 1065 * @return {Element|null} 1066 */ 1067 1068 closestParent: function(el, selector, includeSelf, doc) { 1069 var parent = el.parentNode; 1070 1071 doc = doc || window.document; 1072 1073 if (includeSelf && el.matches(selector)) { 1074 return el; 1075 } 1076 1077 while (parent && parent != doc.body) { 1078 if (parent.matches && parent.matches(selector)) { 1079 return parent; 1080 } else if (parent.parentNode) { 1081 parent = parent.parentNode; 1082 } else { 1083 return null; 1084 } 1085 } 1086 1087 return null; 1088 }, 1089 1090 /** 1091 * @private 1092 * @param {HTMLElement} el 1093 * @param {string} selector 1094 * @param {HTMLHtmlElement} [doc] 1095 * @return {NodeList} 1096 */ 1097 1098 children: function(el, selector, doc) { 1099 var children = [], 1100 tempId = ''; 1101 1102 doc = doc || window.doc; 1103 1104 if (el) { 1105 if (!el.id) { 1106 tempId = 'Temp' + this.randomHexKey(); 1107 1108 el.id = tempId; 1109 } 1110 1111 children = doc.querySelectorAll('#' + el.id + ' > ' + selector); 1112 1113 if (tempId) { 1114 el.removeAttribute('id'); 1115 } 1116 } 1117 1118 return children; 1119 }, 1120 1121 /** 1122 * Creates a clone of a provided array, with any empty strings removed. 1123 * 1124 * @private 1125 * @param {Array<*>} originalArray 1126 * @return {Array<*>} 1127 */ 1128 1129 clean: function(originalArray) { 1130 var cleanArray = [], 1131 i = -1; 1132 1133 for (i = 0; i < originalArray.length; i++) { 1134 if (originalArray[i] !== '') { 1135 cleanArray.push(originalArray[i]); 1136 } 1137 } 1138 1139 return cleanArray; 1140 }, 1141 1142 /** 1143 * Abstracts an ES6 promise into a q-like deferred interface for storage and deferred resolution. 1144 * 1145 * @private 1146 * @param {object} libraries 1147 * @return {h.Deferred} 1148 */ 1149 1150 defer: function(libraries) { 1151 var deferred = null, 1152 promiseWrapper = null, 1153 $ = null; 1154 1155 promiseWrapper = new this.Deferred(); 1156 1157 if (mixitup.features.has.promises) { 1158 // ES6 native promise or polyfill 1159 1160 promiseWrapper.promise = new Promise(function(resolve, reject) { 1161 promiseWrapper.resolve = resolve; 1162 promiseWrapper.reject = reject; 1163 }); 1164 } else if (($ = (window.jQuery || libraries.$)) && typeof $.Deferred === 'function') { 1165 // jQuery 1166 1167 deferred = $.Deferred(); 1168 1169 promiseWrapper.promise = deferred.promise(); 1170 promiseWrapper.resolve = deferred.resolve; 1171 promiseWrapper.reject = deferred.reject; 1172 } else if (window.console) { 1173 // No implementation 1174 1175 console.warn(mixitup.messages.warningNoPromiseImplementation()); 1176 } 1177 1178 return promiseWrapper; 1179 }, 1180 1181 /** 1182 * @private 1183 * @param {Array<Promise>} tasks 1184 * @param {object} libraries 1185 * @return {Promise<Array>} 1186 */ 1187 1188 all: function(tasks, libraries) { 1189 var $ = null; 1190 1191 if (mixitup.features.has.promises) { 1192 return Promise.all(tasks); 1193 } else if (($ = (window.jQuery || libraries.$)) && typeof $.when === 'function') { 1194 return $.when.apply($, tasks) 1195 .done(function() { 1196 // jQuery when returns spread arguments rather than an array or resolutions 1197 1198 return arguments; 1199 }); 1200 } 1201 1202 // No implementation 1203 1204 if (window.console) { 1205 console.warn(mixitup.messages.warningNoPromiseImplementation()); 1206 } 1207 1208 return []; 1209 }, 1210 1211 /** 1212 * @private 1213 * @param {HTMLElement} el 1214 * @param {string} property 1215 * @param {Array<string>} vendors 1216 * @return {string} 1217 */ 1218 1219 getPrefix: function(el, property, vendors) { 1220 var i = -1, 1221 prefix = ''; 1222 1223 if (h.dashCase(property) in el.style) return ''; 1224 1225 for (i = 0; prefix = vendors[i]; i++) { 1226 if (prefix + property in el.style) { 1227 return prefix.toLowerCase(); 1228 } 1229 } 1230 1231 return 'unsupported'; 1232 }, 1233 1234 /** 1235 * @private 1236 * @return {string} 1237 */ 1238 1239 randomHex: function() { 1240 return ('00000' + (Math.random() * 16777216 << 0).toString(16)).substr(-6).toUpperCase(); 1241 }, 1242 1243 /** 1244 * @private 1245 * @param {HTMLDocument} [doc] 1246 * @return {object} 1247 */ 1248 1249 getDocumentState: function(doc) { 1250 doc = typeof doc.body === 'object' ? doc : window.document; 1251 1252 return { 1253 scrollTop: window.pageYOffset, 1254 scrollLeft: window.pageXOffset, 1255 docHeight: doc.documentElement.scrollHeight, 1256 docWidth: doc.documentElement.scrollWidth, 1257 viewportHeight: doc.documentElement.clientHeight, 1258 viewportWidth: doc.documentElement.clientWidth 1259 }; 1260 }, 1261 1262 /** 1263 * @private 1264 * @param {object} obj 1265 * @param {function} fn 1266 * @return {function} 1267 */ 1268 1269 bind: function(obj, fn) { 1270 return function() { 1271 return fn.apply(obj, arguments); 1272 }; 1273 }, 1274 1275 /** 1276 * @private 1277 * @param {HTMLElement} el 1278 * @return {boolean} 1279 */ 1280 1281 isVisible: function(el) { 1282 var styles = null; 1283 1284 if (el.offsetParent) return true; 1285 1286 styles = window.getComputedStyle(el); 1287 1288 if ( 1289 styles.position === 'fixed' && 1290 styles.visibility !== 'hidden' && 1291 styles.opacity !== '0' 1292 ) { 1293 // Fixed elements report no offsetParent, 1294 // but may still be invisible 1295 1296 return true; 1297 } 1298 1299 return false; 1300 }, 1301 1302 /** 1303 * @private 1304 * @param {object} obj 1305 */ 1306 1307 seal: function(obj) { 1308 if (typeof Object.seal === 'function') { 1309 Object.seal(obj); 1310 } 1311 }, 1312 1313 /** 1314 * @private 1315 * @param {object} obj 1316 */ 1317 1318 freeze: function(obj) { 1319 if (typeof Object.freeze === 'function') { 1320 Object.freeze(obj); 1321 } 1322 }, 1323 1324 /** 1325 * @private 1326 * @param {string} control 1327 * @param {string} specimen 1328 * @return {boolean} 1329 */ 1330 1331 compareVersions: function(control, specimen) { 1332 var controlParts = control.split('.'), 1333 specimenParts = specimen.split('.'), 1334 controlPart = -1, 1335 specimenPart = -1, 1336 i = -1; 1337 1338 for (i = 0; i < controlParts.length; i++) { 1339 controlPart = parseInt(controlParts[i].replace(/[^\d.]/g, '')); 1340 specimenPart = parseInt(specimenParts[i].replace(/[^\d.]/g, '') || 0); 1341 1342 if (specimenPart < controlPart) { 1343 return false; 1344 } else if (specimenPart > controlPart) { 1345 return true; 1346 } 1347 } 1348 1349 return true; 1350 }, 1351 1352 /** 1353 * @private 1354 * @constructor 1355 */ 1356 1357 Deferred: function() { 1358 this.promise = null; 1359 this.resolve = null; 1360 this.reject = null; 1361 this.id = h.randomHex(); 1362 }, 1363 1364 /** 1365 * @private 1366 * @param {object} obj 1367 * @return {boolean} 1368 */ 1369 1370 isEmptyObject: function(obj) { 1371 var key = ''; 1372 1373 if (typeof Object.keys === 'function') { 1374 return Object.keys(obj).length === 0; 1375 } 1376 1377 for (key in obj) { 1378 if (obj.hasOwnProperty(key)) { 1379 return false; 1380 } 1381 } 1382 1383 return true; 1384 }, 1385 1386 /** 1387 * @param {mixitup.Config.ClassNames} classNames 1388 * @param {string} elementName 1389 * @param {string} [modifier] 1390 * @return {string} 1391 */ 1392 1393 getClassname: function(classNames, elementName, modifier) { 1394 var classname = ''; 1395 1396 classname += classNames.block; 1397 1398 if (classname.length) { 1399 classname += classNames.delineatorElement; 1400 } 1401 1402 classname += classNames['element' + this.pascalCase(elementName)]; 1403 1404 if (!modifier) return classname; 1405 1406 if (classname.length) { 1407 classname += classNames.delineatorModifier; 1408 } 1409 1410 classname += modifier; 1411 1412 return classname; 1413 }, 1414 1415 /** 1416 * Returns the value of a property on a given object via its string key. 1417 * 1418 * @param {object} obj 1419 * @param {string} stringKey 1420 * @return {*} value 1421 */ 1422 1423 getProperty: function(obj, stringKey) { 1424 var parts = stringKey.split('.'), 1425 returnCurrent = null, 1426 current = '', 1427 i = 0; 1428 1429 if (!stringKey) { 1430 return obj; 1431 } 1432 1433 returnCurrent = function(obj) { 1434 if (!obj) { 1435 return null; 1436 } else { 1437 return obj[current]; 1438 } 1439 }; 1440 1441 while (i < parts.length) { 1442 current = parts[i]; 1443 1444 obj = returnCurrent(obj); 1445 1446 i++; 1447 } 1448 1449 if (typeof obj !== 'undefined') { 1450 return obj; 1451 } else { 1452 return null; 1453 } 1454 } 1455 }; 1456 1457 mixitup.h = h; 1458 1459 /** 1460 * The Base class adds instance methods to all other extensible MixItUp classes, 1461 * enabling the calling of any registered hooks. 1462 * 1463 * @constructor 1464 * @namespace 1465 * @memberof mixitup 1466 * @private 1467 * @since 3.0.0 1468 */ 1469 1470 mixitup.Base = function() {}; 1471 1472 mixitup.Base.prototype = { 1473 constructor: mixitup.Base, 1474 1475 /** 1476 * Calls any registered hooks for the provided action. 1477 * 1478 * @memberof mixitup.Base 1479 * @private 1480 * @instance 1481 * @since 2.0.0 1482 * @param {string} actionName 1483 * @param {Array<*>} args 1484 * @return {void} 1485 */ 1486 1487 callActions: function(actionName, args) { 1488 var self = this, 1489 hooks = self.constructor.actions[actionName], 1490 extensionName = ''; 1491 1492 if (!hooks || h.isEmptyObject(hooks)) return; 1493 1494 for (extensionName in hooks) { 1495 hooks[extensionName].apply(self, args); 1496 } 1497 }, 1498 1499 /** 1500 * Calls any registered hooks for the provided filter. 1501 * 1502 * @memberof mixitup.Base 1503 * @private 1504 * @instance 1505 * @since 2.0.0 1506 * @param {string} filterName 1507 * @param {*} input 1508 * @param {Array<*>} args 1509 * @return {*} 1510 */ 1511 1512 callFilters: function(filterName, input, args) { 1513 var self = this, 1514 hooks = self.constructor.filters[filterName], 1515 output = input, 1516 extensionName = ''; 1517 1518 if (!hooks || h.isEmptyObject(hooks)) return output; 1519 1520 args = args || []; 1521 1522 for (extensionName in hooks) { 1523 args = h.arrayFromList(args); 1524 1525 args.unshift(output); 1526 1527 output = hooks[extensionName].apply(self, args); 1528 } 1529 1530 return output; 1531 } 1532 }; 1533 1534 /** 1535 * The BaseStatic class holds a set of static methods which are then added to all other 1536 * extensible MixItUp classes as a means of integrating extensions via the addition of new 1537 * methods and/or actions and hooks. 1538 * 1539 * @constructor 1540 * @namespace 1541 * @memberof mixitup 1542 * @private 1543 * @since 3.0.0 1544 */ 1545 1546 mixitup.BaseStatic = function() { 1547 this.actions = {}; 1548 this.filters = {}; 1549 1550 /** 1551 * Performs a shallow extend on the class's prototype, adding one or more new members to 1552 * the class in a single operation. 1553 * 1554 * @memberof mixitup.BaseStatic 1555 * @public 1556 * @static 1557 * @since 2.1.0 1558 * @param {object} extension 1559 * @return {void} 1560 */ 1561 1562 this.extend = function(extension) { 1563 h.extend(this.prototype, extension); 1564 }; 1565 1566 /** 1567 * Registers a function to be called on the action hook of the provided name. 1568 * 1569 * @memberof mixitup.BaseStatic 1570 * @public 1571 * @static 1572 * @since 2.1.0 1573 * @param {string} hookName 1574 * @param {string} extensionName 1575 * @param {function} func 1576 * @return {void} 1577 */ 1578 1579 this.registerAction = function(hookName, extensionName, func) { 1580 (this.actions[hookName] = this.actions[hookName] || {})[extensionName] = func; 1581 }; 1582 1583 /** 1584 * Registers a function to be called on the filter of the provided name. 1585 * 1586 * @memberof mixitup.BaseStatic 1587 * @public 1588 * @static 1589 * @since 2.1.0 1590 * @param {string} hookName 1591 * @param {string} extensionName 1592 * @param {function} func 1593 * @return {void} 1594 */ 1595 1596 this.registerFilter = function(hookName, extensionName, func) { 1597 (this.filters[hookName] = this.filters[hookName] || {})[extensionName] = func; 1598 }; 1599 }; 1600 1601 /** 1602 * The `mixitup.Features` class performs all feature and CSS prefix detection 1603 * neccessary for MixItUp to function correctly, as well as storing various 1604 * string and array constants. All feature decection is on evaluation of the 1605 * library and stored in a singleton instance for use by other internal classes. 1606 * 1607 * @constructor 1608 * @namespace 1609 * @memberof mixitup 1610 * @private 1611 * @since 3.0.0 1612 */ 1613 1614 mixitup.Features = function() { 1615 mixitup.Base.call(this); 1616 1617 this.callActions('beforeConstruct'); 1618 1619 this.boxSizingPrefix = ''; 1620 this.transformPrefix = ''; 1621 this.transitionPrefix = ''; 1622 1623 this.boxSizingPrefix = ''; 1624 this.transformProp = ''; 1625 this.transformRule = ''; 1626 this.transitionProp = ''; 1627 this.perspectiveProp = ''; 1628 this.perspectiveOriginProp = ''; 1629 1630 this.has = new mixitup.Has(); 1631 1632 this.canary = null; 1633 1634 this.BOX_SIZING_PROP = 'boxSizing'; 1635 this.TRANSITION_PROP = 'transition'; 1636 this.TRANSFORM_PROP = 'transform'; 1637 this.PERSPECTIVE_PROP = 'perspective'; 1638 this.PERSPECTIVE_ORIGIN_PROP = 'perspectiveOrigin'; 1639 this.VENDORS = ['Webkit', 'moz', 'O', 'ms']; 1640 1641 this.TWEENABLE = [ 1642 'opacity', 1643 'width', 'height', 1644 'marginRight', 'marginBottom', 1645 'x', 'y', 1646 'scale', 1647 'translateX', 'translateY', 'translateZ', 1648 'rotateX', 'rotateY', 'rotateZ' 1649 ]; 1650 1651 this.callActions('afterConstruct'); 1652 }; 1653 1654 mixitup.BaseStatic.call(mixitup.Features); 1655 1656 mixitup.Features.prototype = Object.create(mixitup.Base.prototype); 1657 1658 h.extend(mixitup.Features.prototype, 1659 /** @lends mixitup.Features */ 1660 { 1661 constructor: mixitup.Features, 1662 1663 /** 1664 * @private 1665 * @return {void} 1666 */ 1667 1668 init: function() { 1669 var self = this; 1670 1671 self.callActions('beforeInit', arguments); 1672 1673 self.canary = document.createElement('div'); 1674 1675 self.setPrefixes(); 1676 self.runTests(); 1677 1678 self.callActions('beforeInit', arguments); 1679 }, 1680 1681 /** 1682 * @private 1683 * @return {void} 1684 */ 1685 1686 runTests: function() { 1687 var self = this; 1688 1689 self.callActions('beforeRunTests', arguments); 1690 1691 self.has.promises = typeof window.Promise === 'function'; 1692 self.has.transitions = self.transitionPrefix !== 'unsupported'; 1693 1694 self.callActions('afterRunTests', arguments); 1695 1696 h.freeze(self.has); 1697 }, 1698 1699 /** 1700 * @private 1701 * @return {void} 1702 */ 1703 1704 setPrefixes: function() { 1705 var self = this; 1706 1707 self.callActions('beforeSetPrefixes', arguments); 1708 1709 self.transitionPrefix = h.getPrefix(self.canary, 'Transition', self.VENDORS); 1710 self.transformPrefix = h.getPrefix(self.canary, 'Transform', self.VENDORS); 1711 self.boxSizingPrefix = h.getPrefix(self.canary, 'BoxSizing', self.VENDORS); 1712 1713 self.boxSizingProp = self.boxSizingPrefix ? 1714 self.boxSizingPrefix + h.pascalCase(self.BOX_SIZING_PROP) : self.BOX_SIZING_PROP; 1715 1716 self.transitionProp = self.transitionPrefix ? 1717 self.transitionPrefix + h.pascalCase(self.TRANSITION_PROP) : self.TRANSITION_PROP; 1718 1719 self.transformProp = self.transformPrefix ? 1720 self.transformPrefix + h.pascalCase(self.TRANSFORM_PROP) : self.TRANSFORM_PROP; 1721 1722 self.transformRule = self.transformPrefix ? 1723 '-' + self.transformPrefix + '-' + self.TRANSFORM_PROP : self.TRANSFORM_PROP; 1724 1725 self.perspectiveProp = self.transformPrefix ? 1726 self.transformPrefix + h.pascalCase(self.PERSPECTIVE_PROP) : self.PERSPECTIVE_PROP; 1727 1728 self.perspectiveOriginProp = self.transformPrefix ? 1729 self.transformPrefix + h.pascalCase(self.PERSPECTIVE_ORIGIN_PROP) : 1730 self.PERSPECTIVE_ORIGIN_PROP; 1731 1732 self.callActions('afterSetPrefixes', arguments); 1733 } 1734 }); 1735 1736 /** 1737 * @constructor 1738 * @memberof mixitup 1739 * @private 1740 * @since 3.0.0 1741 */ 1742 1743 mixitup.Has = function() { 1744 this.transitions = false; 1745 this.promises = false; 1746 1747 h.seal(this); 1748 }; 1749 1750 // Assign a singleton instance to `mixitup.features` and initialise: 1751 1752 mixitup.features = new mixitup.Features(); 1753 1754 mixitup.features.init(); 1755 1756 /** 1757 * A group of properties defining the mixer's animation and effects settings. 1758 * 1759 * @constructor 1760 * @memberof mixitup.Config 1761 * @name animation 1762 * @namespace 1763 * @public 1764 * @since 2.0.0 1765 */ 1766 1767 mixitup.ConfigAnimation = function() { 1768 mixitup.Base.call(this); 1769 1770 this.callActions('beforeConstruct'); 1771 1772 /** 1773 * A boolean dictating whether or not animation should be enabled for the MixItUp instance. 1774 * If `false`, all operations will occur instantly and syncronously, although callback 1775 * functions and any returned promises will still be fulfilled. 1776 * 1777 * @example <caption>Example: Create a mixer with all animations disabled</caption> 1778 * var mixer = mixitup(containerEl, { 1779 * animation: { 1780 * enable: false 1781 * } 1782 * }); 1783 * 1784 * @name enable 1785 * @memberof mixitup.Config.animation 1786 * @instance 1787 * @type {boolean} 1788 * @default true 1789 */ 1790 1791 this.enable = true; 1792 1793 /** 1794 * A string of one or more space-sep
1794erated properties to which transitions will be 1795 * applied for all filtering animations. 1796 * 1797 * Properties can be listed any order or combination, although they will be applied in a specific 1798 * predefined order to produce consistent results. 1799 * 1800 * To learn more about available effects, experiment with our <a href="https://www.kunkalabs.com/mixitup/"> 1801 * sandbox demo</a> and try out the "Export config" button in the Animation options drop down
vendor: 299,530 bytes, lines 1801-10681
1801. 1802 * 1803 * @example <caption>Example: Apply "fade" and "translateZ" effects to all animations</caption> 1804 * // As targets are filtered in and out, they will fade between 1805 * // opacity 1 and 0 and transform between translateZ(-100px) and 1806 * // translateZ(0). 1807 * 1808 * var mixer = mixitup(containerEl, { 1809 * animation: { 1810 * effects: 'fade translateZ(-100px)' 1811 * } 1812 * }); 1813 * 1814 * @name effects 1815 * @memberof mixitup.Config.animation 1816 * @instance 1817 * @type {string} 1818 * @default 'fade scale' 1819 */ 1820 1821 this.effects = 'fade scale'; 1822 1823 /** 1824 * A string of one or more space-seperated effects to be applied only to filter-in 1825 * animations, overriding `config.animation.effects` if set. 1826 * 1827 * @example <caption>Example: Apply downwards vertical translate to targets being filtered in</caption> 1828 * 1829 * var mixer = mixitup(containerEl, { 1830 * animation: { 1831 * effectsIn: 'fade translateY(-100%)' 1832 * } 1833 * }); 1834 * 1835 * @name effectsIn 1836 * @memberof mixitup.Config.animation 1837 * @instance 1838 * @type {string} 1839 * @default '' 1840 */ 1841 1842 this.effectsIn = ''; 1843 1844 /** 1845 * A string of one or more space-seperated effects to be applied only to filter-out 1846 * animations, overriding `config.animation.effects` if set. 1847 * 1848 * @example <caption>Example: Apply upwards vertical translate to targets being filtered out</caption> 1849 * 1850 * var mixer = mixitup(containerEl, { 1851 * animation: { 1852 * effectsOut: 'fade translateY(-100%)' 1853 * } 1854 * }); 1855 * 1856 * @name effectsOut 1857 * @memberof mixitup.Config.animation 1858 * @instance 1859 * @type {string} 1860 * @default '' 1861 */ 1862 1863 this.effectsOut = ''; 1864 1865 /** 1866 * An integer dictating the duration of all MixItUp animations in milliseconds, not 1867 * including any additional delay apllied via the `'stagger'` effect. 1868 * 1869 * @example <caption>Example: Apply an animation duration of 200ms to all mixitup animations</caption> 1870 * 1871 * var mixer = mixitup(containerEl, { 1872 * animation: { 1873 * duration: 200 1874 * } 1875 * }); 1876 * 1877 * @name duration 1878 * @memberof mixitup.Config.animation 1879 * @instance 1880 * @type {number} 1881 * @default 600 1882 */ 1883 1884 this.duration = 600; 1885 1886 /** 1887 * A valid CSS3 transition-timing function or shorthand. For a full list of accepted 1888 * values, visit <a href="http://easings.net" target="_blank">easings.net</a>. 1889 * 1890 * @example <caption>Example 1: Apply "ease-in-out" easing to all animations</caption> 1891 * 1892 * var mixer = mixitup(containerEl, { 1893 * animation: { 1894 * easing: 'ease-in-out' 1895 * } 1896 * }); 1897 * 1898 * @example <caption>Example 2: Apply a custom "cubic-bezier" easing function to all animations</caption> 1899 * var mixer = mixitup(containerEl, { 1900 * animation: { 1901 * easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)' 1902 * } 1903 * }); 1904 * 1905 * @name easing 1906 * @memberof mixitup.Config.animation 1907 * @instance 1908 * @type {string} 1909 * @default 'ease' 1910 */ 1911 1912 this.easing = 'ease'; 1913 1914 /** 1915 * A boolean dictating whether or not to apply perspective to the MixItUp container 1916 * during animations. By default, perspective is always applied and creates the 1917 * illusion of three-dimensional space for effects such as `translateZ`, `rotateX`, 1918 * and `rotateY`. 1919 * 1920 * You may wish to disable this and define your own perspective settings via CSS. 1921 * 1922 * @example <caption>Example: Prevent perspective from being applied to any 3D transforms</caption> 1923 * var mixer = mixitup(containerEl, { 1924 * animation: { 1925 * applyPerspective: false 1926 * } 1927 * }); 1928 * 1929 * @name applyPerspective 1930 * @memberof mixitup.Config.animation 1931 * @instance 1932 * @type {bolean} 1933 * @default true 1934 */ 1935 1936 this.applyPerspective = true; 1937 1938 /** 1939 * The perspective distance value to be applied to the container during animations, 1940 * affecting any 3D-transform-based effects. 1941 * 1942 * @example <caption>Example: Set a perspective distance of 2000px</caption> 1943 * var mixer = mixitup(containerEl, { 1944 * animation: { 1945 * effects: 'rotateY(-25deg)', 1946 * perspectiveDistance: '2000px' 1947 * } 1948 * }); 1949 * 1950 * @name perspectiveDistance 1951 * @memberof mixitup.Config.animation 1952 * @instance 1953 * @type {string} 1954 * @default '3000px' 1955 */ 1956 1957 this.perspectiveDistance = '3000px'; 1958 1959 /** 1960 * The perspective-origin value to be applied to the container during animations, 1961 * affecting any 3D-transform-based effects. 1962 * 1963 * @example <caption>Example: Set a perspective origin in the top-right of the container</caption> 1964 * var mixer = mixitup(containerEl, { 1965 * animation: { 1966 * effects: 'transateZ(-200px)', 1967 * perspectiveOrigin: '100% 0' 1968 * } 1969 * }); 1970 * 1971 * @name perspectiveOrigin 1972 * @memberof mixitup.Config.animation 1973 * @instance 1974 * @type {string} 1975 * @default '50% 50%' 1976 */ 1977 1978 this.perspectiveOrigin = '50% 50%'; 1979 1980 /** 1981 * A boolean dictating whether or not to enable the queuing of operations. 1982 * 1983 * If `true` (default), and a control is clicked or an API call is made while another 1984 * operation is progress, the operation will go into the queue and will be automatically exectuted 1985 * when the previous operaitons is finished. 1986 * 1987 * If `false`, any requested operations will be ignored, and the `onMixBusy` callback and `mixBusy` 1988 * event will be fired. If `debug.showWarnings` is enabled, a console warning will also occur. 1989 * 1990 * @example <caption>Example: Disable queuing</caption> 1991 * var mixer = mixitup(containerEl, { 1992 * animation: { 1993 * queue: false 1994 * } 1995 * }); 1996 * 1997 * @name queue 1998 * @memberof mixitup.Config.animation 1999 * @instance 2000 * @type {boolean} 2001 * @default true 2002 */ 2003 2004 this.queue = true; 2005 2006 /** 2007 * An integer dictacting the maximum number of operations allowed in the queue at 2008 * any time, when queuing is enabled. 2009 * 2010 * @example <caption>Example: Allow a maximum of 5 operations in the queue at any time</caption> 2011 * var mixer = mixitup(containerEl, { 2012 * animation: { 2013 * queueLimit: 5 2014 * } 2015 * }); 2016 * 2017 * @name queueLimit 2018 * @memberof mixitup.Config.animation 2019 * @instance 2020 * @type {number} 2021 * @default 3 2022 */ 2023 2024 this.queueLimit = 3; 2025 2026 /** 2027 * A boolean dictating whether or not to transition the height and width of the 2028 * container as elements are filtered in and out. If disabled, the container height 2029 * will change abruptly. 2030 * 2031 * It may be desirable to disable this on mobile devices as the CSS `height` and 2032 * `width` properties do not receive GPU-acceleration and can therefore cause stuttering. 2033 * 2034 * @example <caption>Example 1: Disable the transitioning of the container height and/or width</caption> 2035 * var mixer = mixitup(containerEl, { 2036 * animation: { 2037 * animateResizeContainer: false 2038 * } 2039 * }); 2040 * 2041 * @example <caption>Example 2: Disable the transitioning of the container height and/or width for mobile devices only</caption> 2042 * var mixer = mixitup(containerEl, { 2043 * animation: { 2044 * animateResizeContainer: myFeatureTests.isMobile ? false : true 2045 * } 2046 * }); 2047 * 2048 * @name animateResizeContainer 2049 * @memberof mixitup.Config.animation 2050 * @instance 2051 * @type {boolean} 2052 * @default true 2053 */ 2054 2055 this.animateResizeContainer = true; 2056 2057 /** 2058 * A boolean dictating whether or not to transition the height and width of target 2059 * elements as they change throughout the course of an animation. 2060 * 2061 * This is often a must for flex-box grid layouts where the size of target elements may change 2062 * depending on final their position in relation to their siblings, or for `.changeLayout()` 2063 * operations where the size of targets change between layouts. 2064 * 2065 * NB: This feature requires additional calculations and manipulation to non-hardware-accelerated 2066 * properties which may adversely affect performance on slower devices, and is therefore 2067 * disabled by default. 2068 * 2069 * @example <caption>Example: Enable the transitioning of target widths and heights</caption> 2070 * var mixer = mixitup(containerEl, { 2071 * animation: { 2072 * animateResizeTargets: true 2073 * } 2074 * }); 2075 * 2076 * @name animateResizeTargets 2077 * @memberof mixitup.Config.animation 2078 * @instance 2079 * @type {boolean} 2080 * @default false 2081 */ 2082 2083 this.animateResizeTargets = false; 2084 2085 /** 2086 * A custom function used to manipulate the order in which the stagger delay is 2087 * incremented when using the ‘stagger’ effect. 2088 * 2089 * When using the 'stagger' effect, the delay applied to each target element is incremented 2090 * based on its index. You may create a custom function to manipulate the order in which the 2091 * delay is incremented and create engaging non-linear stagger effects. 2092 * 2093 * The function receives the index of the target element as a parameter, and must 2094 * return an integer which serves as the multiplier for the stagger delay. 2095 * 2096 * @example <caption>Example 1: Stagger target elements by column in a 3-column grid</caption> 2097 * var mixer = mixitup(containerEl, { 2098 * animation: { 2099 * effects: 'fade stagger(100ms)', 2100 * staggerSequence: function(i) { 2101 * return i % 3; 2102 * } 2103 * } 2104 * }); 2105 * 2106 * @example <caption>Example 2: Using an algorithm to produce a more complex sequence</caption> 2107 * var mixer = mixitup(containerEl, { 2108 * animation: { 2109 * effects: 'fade stagger(100ms)', 2110 * staggerSequence: function(i) { 2111 * return (2*i) - (5*((i/3) - ((1/3) * (i%3)))); 2112 * } 2113 * } 2114 * }); 2115 * 2116 * @name staggerSequence 2117 * @memberof mixitup.Config.animation 2118 * @instance 2119 * @type {function} 2120 * @default null 2121 */ 2122 2123 this.staggerSequence = null; 2124 2125 /** 2126 * A boolean dictating whether or not to reverse the direction of `translate` 2127 * and `rotate` transforms for elements being filtered out. 2128 * 2129 * It can be used to create carousel-like animations where elements enter and exit 2130 * from opposite directions. If enabled, the effect `translateX(-100%)` for elements 2131 * being filtered in would become `translateX(100%)` for targets being filtered out. 2132 * 2133 * This functionality can also be achieved by providing seperate effects 2134 * strings for `config.animation.effectsIn` and `config.animation.effectsOut`. 2135 * 2136 * @example <caption>Example: Reverse the desired direction on any translate/rotate effect for targets being filtered out</caption> 2137 * // Elements being filtered in will be translated from '100%' to '0' while 2138 * // elements being filtered out will be translated from 0 to '-100%' 2139 * 2140 * var mixer = mixitup(containerEl, { 2141 * animation: { 2142 * effects: 'fade translateX(100%)', 2143 * reverseOut: true, 2144 * nudge: false // Disable nudging to create a carousel-like effect 2145 * } 2146 * }); 2147 * 2148 * @name reverseOut 2149 * @memberof mixitup.Config.animation 2150 * @instance 2151 * @type {boolean} 2152 * @default false 2153 */ 2154 2155 this.reverseOut = false; 2156 2157 /** 2158 * A boolean dictating whether or not to "nudge" the animation path of targets 2159 * when they are being filtered in and out simulatenously. 2160 * 2161 * This has been the default behavior of MixItUp since version 1, but it 2162 * may be desirable to disable this effect when filtering directly from 2163 * one exclusive set of targets to a different exclusive set of targets, 2164 * to create a carousel-like effect, or a generally more subtle animation. 2165 * 2166 * @example <caption>Example: Disable the "nudging" of targets being filtered in and out simulatenously</caption> 2167 * 2168 * var mixer = mixitup(containerEl, { 2169 * animation: { 2170 * nudge: false 2171 * } 2172 * }); 2173 * 2174 * @name nudge 2175 * @memberof mixitup.Config.animation 2176 * @instance 2177 * @type {boolean} 2178 * @default true 2179 */ 2180 2181 this.nudge = true; 2182 2183 /** 2184 * A boolean dictating whether or not to clamp the height of the container while MixItUp's 2185 * geometry tests are carried out before an operation. 2186 * 2187 * To prevent scroll-bar flicker, clamping is turned on by default. But in the case where the 2188 * height of the container might affect its vertical positioning in the viewport 2189 * (e.g. a vertically-centered container), this should be turned off to ensure accurate 2190 * test results and a smooth animation. 2191 * 2192 * @example <caption>Example: Disable container height-clamping</caption> 2193 * 2194 * var mixer = mixitup(containerEl, { 2195 * animation: { 2196 * clampHeight: false 2197 * } 2198 * }); 2199 * 2200 * @name clampHeight 2201 * @memberof mixitup.Config.animation 2202 * @instance 2203 * @type {boolean} 2204 * @default true 2205 */ 2206 2207 this.clampHeight = true; 2208 2209 /** 2210 * A boolean dictating whether or not to clamp the width of the container while MixItUp's 2211 * geometry tests are carried out before an operation. 2212 * 2213 * To prevent scroll-bar flicker, clamping is turned on by default. But in the case where the 2214 * width of the container might affect its horitzontal positioning in the viewport 2215 * (e.g. a horizontall-centered container), this should be turned off to ensure accurate 2216 * test results and a smooth animation. 2217 * 2218 * @example <caption>Example: Disable container width-clamping</caption> 2219 * 2220 * var mixer = mixitup(containerEl, { 2221 * animation: { 2222 * clampWidth: false 2223 * } 2224 * }); 2225 * 2226 * @name clampWidth 2227 * @memberof mixitup.Config.animation 2228 * @instance 2229 * @type {boolean} 2230 * @default true 2231 */ 2232 2233 this.clampWidth = true; 2234 2235 this.callActions('afterConstruct'); 2236 2237 h.seal(this); 2238 }; 2239 2240 mixitup.BaseStatic.call(mixitup.ConfigAnimation); 2241 2242 mixitup.ConfigAnimation.prototype = Object.create(mixitup.Base.prototype); 2243 2244 mixitup.ConfigAnimation.prototype.constructor = mixitup.ConfigAnimation; 2245 2246 /** 2247 * A group of properties relating to the behavior of the Mixer. 2248 * 2249 * @constructor 2250 * @memberof mixitup.Config 2251 * @name behavior 2252 * @namespace 2253 * @public 2254 * @since 3.1.12 2255 */ 2256 2257 mixitup.ConfigBehavior = function() { 2258 mixitup.Base.call(this); 2259 2260 this.callActions('beforeConstruct'); 2261 2262 /** 2263 * A boolean dictating whether to allow "live" sorting of the mixer. 2264 * 2265 * Because of the expensive nature of sorting, MixItUp makes use of several 2266 * internal optimizations to skip redundant sorting operations, such as when 2267 * the newly requested sort command is the same as the active one. The caveat 2268 * to this optimization is that "live" edits to the value of a target's sorting 2269 * attribute will be ignored when requesting a re-sort by the same attribute. 2270 * 2271 * By setting to `behavior.liveSort` to `true`, the mixer will always re-sort 2272 * regardless of whether or not the sorting attribute and order have changed. 2273 * 2274 * @example <caption>Example: Enabling `liveSort` to allow for re-sorting</caption> 2275 * 2276 * var mixer = mixitup(containerEl, { 2277 * behavior: { 2278 * liveSort: true 2279 * }, 2280 * load: { 2281 * sort: 'edited:desc' 2282 * } 2283 * }); 2284 * 2285 * var target = containerEl.children[3]; 2286 * 2287 * console.log(target.getAttribute('data-edited')); // '2015-04-24' 2288 * 2289 * target.setAttribute('data-edited', '2017-08-10'); // Update the target's edited date 2290 * 2291 * mixer.sort('edited:desc') 2292 * .then(function(state) { 2293 * // The target is now at the top of the list 2294 * 2295 * console.log(state.targets[0] === target); // true 2296 * }); 2297 * 2298 * @name liveSort 2299 * @memberof mixitup.Config.behavior 2300 * @instance 2301 * @type {boolean} 2302 * @default false 2303 */ 2304 2305 this.liveSort = false; 2306 2307 this.callActions('afterConstruct'); 2308 2309 h.seal(this); 2310 }; 2311 2312 mixitup.BaseStatic.call(mixitup.ConfigBehavior); 2313 2314 mixitup.ConfigBehavior.prototype = Object.create(mixitup.Base.prototype); 2315 2316 mixitup.ConfigBehavior.prototype.constructor = mixitup.ConfigBehavior; 2317 2318 /** 2319 * A group of optional callback functions to be invoked at various 2320 * points within the lifecycle of a mixer operation. 2321 * 2322 * Each function is analogous to an event of the same name triggered from the 2323 * container element, and is invoked immediately after it. 2324 * 2325 * All callback functions receive the current `state` object as their first 2326 * argument, as well as other more specific arguments described below. 2327 * 2328 * @constructor 2329 * @memberof mixitup.Config 2330 * @name callbacks 2331 * @namespace 2332 * @public 2333 * @since 2.0.0 2334 */ 2335 2336 mixitup.ConfigCallbacks = function() { 2337 mixitup.Base.call(this); 2338 2339 this.callActions('beforeConstruct'); 2340 2341 /** 2342 * A callback function invoked immediately after any MixItUp operation is requested 2343 * and before animations have begun. 2344 * 2345 * A second `futureState` argument is passed to the function which represents the final 2346 * state of the mixer once the requested operation has completed. 2347 * 2348 * @example <caption>Example: Adding an `onMixStart` callback function</caption> 2349 * var mixer = mixitup(containerEl, { 2350 * callbacks: { 2351 * onMixStart: function(state, futureState) { 2352 * console.log('Starting operation...'); 2353 * } 2354 * } 2355 * }); 2356 * 2357 * @name onMixStart 2358 * @memberof mixitup.Config.callbacks 2359 * @instance 2360 * @type {function} 2361 * @default null 2362 */ 2363 2364 this.onMixStart = null; 2365 2366 /** 2367 * A callback function invoked when a MixItUp operation is requested while another 2368 * operation is in progress, and the animation queue is full, or queueing 2369 * is disabled. 2370 * 2371 * @example <caption>Example: Adding an `onMixBusy` callback function</caption> 2372 * var mixer = mixitup(containerEl, { 2373 * callbacks: { 2374 * onMixBusy: function(state) { 2375 * console.log('Mixer busy'); 2376 * } 2377 * } 2378 * }); 2379 * 2380 * @name onMixBusy 2381 * @memberof mixitup.Config.callbacks 2382 * @instance 2383 * @type {function} 2384 * @default null 2385 */ 2386 2387 this.onMixBusy = null; 2388 2389 /** 2390 * A callback function invoked after any MixItUp operation has completed, and the 2391 * state has been updated. 2392 * 2393 * @example <caption>Example: Adding an `onMixEnd` callback function</caption> 2394 * var mixer = mixitup(containerEl, { 2395 * callbacks: { 2396 * onMixEnd: function(state) { 2397 * console.log('Operation complete'); 2398 * } 2399 * } 2400 * }); 2401 * 2402 * @name onMixEnd 2403 * @memberof mixitup.Config.callbacks 2404 * @instance 2405 * @type {function} 2406 * @default null 2407 */ 2408 2409 this.onMixEnd = null; 2410 2411 /** 2412 * A callback function invoked whenever an operation "fails", i.e. no targets 2413 * could be found matching the requested filter. 2414 * 2415 * @example <caption>Example: Adding an `onMixFail` callback function</caption> 2416 * var mixer = mixitup(containerEl, { 2417 * callbacks: { 2418 * onMixFail: function(state) { 2419 * console.log('No items could be found matching the requested filter'); 2420 * } 2421 * } 2422 * }); 2423 * 2424 * @name onMixFail 2425 * @memberof mixitup.Config.callbacks 2426 * @instance 2427 * @type {function} 2428 * @default null 2429 */ 2430 2431 this.onMixFail = null; 2432 2433 /** 2434 * A callback function invoked whenever a MixItUp control is clicked, and before its 2435 * respective operation is requested. 2436 * 2437 * The clicked element is assigned to the `this` keyword within the function. The original 2438 * click event is passed to the function as the second argument, which can be useful if 2439 * using `<a>` tags as controls where the default behavior needs to be prevented. 2440 * 2441 * Returning `false` from the callback will prevent the control click from triggering 2442 * an operation. 2443 * 2444 * @example <caption>Example 1: Adding an `onMixClick` callback function</caption> 2445 * var mixer = mixitup(containerEl, { 2446 * callbacks: { 2447 * onMixClick: function(state, originalEvent) { 2448 * console.log('The control "' + this.innerText + '" was clicked'); 2449 * } 2450 * } 2451 * }); 2452 * 2453 * @example <caption>Example 2: Using `onMixClick` to manipulate the original click event</caption> 2454 * var mixer = mixitup(containerEl, { 2455 * callbacks: { 2456 * onMixClick: function(state, originalEvent) { 2457 * // Prevent original click event from bubbling up: 2458 * originalEvent.stopPropagation(); 2459 * 2460 * // Prevent default behavior of clicked element: 2461 * originalEvent.preventDefault(); 2462 * } 2463 * } 2464 * }); 2465 * 2466 * @example <caption>Example 3: Using `onMixClick` to conditionally cancel operations</caption> 2467 * var mixer = mixitup(containerEl, { 2468 * callbacks: { 2469 * onMixClick: function(state, originalEvent) { 2470 * // Perform some conditional check: 2471 * 2472 * if (myApp.isLoading) { 2473 * // By returning false, we can prevent the control click from triggering an operation. 2474 * 2475 * return false; 2476 * } 2477 * } 2478 * } 2479 * }); 2480 * 2481 * @name onMixClick 2482 * @memberof mixitup.Config.callbacks 2483 * @instance 2484 * @type {function} 2485 * @default null 2486 */ 2487 2488 this.onMixClick = null; 2489 2490 this.callActions('afterConstruct'); 2491 2492 h.seal(this); 2493 }; 2494 2495 mixitup.BaseStatic.call(mixitup.ConfigCallbacks); 2496 2497 mixitup.ConfigCallbacks.prototype = Object.create(mixitup.Base.prototype); 2498 2499 mixitup.ConfigCallbacks.prototype.constructor = mixitup.ConfigCallbacks; 2500 2501 /** 2502 * A group of properties relating to clickable control elements. 2503 * 2504 * @constructor 2505 * @memberof mixitup.Config 2506 * @name controls 2507 * @namespace 2508 * @public 2509 * @since 2.0.0 2510 */ 2511 2512 mixitup.ConfigControls = function() { 2513 mixitup.Base.call(this); 2514 2515 this.callActions('beforeConstruct'); 2516 2517 /** 2518 * A boolean dictating whether or not controls should be enabled for the mixer instance. 2519 * 2520 * If `true` (default behavior), MixItUp will search the DOM for any clickable elements with 2521 * `data-filter`, `data-sort` or `data-toggle` attributes, and bind them for click events. 2522 * 2523 * If `false`, no click handlers will be bound, and all functionality must therefore be performed 2524 * via the mixer's API methods. 2525 * 2526 * If you do not intend to use the default controls, setting this property to `false` will 2527 * marginally improve the startup time of your mixer instance, and will also prevent any other active 2528 * mixer instances in the DOM which are bound to controls from controlling the instance. 2529 * 2530 * @example <caption>Example: Disabling controls</caption> 2531 * var mixer = mixitup(containerEl, { 2532 * controls: { 2533 * enable: false 2534 * } 2535 * }); 2536 * 2537 * // With the default controls disabled, we can only control 2538 * // the mixer via its API methods, e.g.: 2539 * 2540 * mixer.filter('.cat-1'); 2541 * 2542 * @name enable 2543 * @memberof mixitup.Config.controls 2544 * @instance 2545 * @type {boolean} 2546 * @default true 2547 */ 2548 2549 this.enable = true; 2550 2551 /** 2552 * A boolean dictating whether or not to use event delegation when binding click events 2553 * to the default controls. 2554 * 2555 * If `false` (default behavior), each control button in the DOM will be found and 2556 * individually bound when a mixer is instantiated, with their corresponding actions 2557 * cached for performance. 2558 * 2559 * If `true`, a single click handler will be applied to the `window` (or container element - see 2560 * `config.controls.scope`), and any click events triggered by elements with `data-filter`, 2561 * `data-sort` or `data-toggle` attributes present will be handled as they propagate upwards. 2562 * 2563 * If you require a user interface where control buttons may be added, removed, or changed during the 2564 * lifetime of a mixer, `controls.live` should be set to `true`. There is a marginal but unavoidable 2565 * performance deficit when using live controls, as the value of each control button must be read 2566 * from the DOM in real time once the click event has propagated. 2567 * 2568 * @example <caption>Example: Setting live controls</caption> 2569 * var mixer = mixitup(containerEl, { 2570 * controls: { 2571 * live: true 2572 * } 2573 * }); 2574 * 2575 * // Control buttons can now be added, remove and changed without breaking 2576 * // the mixer's UI 2577 * 2578 * @name live 2579 * @memberof mixitup.Config.controls 2580 * @instance 2581 * @type {boolean} 2582 * @default true 2583 */ 2584 2585 this.live = false; 2586 2587 /** 2588 * A string dictating the "scope" to use when binding or querying the default controls. The available 2589 * values are `'global'` or `'local'`. 2590 * 2591 * When set to `'global'` (default behavior), MixItUp will query the entire document for control buttons 2592 * to bind, or delegate click events from (see `config.controls.live`). 2593 * 2594 * When set to `'local'`, MixItUp will only query (or bind click events to) its own container element. 2595 * This may be desireable if you require multiple active mixer instances within the same document, with 2596 * controls that would otherwise intefere with each other if scoped globally. 2597 * 2598 * Conversely, if you wish to control multiple instances with a single UI, you would create one 2599 * set of controls and keep the controls scope of each mixer set to `global`. 2600 * 2601 * @example <caption>Example: Setting 'local' scoped controls</caption> 2602 * var mixerOne = mixitup(containerOne, { 2603 * controls: { 2604 * scope: 'local' 2605 * } 2606 * }); 2607 * 2608 * var mixerTwo = mixitup(containerTwo, { 2609 * controls: { 2610 * scope: 'local' 2611 * } 2612 * }); 2613 * 2614 * // Both mixers can now exist within the same document with 2615 * // isolated controls placed within their container elements. 2616 * 2617 * @name scope 2618 * @memberof mixitup.Config.controls 2619 * @instance 2620 * @type {string} 2621 * @default 'global' 2622 */ 2623 2624 this.scope = 'global'; // enum: ['local' ,'global'] 2625 2626 /** 2627 * A string dictating the type of logic to apply when concatenating the filter selectors of 2628 * active toggle buttons (i.e. any clickable element with a `data-toggle` attribute). 2629 * 2630 * If set to `'or'` (default behavior), selectors will be concatenated together as 2631 * a comma-seperated list. For example: 2632 * 2633 * `'.cat-1, .cat-2'` (shows any elements matching `'.cat-1'` OR `'.cat-2'`) 2634 * 2635 * If set to `'and'`, selectors will be directly concatenated together. For example: 2636 * 2637 * `'.cat-1.cat-2'` (shows any elements which match both `'.cat-1'` AND `'.cat-2'`) 2638 * 2639 * @example <caption>Example: Setting "and" toggle logic</caption> 2640 * var mixer = mixitup(containerEl, { 2641 * controls: { 2642 * toggleLogic: 'and' 2643 * } 2644 * }); 2645 * 2646 * @name toggleLogic 2647 * @memberof mixitup.Config.controls 2648 * @instance 2649 * @type {string} 2650 * @default 'or' 2651 */ 2652 2653 this.toggleLogic = 'or'; // enum: ['or', 'and'] 2654 2655 /** 2656 * A string dictating the filter behavior when all toggles are inactive. 2657 * 2658 * When set to `'all'` (default behavior), *all* targets will be shown by default 2659 * when no toggles are active, or at the moment all active toggles are toggled off. 2660 * 2661 * When set to `'none'`, no targets will be shown by default when no toggles are 2662 * active, or at the moment all active toggles are toggled off. 2663 * 2664 * @example <caption>Example 1: Setting the default toggle behavior to `'all'`</caption> 2665 * var mixer = mixitup(containerEl, { 2666 * controls: { 2667 * toggleDefault: 'all' 2668 * } 2669 * }); 2670 * 2671 * mixer.toggleOn('.cat-2') 2672 * .then(function() { 2673 * // Deactivate all active toggles 2674 * 2675 * return mixer.toggleOff('.cat-2') 2676 * }) 2677 * .then(function(state) { 2678 * console.log(state.activeFilter.selector); // 'all' 2679 * console.log(state.totalShow); // 12 2680 * }); 2681 * 2682 * @example <caption>Example 2: Setting the default toggle behavior to `'none'`</caption> 2683 * var mixer = mixitup(containerEl, { 2684 * controls: { 2685 * toggleDefault: 'none' 2686 * } 2687 * }); 2688 * 2689 * mixer.toggleOn('.cat-2') 2690 * .then(function() { 2691 * // Deactivate all active toggles 2692 * 2693 * return mixer.toggleOff('.cat-2') 2694 * }) 2695 * .then(function(state) { 2696 * console.log(state.activeFilter.selector); // 'none' 2697 * console.log(state.totalShow); // 0 2698 * }); 2699 * 2700 * @name toggleDefault 2701 * @memberof mixitup.Config.controls 2702 * @instance 2703 * @type {string} 2704 * @default 'all' 2705 */ 2706 2707 this.toggleDefault = 'all'; // enum: ['all', 'none'] 2708 2709 this.callActions('afterConstruct'); 2710 2711 h.seal(this); 2712 }; 2713 2714 mixitup.BaseStatic.call(mixitup.ConfigControls); 2715 2716 mixitup.ConfigControls.prototype = Object.create(mixitup.Base.prototype); 2717 2718 mixitup.ConfigControls.prototype.constructor = mixitup.ConfigControls; 2719 2720 /** 2721 * A group of properties defining the output and structure of class names programmatically 2722 * added to controls and containers to reflect the state of the mixer. 2723 * 2724 * Most commonly, class names are added to controls by MixItUp to indicate that 2725 * the control is active so that it can be styled accordingly - `'mixitup-control-active'` by default. 2726 * 2727 * Using a "BEM" like structure, each classname is broken into the three parts: 2728 * a block namespace (`'mixitup'`), an element name (e.g. `'control'`), and an optional modifier 2729 * name (e.g. `'active'`) reflecting the state of the element. 2730 * 2731 * By default, each part of the classname is concatenated together using single hyphens as 2732 * delineators, but this can be easily customised to match the naming convention and style of 2733 * your project. 2734 * 2735 * @constructor 2736 * @memberof mixitup.Config 2737 * @name classNames 2738 * @namespace 2739 * @public 2740 * @since 3.0.0 2741 */ 2742 2743 mixitup.ConfigClassNames = function() { 2744 mixitup.Base.call(this); 2745 2746 this.callActions('beforeConstruct'); 2747 2748 /** 2749 * The "block" portion, or top-level namespace added to the start of any class names created by MixItUp. 2750 * 2751 * @example <caption>Example 1: changing the `config.classNames.block` value</caption> 2752 * var mixer = mixitup(containerEl, { 2753 * classNames: { 2754 * block: 'portfolio' 2755 * } 2756 * }); 2757 * 2758 * // Active control output: "portfolio-control-active" 2759 * 2760 * @example <caption>Example 2: Removing `config.classNames.block`</caption> 2761 * var mixer = mixitup(containerEl, { 2762 * classNames: { 2763 * block: '' 2764 * } 2765 * }); 2766 * 2767 * // Active control output: "control-active" 2768 * 2769 * @name block 2770 * @memberof mixitup.Config.classNames 2771 * @instance 2772 * @type {string} 2773 * @default 'mixitup' 2774 */ 2775 2776 this.block = 'mixitup'; 2777 2778 /** 2779 * The "element" portion of the class name added to container. 2780 * 2781 * @name elementContainer 2782 * @memberof mixitup.Config.classNames 2783 * @instance 2784 * @type {string} 2785 * @default 'container' 2786 */ 2787 2788 this.elementContainer = 'container'; 2789 2790 /** 2791 * The "element" portion of the class name added to filter controls. 2792 * 2793 * By default, all filter, sort, multimix and toggle controls take the same element value of `'control'`, but 2794 * each type's element value can be individually overwritten to match the unique classNames of your controls as needed. 2795 * 2796 * @example <caption>Example 1: changing the `config.classNames.elementFilter` value</caption> 2797 * var mixer = mixitup(containerEl, { 2798 * classNames: { 2799 * elementFilter: 'filter' 2800 * } 2801 * }); 2802 * 2803 * // Active filter output: "mixitup-filter-active" 2804 * 2805 * @example <caption>Example 2: changing the `config.classNames.block` and `config.classNames.elementFilter` values</caption> 2806 * var mixer = mixitup(containerEl, { 2807 * classNames: { 2808 * block: 'portfolio', 2809 * elementFilter: 'filter' 2810 * } 2811 * }); 2812 * 2813 * // Active filter output: "portfolio-filter-active" 2814 * 2815 * @name elementFilter 2816 * @memberof mixitup.Config.classNames 2817 * @instance 2818 * @type {string} 2819 * @default 'control' 2820 */ 2821 2822 this.elementFilter = 'control'; 2823 2824 /** 2825 * The "element" portion of the class name added to sort controls. 2826 * 2827 * By default, all filter, sort, multimix and toggle controls take the same element value of `'control'`, but 2828 * each type's element value can be individually overwritten to match the unique classNames of your controls as needed. 2829 * 2830 * @example <caption>Example 1: changing the `config.classNames.elementSort` value</caption> 2831 * var mixer = mixitup(containerEl, { 2832 * classNames: { 2833 * elementSort: 'sort' 2834 * } 2835 * }); 2836 * 2837 * // Active sort output: "mixitup-sort-active" 2838 * 2839 * @example <caption>Example 2: changing the `config.classNames.block` and `config.classNames.elementSort` values</caption> 2840 * var mixer = mixitup(containerEl, { 2841 * classNames: { 2842 * block: 'portfolio', 2843 * elementSort: 'sort' 2844 * } 2845 * }); 2846 * 2847 * // Active sort output: "portfolio-sort-active" 2848 * 2849 * @name elementSort 2850 * @memberof mixitup.Config.classNames 2851 * @instance 2852 * @type {string} 2853 * @default 'control' 2854 */ 2855 2856 this.elementSort = 'control'; 2857 2858 /** 2859 * The "element" portion of the class name added to multimix controls. 2860 * 2861 * By default, all filter, sort, multimix and toggle controls take the same element value of `'control'`, but 2862 * each type's element value can be individually overwritten to match the unique classNames of your controls as needed. 2863 * 2864 * @example <caption>Example 1: changing the `config.classNames.elementMultimix` value</caption> 2865 * var mixer = mixitup(containerEl, { 2866 * classNames: { 2867 * elementMultimix: 'multimix' 2868 * } 2869 * }); 2870 * 2871 * // Active multimix output: "mixitup-multimix-active" 2872 * 2873 * @example <caption>Example 2: changing the `config.classNames.block` and `config.classNames.elementMultimix` values</caption> 2874 * var mixer = mixitup(containerEl, { 2875 * classNames: { 2876 * block: 'portfolio', 2877 * elementSort: 'multimix' 2878 * } 2879 * }); 2880 * 2881 * // Active multimix output: "portfolio-multimix-active" 2882 * 2883 * @name elementMultimix 2884 * @memberof mixitup.Config.classNames 2885 * @instance 2886 * @type {string} 2887 * @default 'control' 2888 */ 2889 2890 this.elementMultimix = 'control'; 2891 2892 /** 2893 * The "element" portion of the class name added to toggle controls. 2894 * 2895 * By default, all filter, sort, multimix and toggle controls take the same element value of `'control'`, but 2896 * each type's element value can be individually overwritten to match the unique classNames of your controls as needed. 2897 * 2898 * @example <caption>Example 1: changing the `config.classNames.elementToggle` value</caption> 2899 * var mixer = mixitup(containerEl, { 2900 * classNames: { 2901 * elementToggle: 'toggle' 2902 * } 2903 * }); 2904 * 2905 * // Active toggle output: "mixitup-toggle-active" 2906 * 2907 * @example <caption>Example 2: changing the `config.classNames.block` and `config.classNames.elementToggle` values</caption> 2908 * var mixer = mixitup(containerEl, { 2909 * classNames: { 2910 * block: 'portfolio', 2911 * elementToggle: 'toggle' 2912 * } 2913 * }); 2914 * 2915 * // Active toggle output: "portfolio-toggle-active" 2916 * 2917 * @name elementToggle 2918 * @memberof mixitup.Config.classNames 2919 * @instance 2920 * @type {string} 2921 * @default 'control' 2922 */ 2923 2924 this.elementToggle = 'control'; 2925 2926 /** 2927 * The "modifier" portion of the class name added to active controls. 2928 * @name modifierActive 2929 * @memberof mixitup.Config.classNames 2930 * @instance 2931 * @type {string} 2932 * @default 'active' 2933 */ 2934 2935 this.modifierActive = 'active'; 2936 2937 /** 2938 * The "modifier" portion of the class name added to disabled controls. 2939 * 2940 * @name modifierDisabled 2941 * @memberof mixitup.Config.classNames 2942 * @instance 2943 * @type {string} 2944 * @default 'disabled' 2945 */ 2946 2947 this.modifierDisabled = 'disabled'; 2948 2949 /** 2950 * The "modifier" portion of the class name added to the container when in a "failed" state. 2951 * 2952 * @name modifierFailed 2953 * @memberof mixitup.Config.classNames 2954 * @instance 2955 * @type {string} 2956 * @default 'failed' 2957 */ 2958 2959 this.modifierFailed = 'failed'; 2960 2961 /** 2962 * The delineator used between the "block" and "element" portions of any class name added by MixItUp. 2963 * 2964 * If the block portion is ommited by setting it to an empty string, no delineator will be added. 2965 * 2966 * @example <caption>Example: changing the delineator to match BEM convention</caption> 2967 * var mixer = mixitup(containerEl, { 2968 * classNames: { 2969 * delineatorElement: '__' 2970 * } 2971 * }); 2972 * 2973 * // example active control output: "mixitup__control-active" 2974 * 2975 * @name delineatorElement 2976 * @memberof mixitup.Config.classNames 2977 * @instance 2978 * @type {string} 2979 * @default '-' 2980 */ 2981 2982 this.delineatorElement = '-'; 2983 2984 /** 2985 * The delineator used between the "element" and "modifier" portions of any class name added by MixItUp. 2986 * 2987 * If the element portion is ommited by setting it to an empty string, no delineator will be added. 2988 * 2989 * @example <caption>Example: changing both delineators to match BEM convention</caption> 2990 * var mixer = mixitup(containerEl, { 2991 * classNames: { 2992 * delineatorElement: '__' 2993 * delineatorModifier: '--' 2994 * } 2995 * }); 2996 * 2997 * // Active control output: "mixitup__control--active" 2998 * 2999 * @name delineatorModifier 3000 * @memberof mixitup.Config.classNames 3001 * @instance 3002 * @type {string} 3003 * @default '-' 3004 */ 3005 3006 this.delineatorModifier = '-'; 3007 3008 this.callActions('afterConstruct'); 3009 3010 h.seal(this); 3011 }; 3012 3013 mixitup.BaseStatic.call(mixitup.ConfigClassNames); 3014 3015 mixitup.ConfigClassNames.prototype = Object.create(mixitup.Base.prototype); 3016 3017 mixitup.ConfigClassNames.prototype.constructor = mixitup.ConfigClassNames; 3018 3019 /** 3020 * A group of properties relating to MixItUp's dataset API. 3021 * 3022 * @constructor 3023 * @memberof mixitup.Config 3024 * @name data 3025 * @namespace 3026 * @public 3027 * @since 3.0.0 3028 */ 3029 3030 mixitup.ConfigData = function() { 3031 mixitup.Base.call(this); 3032 3033 this.callActions('beforeConstruct'); 3034 3035 /** 3036 * A string specifying the name of the key containing your data model's unique 3037 * identifier (UID). To use the dataset API, a UID key must be specified and 3038 * be present and unique on all objects in the dataset you provide to MixItUp. 3039 * 3040 * For example, if your dataset is made up of MongoDB documents, the UID 3041 * key would be `'id'` or `'_id'`. 3042 * 3043 * @example <caption>Example: Setting the UID to `'id'`</caption> 3044 * var mixer = mixitup(containerEl, { 3045 * data: { 3046 * uidKey: 'id' 3047 * } 3048 * }); 3049 * 3050 * @name uidKey 3051 * @memberof mixitup.Config.data 3052 * @instance 3053 * @type {string} 3054 * @default '' 3055 */ 3056 3057 this.uidKey = ''; 3058 3059 /** 3060 * A boolean dictating whether or not MixItUp should "dirty check" each object in 3061 * your dataset for changes whenever `.dataset()` is called, and re-render any targets 3062 * for which a change is found. 3063 * 3064 * Depending on the complexity of your data model, dirty checking can be expensive 3065 * and is therefore disabled by default. 3066 * 3067 * NB: For changes to be detected, a new immutable instance of the edited model must be 3068 * provided to mixitup, rather than manipulating properties on the existing instance. 3069 * If your changes are a result of a DB write and read, you will most likely be calling 3070 * `.dataset()` with a clean set of objects each time, so this will not be an issue. 3071 * 3072 * @example <caption>Example: Enabling dirty checking</caption> 3073 * 3074 * var myDataset = [ 3075 * { 3076 * id: 0, 3077 * title: "Blog Post Title 0" 3078 * ... 3079 * }, 3080 * { 3081 * id: 1, 3082 * title: "Blog Post Title 1" 3083 * ... 3084 * } 3085 * ]; 3086 * 3087 * // Instantiate a mixer with a pre-loaded dataset, and a target renderer 3088 * // function defined 3089 * 3090 * var mixer = mixitup(containerEl, { 3091 * data: { 3092 * uidKey: 'id', 3093 * dirtyCheck: true 3094 * }, 3095 * load: { 3096 * dataset: myDataset 3097 * }, 3098 * render: { 3099 * target: function() { ... } 3100 * } 3101 * }); 3102 * 3103 * // For illustration, we will clone and edit the second object in the dataset. 3104 * // NB: this would typically be done server-side in response to a DB update, 3105 * and then re-queried via an API. 3106 * 3107 * myDataset[1] = Object.assign({}, myDataset[1]); 3108 * 3109 * myDataset[1].title = 'Blog Post Title 11'; 3110 * 3111 * mixer.dataset(myDataset) 3112 * .then(function() { 3113 * // the target with ID "1", will be re-rendered reflecting its new title 3114 * }); 3115 * 3116 * @name dirtyCheck 3117 * @memberof mixitup.Config.data 3118 * @instance 3119 * @type {boolean} 3120 * @default false 3121 */ 3122 3123 this.dirtyCheck = false; 3124 3125 this.callActions('afterConstruct'); 3126 3127 h.seal(this); 3128 }; 3129 3130 mixitup.BaseStatic.call(mixitup.ConfigData); 3131 3132 mixitup.ConfigData.prototype = Object.create(mixitup.Base.prototype); 3133 3134 mixitup.ConfigData.prototype.constructor = mixitup.ConfigData; 3135 3136 /** 3137 * A group of properties allowing the toggling of various debug features. 3138 * 3139 * @constructor 3140 * @memberof mixitup.Config 3141 * @name debug 3142 * @namespace 3143 * @public 3144 * @since 3.0.0 3145 */ 3146 3147 mixitup.ConfigDebug = function() { 3148 mixitup.Base.call(this); 3149 3150 this.callActions('beforeConstruct'); 3151 3152 /** 3153 * A boolean dictating whether or not the mixer instance returned by the 3154 * `mixitup()` factory function should expose private properties and methods. 3155 * 3156 * By default, mixer instances only expose their public API, but enabling 3157 * debug mode will give you access to various mixer internals which may aid 3158 * in debugging, or the authoring of extensions. 3159 * 3160 * @example <caption>Example: Enabling debug mode</caption> 3161 * 3162 * var mixer = mixitup(containerEl, { 3163 * debug: { 3164 * enable: true 3165 * } 3166 * }); 3167 * 3168 * // Private properties and methods will now be visible on the mixer instance: 3169 * 3170 * console.log(mixer); 3171 * 3172 * @name enable 3173 * @memberof mixitup.Config.debug 3174 * @instance 3175 * @type {boolean} 3176 * @default false 3177 */ 3178 3179 this.enable = false; 3180 3181 /** 3182 * A boolean dictating whether or not warnings should be shown when various 3183 * common gotchas occur. 3184 * 3185 * Warnings are intended to provide insights during development when something 3186 * occurs that is not a fatal, but may indicate an issue with your integration, 3187 * and are therefore turned on by default. However, you may wish to disable 3188 * them in production. 3189 * 3190 * @example <caption>Example 1: Disabling warnings</caption> 3191 * 3192 * var mixer = mixitup(containerEl, { 3193 * debug: { 3194 * showWarnings: false 3195 * } 3196 * }); 3197 * 3198 * @example <caption>Example 2: Disabling warnings based on environment</caption> 3199 * 3200 * var showWarnings = myAppConfig.environment === 'development' ? true : false; 3201 * 3202 * var mixer = mixitup(containerEl, { 3203 * debug: { 3204 * showWarnings: showWarnings 3205 * } 3206 * }); 3207 * 3208 * @name showWarnings 3209 * @memberof mixitup.Config.debug 3210 * @instance 3211 * @type {boolean} 3212 * @default true 3213 */ 3214 3215 this.showWarnings = true; 3216 3217 /** 3218 * Used for server-side testing only. 3219 * 3220 * @private 3221 * @name fauxAsync 3222 * @memberof mixitup.Config.debug 3223 * @instance 3224 * @type {boolean} 3225 * @default false 3226 */ 3227 3228 this.fauxAsync = false; 3229 3230 this.callActions('afterConstruct'); 3231 3232 h.seal(this); 3233 }; 3234 3235 mixitup.BaseStatic.call(mixitup.ConfigDebug); 3236 3237 mixitup.ConfigDebug.prototype = Object.create(mixitup.Base.prototype); 3238 3239 mixitup.ConfigDebug.prototype.constructor = mixitup.ConfigDebug; 3240 3241 /** 3242 * A group of properties relating to the layout of the container. 3243 * 3244 * @constructor 3245 * @memberof mixitup.Config 3246 * @name layout 3247 * @namespace 3248 * @public 3249 * @since 3.0.0 3250 */ 3251 3252 mixitup.ConfigLayout = function() { 3253 mixitup.Base.call(this); 3254 3255 this.callActions('beforeConstruct'); 3256 3257 /** 3258 * A boolean dictating whether or not mixitup should query all descendants 3259 * of the container for targets, or only immediate children. 3260 * 3261 * By default, mixitup will query all descendants matching the 3262 * `selectors.target` selector when indexing targets upon instantiation. 3263 * This allows for targets to be nested inside a sub-container which is 3264 * useful when ring-fencing targets from locally scoped controls in your 3265 * markup (see `controls.scope`). 3266 * 3267 * However, if you are building a more complex UI requiring the nesting 3268 * of mixers within mixers, you will most likely want to limit targets to 3269 * immediate children of the container by setting this property to `false`. 3270 * 3271 * @example <caption>Example: Restricting targets to immediate children</caption> 3272 * 3273 * var mixer = mixitup(containerEl, { 3274 * layout: { 3275 * allowNestedTargets: false 3276 * } 3277 * }); 3278 * 3279 * @name allowNestedTargets 3280 * @memberof mixitup.Config.layout 3281 * @instance 3282 * @type {boolean} 3283 * @default true 3284 */ 3285 3286 this.allowNestedTargets = true; 3287 3288 /** 3289 * A string specifying an optional class name to apply to the container when in 3290 * its default state. 3291 * 3292 * By changing this class name or adding a class name to the container via the 3293 * `.changeLayout()` API method, the CSS layout of the container can be changed, 3294 * and MixItUp will attemp to gracefully animate the container and its targets 3295 * between states. 3296 * 3297 * @example <caption>Example 1: Specifying a container class name</caption> 3298 * 3299 * var mixer = mixitup(containerEl, { 3300 * layout: { 3301 * containerClassName: 'grid' 3302 * } 3303 * }); 3304 * 3305 * @example <caption>Example 2: Changing the default class name with `.changeLayout()`</caption> 3306 * 3307 * var mixer = mixitup(containerEl, { 3308 * layout: { 3309 * containerClassName: 'grid' 3310 * } 3311 * }); 3312 * 3313 * mixer.changeLayout('list') 3314 * .then(function(state) { 3315 * console.log(state.activeContainerClass); // "list" 3316 * }); 3317 * 3318 * @name containerClassName 3319 * @memberof mixitup.Config.layout 3320 * @instance 3321 * @type {string} 3322 * @default '' 3323 */ 3324 3325 this.containerClassName = ''; 3326 3327 /** 3328 * A reference to a non-target sibling element after which to insert targets 3329 * when there are no targets in the container. 3330 * 3331 * @example <caption>Example: Setting a `siblingBefore` reference element</caption> 3332 * 3333 * var addButton = containerEl.querySelector('button'); 3334 * 3335 * var mixer = mixitup(containerEl, { 3336 * layout: { 3337 * siblingBefore: addButton 3338 * } 3339 * }); 3340 * 3341 * @name siblingBefore 3342 * @memberof mixitup.Config.layout 3343 * @instance 3344 * @type {HTMLElement} 3345 * @default null 3346 */ 3347 3348 this.siblingBefore = null; 3349 3350 /** 3351 * A reference to a non-target sibling element before which to insert targets 3352 * when there are no targets in the container. 3353 * 3354 * @example <caption>Example: Setting an `siblingAfter` reference element</caption> 3355 * 3356 * var gap = containerEl.querySelector('.gap'); 3357 * 3358 * var mixer = mixitup(containerEl, { 3359 * layout: { 3360 * siblingAfter: gap 3361 * } 3362 * }); 3363 * 3364 * @name siblingAfter 3365 * @memberof mixitup.Config.layout 3366 * @instance 3367 * @type {HTMLElement} 3368 * @default null 3369 */ 3370 3371 this.siblingAfter = null; 3372 3373 this.callActions('afterConstruct'); 3374 3375 h.seal(this); 3376 }; 3377 3378 mixitup.BaseStatic.call(mixitup.ConfigLayout); 3379 3380 mixitup.ConfigLayout.prototype = Object.create(mixitup.Base.prototype); 3381 3382 mixitup.ConfigLayout.prototype.constructor = mixitup.ConfigLayout; 3383 3384 /** 3385 * A group of properties defining the initial state of the mixer on load (instantiation). 3386 * 3387 * @constructor 3388 * @memberof mixitup.Config 3389 * @name load 3390 * @namespace 3391 * @public 3392 * @since 2.0.0 3393 */ 3394 3395 mixitup.ConfigLoad = function() { 3396 mixitup.Base.call(this); 3397 3398 this.callActions('beforeConstruct'); 3399 3400 /** 3401 * A string defining any filtering to be statically applied to the mixer on load. 3402 * As per the `.filter()` API, this can be any valid selector string, or the 3403 * values `'all'` or `'none'`. 3404 * 3405 * @example <caption>Example 1: Defining an initial filter selector to be applied on load</caption> 3406 * 3407 * // The mixer will show only those targets matching '.category-a' on load. 3408 * 3409 * var mixer = mixitup(containerEl, { 3410 * load: { 3411 * filter: '.category-a' 3412 * } 3413 * }); 3414 * 3415 * @example <caption>Example 2: Hiding all targets on load</caption> 3416 * 3417 * // The mixer will show hide all targets on load. 3418 * 3419 * var mixer = mixitup(containerEl, { 3420 * load: { 3421 * filter: 'none' 3422 * } 3423 * }); 3424 * 3425 * @name filter 3426 * @memberof mixitup.Config.load 3427 * @instance 3428 * @type {string} 3429 * @default 'all' 3430 */ 3431 3432 this.filter = 'all'; 3433 3434 /** 3435 * A string defining any sorting to be statically applied to the mixer on load. 3436 * As per the `.sort()` API, this should be a valid "sort string" made up of 3437 * an attribute to sort by (or `'default'`) followed by an optional sorting 3438 * order, or the value `'random'`; 3439 * 3440 * @example <caption>Example: Defining sorting to be applied on load</caption> 3441 * 3442 * // The mixer will sort the container by the value of the `data-published-date` 3443 * // attribute, in descending order. 3444 * 3445 * var mixer = mixitup(containerEl, { 3446 * load: { 3447 * sort: 'published-date:desc' 3448 * } 3449 * }); 3450 * 3451 * @name sort 3452 * @memberof mixitup.Config.load 3453 * @instance 3454 * @type {string} 3455 * @default 'default:asc' 3456 */ 3457 3458 this.sort = 'default:asc'; 3459 3460 /** 3461 * An array of objects representing the underlying data of any pre-rendered targets, 3462 * when using the `.dataset()` API. 3463 * 3464 * NB: If targets are pre-rendered when the mixer is instantiated, this must be set. 3465 * 3466 * @example <caption>Example: Defining the initial underyling dataset</caption> 3467 * 3468 * var myDataset = [ 3469 * { 3470 * id: 0, 3471 * title: "Blog Post Title 0", 3472 * ... 3473 * }, 3474 * { 3475 * id: 1, 3476 * title: "Blog Post Title 1", 3477 * ... 3478 * } 3479 * ]; 3480 * 3481 * var mixer = mixitup(containerEl, { 3482 * data: { 3483 * uidKey: 'id' 3484 * }, 3485 * load: { 3486 * dataset: myDataset 3487 * } 3488 * }); 3489 * 3490 * @name dataset 3491 * @memberof mixitup.Config.load 3492 * @instance 3493 * @type {Array.<object>} 3494 * @default null 3495 */ 3496 3497 this.dataset = null; 3498 3499 this.callActions('afterConstruct'); 3500 3501 h.seal(this); 3502 }; 3503 3504 mixitup.BaseStatic.call(mixitup.ConfigLoad); 3505 3506 mixitup.ConfigLoad.prototype = Object.create(mixitup.Base.prototype); 3507 3508 mixitup.ConfigLoad.prototype.constructor = mixitup.ConfigLoad; 3509 3510 /** 3511 * A group of properties defining the selectors used to query elements within a mixitup container. 3512 * 3513 * @constructor 3514 * @memberof mixitup.Config 3515 * @name selectors 3516 * @namespace 3517 * @public 3518 * @since 3.0.0 3519 */ 3520 3521 mixitup.ConfigSelectors = function() { 3522 mixitup.Base.call(this); 3523 3524 this.callActions('beforeConstruct'); 3525 3526 /** 3527 * A selector string used to query and index target elements within the container. 3528 * 3529 * By default, the class selector `'.mix'` is used, but this can be changed to an 3530 * attribute or element selector to match the style of your project. 3531 * 3532 * @example <caption>Example 1: Changing the target selector</caption> 3533 * 3534 * var mixer = mixitup(containerEl, { 3535 * selectors: { 3536 * target: '.portfolio-item' 3537 * } 3538 * }); 3539 * 3540 * @example <caption>Example 2: Using an attribute selector as a target selector</caption> 3541 * 3542 * // The mixer will search for any children with the attribute `data-ref="mix"` 3543 * 3544 * var mixer = mixitup(containerEl, { 3545 * selectors: { 3546 * target: '[data-ref="mix"]' 3547 * } 3548 * }); 3549 * 3550 * @name target 3551 * @memberof mixitup.Config.selectors 3552 * @instance 3553 * @type {string} 3554 * @default '.mix' 3555 */ 3556 3557 this.target = '.mix'; 3558 3559 /** 3560 * A optional selector string used to add further specificity to the querying of control elements, 3561 * in addition to their mandatory data attribute (e.g. `data-filter`, `data-toggle`, `data-sort`). 3562 * 3563 * This can be used if other elements in your document must contain the above attributes 3564 * (e.g. for use in third-party scripts), and would otherwise interfere with MixItUp. Adding 3565 * an additional `control` selector of your choice allows MixItUp to restrict event handling 3566 * to only those elements matching the defined selector. 3567 * 3568 * @name control 3569 * @memberof mixitup.Config.selectors 3570 * @instance 3571 * @type {string} 3572 * @default '' 3573 * 3574 * @example <caption>Example 1: Adding a `selectors.control` selector</caption> 3575 * 3576 * var mixer = mixitup(containerEl, { 3577 * selectors: { 3578 * control: '.mixitup-control' 3579 * } 3580 * }); 3581 * 3582 * // Will not be handled: 3583 * // <button data-filter=".category-a"></button> 3584 * 3585 * // Will be handled: 3586 * // <button class="mixitup-control" data-filter=".category-a"></button> 3587 */ 3588 3589 this.control = ''; 3590 3591 this.callActions('afterConstruct'); 3592 3593 h.seal(this); 3594 }; 3595 3596 mixitup.BaseStatic.call(mixitup.ConfigSelectors); 3597 3598 mixitup.ConfigSelectors.prototype = Object.create(mixitup.Base.prototype); 3599 3600 mixitup.ConfigSelectors.prototype.constructor = mixitup.ConfigSelectors; 3601 3602 /** 3603 * A group of optional render functions for creating and updating elements. 3604 * 3605 * All render functions receive a data object, and should return a valid HTML string. 3606 * 3607 * @constructor 3608 * @memberof mixitup.Config 3609 * @name render 3610 * @namespace 3611 * @public 3612 * @since 3.0.0 3613 */ 3614 3615 mixitup.ConfigRender = function() { 3616 mixitup.Base.call(this); 3617 3618 this.callActions('beforeConstruct'); 3619 3620 /** 3621 * A function returning an HTML string representing a target element, or a reference to a 3622 * single DOM element. 3623 * 3624 * The function is invoked as part of the `.dataset()` API, whenever a new item is added 3625 * to the dataset, or an item in the dataset changes (if `dataset.dirtyCheck` is enabled). 3626 * 3627 * The function receives the relevant dataset item as its first parameter. 3628 * 3629 * @example <caption>Example 1: Using string concatenation</caption> 3630 * 3631 * var mixer = mixitup(containerEl, { 3632 * render: { 3633 * target: function(item) { 3634 * return ( 3635 * '<div class="mix">' + 3636 * '<h2>' + item.title + '</h2>' + 3637 * '</div>' 3638 * ); 3639 * } 3640 * } 3641 * }); 3642 * 3643 * @example <caption>Example 2: Using an ES2015 template literal</caption> 3644 * 3645 * var mixer = mixitup(containerEl, { 3646 * render: { 3647 * target: function(item) { 3648 * return ( 3649 * `<div class="mix"> 3650 * <h2>${item.title}</h2> 3651 * </div>` 3652 * ); 3653 * } 3654 * } 3655 * }); 3656 * 3657 * @example <caption>Example 3: Using a Handlebars template</caption> 3658 * 3659 * var targetTemplate = Handlebars.compile('<div class="mix"><h2>{{title}}</h2></div>'); 3660 * 3661 * var mixer = mixitup(containerEl, { 3662 * render: { 3663 * target: targetTemplate 3664 * } 3665 * }); 3666 * 3667 * @example <caption>Example 4: Returning a DOM element</caption> 3668 * 3669 * var mixer = mixitup(containerEl, { 3670 * render: { 3671 * target: function(item) { 3672 * // Create a single element using your framework's built-in renderer 3673 * 3674 * var el = ... 3675 * 3676 * return el; 3677 * } 3678 * } 3679 * }); 3680 * 3681 * @name target 3682 * @memberof mixitup.Config.render 3683 * @instance 3684 * @type {function} 3685 * @default 'null' 3686 */ 3687 3688 this.target = null; 3689 3690 this.callActions('afterConstruct'); 3691 3692 h.seal(this); 3693 }; 3694 3695 mixitup.BaseStatic.call(mixitup.ConfigRender); 3696 3697 mixitup.ConfigRender.prototype = Object.create(mixitup.Base.prototype); 3698 3699 mixitup.ConfigRender.prototype.constructor = mixitup.ConfigRender; 3700 3701 /** 3702 * @constructor 3703 * @memberof mixitup 3704 * @private 3705 * @since 3.0.0 3706 */ 3707 3708 mixitup.ConfigTemplates = function() { 3709 mixitup.Base.call(this); 3710 3711 this.callActions('beforeConstruct'); 3712 3713 this.callActions('afterConstruct'); 3714 3715 h.seal(this); 3716 }; 3717 3718 mixitup.BaseStatic.call(mixitup.ConfigTemplates); 3719 3720 mixitup.ConfigTemplates.prototype = Object.create(mixitup.Base.prototype); 3721 3722 mixitup.ConfigTemplates.prototype.constructor = mixitup.ConfigTemplates; 3723 3724 /** 3725 * `mixitup.Config` is an interface used for customising the functionality of a 3726 * mixer instance. It is organised into several semantically distinct sub-objects, 3727 * each one pertaining to a particular aspect of MixItUp functionality. 3728 * 3729 * An object literal containing any or all of the available properies, 3730 * known as the "configuration object", can be passed as the second parameter to 3731 * the `mixitup` factory function when creating a mixer instance to customise its 3732 * functionality as needed. 3733 * 3734 * If no configuration object is passed, the mixer instance will take on the default 3735 * configuration values detailed below. 3736 * 3737 * @example <caption>Example 1: Creating and passing the configuration object</caption> 3738 * // Create a configuration object with desired values 3739 * 3740 * var config = { 3741 * animation: { 3742 * enable: false 3743 * }, 3744 * selectors: { 3745 * target: '.item' 3746 * } 3747 * }; 3748 * 3749 * // Pass the configuration object to the mixitup factory function 3750 * 3751 * var mixer = mixitup(containerEl, config); 3752 * 3753 * @example <caption>Example 2: Passing the configuration object inline</caption> 3754 * // Typically, the configuration object is passed inline for brevity. 3755 * 3756 * var mixer = mixitup(containerEl, { 3757 * controls: { 3758 * live: true, 3759 * toggleLogic: 'and' 3760 * } 3761 * }); 3762 * 3763 * 3764 * @constructor 3765 * @memberof mixitup 3766 * @namespace 3767 * @public 3768 * @since 2.0.0 3769 */ 3770 3771 mixitup.Config = function() { 3772 mixitup.Base.call(this); 3773 3774 this.callActions('beforeConstruct'); 3775 3776 this.animation = new mixitup.ConfigAnimation(); 3777 this.behavior = new mixitup.ConfigBehavior(); 3778 this.callbacks = new mixitup.ConfigCallbacks(); 3779 this.controls = new mixitup.ConfigControls(); 3780 this.classNames = new mixitup.ConfigClassNames(); 3781 this.data = new mixitup.ConfigData(); 3782 this.debug = new mixitup.ConfigDebug(); 3783 this.layout = new mixitup.ConfigLayout(); 3784 this.load = new mixitup.ConfigLoad(); 3785 this.selectors = new mixitup.ConfigSelectors(); 3786 this.render = new mixitup.ConfigRender(); 3787 this.templates = new mixitup.ConfigTemplates(); 3788 3789 this.callActions('afterConstruct'); 3790 3791 h.seal(this); 3792 }; 3793 3794 mixitup.BaseStatic.call(mixitup.Config); 3795 3796 mixitup.Config.prototype = Object.create(mixitup.Base.prototype); 3797 3798 mixitup.Config.prototype.constructor = mixitup.Config; 3799 3800 /** 3801 * @constructor 3802 * @memberof mixitup 3803 * @private 3804 * @since 3.0.0 3805 */ 3806 3807 mixitup.MixerDom = function() { 3808 mixitup.Base.call(this); 3809 3810 this.callActions('beforeConstruct'); 3811 3812 this.document = null; 3813 this.body = null; 3814 this.container = null; 3815 this.parent = null; 3816 this.targets = []; 3817 3818 this.callActions('afterConstruct'); 3819 3820 h.seal(this); 3821 }; 3822 3823 mixitup.BaseStatic.call(mixitup.MixerDom); 3824 3825 mixitup.MixerDom.prototype = Object.create(mixitup.Base.prototype); 3826 3827 mixitup.MixerDom.prototype.constructor = mixitup.MixerDom; 3828 3829 /** 3830 * @constructor 3831 * @memberof mixitup 3832 * @private 3833 * @since 3.0.0 3834 */ 3835 3836 mixitup.UiClassNames = function() { 3837 mixitup.Base.call(this); 3838 3839 this.callActions('beforeConstruct'); 3840 3841 this.base = ''; 3842 this.active = ''; 3843 this.disabled = ''; 3844 3845 this.callActions('afterConstruct'); 3846 3847 h.seal(this); 3848 }; 3849 3850 mixitup.BaseStatic.call(mixitup.UiClassNames); 3851 3852 mixitup.UiClassNames.prototype = Object.create(mixitup.Base.prototype); 3853 3854 mixitup.UiClassNames.prototype.constructor = mixitup.UiClassNames; 3855 3856 /** 3857 * An object into which all arbitrary arguments sent to '.dataset()' are mapped. 3858 * 3859 * @constructor 3860 * @memberof mixitup 3861 * @private 3862 * @since 3.0.0 3863 */ 3864 3865 mixitup.CommandDataset = function() { 3866 mixitup.Base.call(this); 3867 3868 this.callActions('beforeConstruct'); 3869 3870 this.dataset = null; 3871 3872 this.callActions('afterConstruct'); 3873 3874 h.seal(this); 3875 }; 3876 3877 mixitup.BaseStatic.call(mixitup.CommandDataset); 3878 3879 mixitup.CommandDataset.prototype = Object.create(mixitup.Base.prototype); 3880 3881 mixitup.CommandDataset.prototype.constructor = mixitup.CommandDataset; 3882 3883 /** 3884 * An object into which all arbitrary arguments sent to '.multimix()' are mapped. 3885 * 3886 * @constructor 3887 * @memberof mixitup 3888 * @private 3889 * @since 3.0.0 3890 */ 3891 3892 mixitup.CommandMultimix = function() { 3893 mixitup.Base.call(this); 3894 3895 this.callActions('beforeConstruct'); 3896 3897 this.filter = null; 3898 this.sort = null; 3899 this.insert = null; 3900 this.remove = null; 3901 this.changeLayout = null; 3902 3903 this.callActions('afterConstruct'); 3904 3905 h.seal(this); 3906 }; 3907 3908 mixitup.BaseStatic.call(mixitup.CommandMultimix); 3909 3910 mixitup.CommandMultimix.prototype = Object.create(mixitup.Base.prototype); 3911 3912 mixitup.CommandMultimix.prototype.constructor = mixitup.CommandMultimix; 3913 3914 /** 3915 * An object into which all arbitrary arguments sent to '.filter()' are mapped. 3916 * 3917 * @constructor 3918 * @memberof mixitup 3919 * @private 3920 * @since 3.0.0 3921 */ 3922 3923 mixitup.CommandFilter = function() { 3924 mixitup.Base.call(this); 3925 3926 this.callActions('beforeConstruct'); 3927 3928 this.selector = ''; 3929 this.collection = null; 3930 this.action = 'show'; // enum: ['show', 'hide'] 3931 3932 this.callActions('afterConstruct'); 3933 3934 h.seal(this); 3935 }; 3936 3937 mixitup.BaseStatic.call(mixitup.CommandFilter); 3938 3939 mixitup.CommandFilter.prototype = Object.create(mixitup.Base.prototype); 3940 3941 mixitup.CommandFilter.prototype.constructor = mixitup.CommandFilter; 3942 3943 /** 3944 * An object into which all arbitrary arguments sent to '.sort()' are mapped. 3945 * 3946 * @constructor 3947 * @memberof mixitup 3948 * @private 3949 * @since 3.0.0 3950 */ 3951 3952 mixitup.CommandSort = function() { 3953 mixitup.Base.call(this); 3954 3955 this.callActions('beforeConstruct'); 3956 3957 this.sortString = ''; 3958 this.attribute = ''; 3959 this.order = 'asc'; 3960 this.collection = null; 3961 this.next = null; 3962 3963 this.callActions('afterConstruct'); 3964 3965 h.seal(this); 3966 }; 3967 3968 mixitup.BaseStatic.call(mixitup.CommandSort); 3969 3970 mixitup.CommandSort.prototype = Object.create(mixitup.Base.prototype); 3971 3972 mixitup.CommandSort.prototype.constructor = mixitup.CommandSort; 3973 3974 /** 3975 * An object into which all arbitrary arguments sent to '.insert()' are mapped. 3976 * 3977 * @constructor 3978 * @memberof mixitup 3979 * @private 3980 * @since 3.0.0 3981 */ 3982 3983 mixitup.CommandInsert = function() { 3984 mixitup.Base.call(this); 3985 3986 this.callActions('beforeConstruct'); 3987 3988 this.index = 0; 3989 this.collection = []; 3990 this.position = 'before'; // enum: ['before', 'after'] 3991 this.sibling = null; 3992 3993 this.callActions('afterConstruct'); 3994 3995 h.seal(this); 3996 }; 3997 3998 mixitup.BaseStatic.call(mixitup.CommandInsert); 3999 4000 mixitup.CommandInsert.prototype = Object.create(mixitup.Base.prototype); 4001 4002 mixitup.CommandInsert.prototype.constructor = mixitup.CommandInsert; 4003 4004 /** 4005 * An object into which all arbitrary arguments sent to '.remove()' are mapped. 4006 * 4007 * @constructor 4008 * @memberof mixitup 4009 * @private 4010 * @since 3.0.0 4011 */ 4012 4013 mixitup.CommandRemove = function() { 4014 mixitup.Base.call(this); 4015 4016 this.callActions('beforeConstruct'); 4017 4018 this.targets = []; 4019 this.collection = []; 4020 4021 this.callActions('afterConstruct'); 4022 4023 h.seal(this); 4024 }; 4025 4026 mixitup.BaseStatic.call(mixitup.CommandRemove); 4027 4028 mixitup.CommandRemove.prototype = Object.create(mixitup.Base.prototype); 4029 4030 mixitup.CommandRemove.prototype.constructor = mixitup.CommandRemove; 4031 4032 /** 4033 * An object into which all arbitrary arguments sent to '.changeLayout()' are mapped. 4034 * 4035 * @constructor 4036 * @memberof mixitup 4037 * @private 4038 * @since 3.0.0 4039 */ 4040 4041 mixitup.CommandChangeLayout = function() { 4042 mixitup.Base.call(this); 4043 4044 this.callActions('beforeConstruct'); 4045 4046 this.containerClassName = ''; 4047 4048 this.callActions('afterConstruct'); 4049 4050 h.seal(this); 4051 }; 4052 4053 mixitup.BaseStatic.call(mixitup.CommandChangeLayout); 4054 4055 mixitup.CommandChangeLayout.prototype = Object.create(mixitup.Base.prototype); 4056 4057 mixitup.CommandChangeLayout.prototype.constructor = mixitup.CommandChangeLayout; 4058 4059 /** 4060 * @constructor 4061 * @memberof mixitup 4062 * @private 4063 * @since 3.0.0 4064 * @param {string} type 4065 * @param {string} selector 4066 * @param {boolean} [live] 4067 * @param {string} [parent] 4068 * An optional string representing the name of the mixer.dom property containing a reference to a parent element. 4069 */ 4070 4071 mixitup.ControlDefinition = function(type, selector, live, parent) { 4072 mixitup.Base.call(this); 4073 4074 this.callActions('beforeConstruct'); 4075 4076 this.type = type; 4077 this.selector = selector; 4078 this.live = live || false; 4079 this.parent = parent || ''; 4080 4081 this.callActions('afterConstruct'); 4082 4083 h.freeze(this); 4084 h.seal(this); 4085 }; 4086 4087 mixitup.BaseStatic.call(mixitup.ControlDefinition); 4088 4089 mixitup.ControlDefinition.prototype = Object.create(mixitup.Base.prototype); 4090 4091 mixitup.ControlDefinition.prototype.constructor = mixitup.ControlDefinition; 4092 4093 mixitup.controlDefinitions = []; 4094 4095 mixitup.controlDefinitions.push(new mixitup.ControlDefinition('multimix', '[data-filter][data-sort]')); 4096 mixitup.controlDefinitions.push(new mixitup.ControlDefinition('filter', '[data-filter]')); 4097 mixitup.controlDefinitions.push(new mixitup.ControlDefinition('sort', '[data-sort]')); 4098 mixitup.controlDefinitions.push(new mixitup.ControlDefinition('toggle', '[data-toggle]')); 4099 4100 /** 4101 * @constructor 4102 * @memberof mixitup 4103 * @private 4104 * @since 3.0.0 4105 */ 4106 4107 mixitup.Control = function() { 4108 mixitup.Base.call(this); 4109 4110 this.callActions('beforeConstruct'); 4111 4112 this.el = null; 4113 this.selector = ''; 4114 this.bound = []; 4115 this.pending = -1; 4116 this.type = ''; 4117 this.status = 'inactive'; // enum: ['inactive', 'active', 'disabled', 'live'] 4118 this.filter = ''; 4119 this.sort = ''; 4120 this.canDisable = false; 4121 this.handler = null; 4122 this.classNames = new mixitup.UiClassNames(); 4123 4124 this.callActions('afterConstruct'); 4125 4126 h.seal(this); 4127 }; 4128 4129 mixitup.BaseStatic.call(mixitup.Control); 4130 4131 mixitup.Control.prototype = Object.create(mixitup.Base.prototype); 4132 4133 h.extend(mixitup.Control.prototype, 4134 /** @lends mixitup.Control */ 4135 { 4136 constructor: mixitup.Control, 4137 4138 /** 4139 * @private 4140 * @param {HTMLElement} el 4141 * @param {string} type 4142 * @param {string} selector 4143 */ 4144 4145 init: function(el, type, selector) { 4146 var self = this; 4147 4148 this.callActions('beforeInit', arguments); 4149 4150 self.el = el; 4151 self.type = type; 4152 self.selector = selector; 4153 4154 if (self.selector) { 4155 self.status = 'live'; 4156 } else { 4157 self.canDisable = typeof self.el.disable === 'boolean'; 4158 4159 switch (self.type) { 4160 case 'filter': 4161 self.filter = self.el.getAttribute('data-filter'); 4162 4163 break; 4164 case 'toggle': 4165 self.filter = self.el.getAttribute('data-toggle'); 4166 4167 break; 4168 case 'sort': 4169 self.sort = self.el.getAttribute('data-sort'); 4170 4171 break; 4172 case 'multimix': 4173 self.filter = self.el.getAttribute('data-filter'); 4174 self.sort = self.el.getAttribute('data-sort'); 4175 4176 break; 4177 } 4178 } 4179 4180 self.bindClick(); 4181 4182 mixitup.controls.push(self); 4183 4184 this.callActions('afterInit', arguments); 4185 }, 4186 4187 /** 4188 * @private 4189 * @param {mixitup.Mixer} mixer 4190 * @return {boolean} 4191 */ 4192 4193 isBound: function(mixer) { 4194 var self = this, 4195 isBound = false; 4196 4197 this.callActions('beforeIsBound', arguments); 4198 4199 isBound = self.bound.indexOf(mixer) > -1; 4200 4201 return self.callFilters('afterIsBound', isBound, arguments); 4202 }, 4203 4204 /** 4205 * @private 4206 * @param {mixitup.Mixer} mixer 4207 * @return {void} 4208 */ 4209 4210 addBinding: function(mixer) { 4211 var self = this; 4212 4213 this.callActions('beforeAddBinding', arguments); 4214 4215 if (!self.isBound()) { 4216 self.bound.push(mixer); 4217 } 4218 4219 this.callActions('afterAddBinding', arguments); 4220 }, 4221 4222 /** 4223 * @private 4224 * @param {mixitup.Mixer} mixer 4225 * @return {void} 4226 */ 4227 4228 removeBinding: function(mixer) { 4229 var self = this, 4230 removeIndex = -1; 4231 4232 this.callActions('beforeRemoveBinding', arguments); 4233 4234 if ((removeIndex = self.bound.indexOf(mixer)) > -1) { 4235 self.bound.splice(removeIndex, 1); 4236 } 4237 4238 if (self.bound.length < 1) { 4239 // No bindings exist, unbind event click handlers 4240 4241 self.unbindClick(); 4242 4243 // Remove from `mixitup.controls` list 4244 4245 removeIndex = mixitup.controls.indexOf(self); 4246 4247 mixitup.controls.splice(removeIndex, 1); 4248 4249 if (self.status === 'active') { 4250 self.renderStatus(self.el, 'inactive'); 4251 } 4252 } 4253 4254 this.callActions('afterRemoveBinding', arguments); 4255 }, 4256 4257 /** 4258 * @private 4259 * @return {void} 4260 */ 4261 4262 bindClick: function() { 4263 var self = this; 4264 4265 this.callActions('beforeBindClick', arguments); 4266 4267 self.handler = function(e) { 4268 self.handleClick(e); 4269 }; 4270 4271 h.on(self.el, 'click', self.handler); 4272 4273 this.callActions('afterBindClick', arguments); 4274 }, 4275 4276 /** 4277 * @private 4278 * @return {void} 4279 */ 4280 4281 unbindClick: function() { 4282 var self = this; 4283 4284 this.callActions('beforeUnbindClick', arguments); 4285 4286 h.off(self.el, 'click', self.handler); 4287 4288 self.handler = null; 4289 4290 this.callActions('afterUnbindClick', arguments); 4291 }, 4292 4293 /** 4294 * @private 4295 * @param {MouseEvent} e 4296 * @return {void} 4297 */ 4298 4299 handleClick: function(e) { 4300 var self = this, 4301 button = null, 4302 mixer = null, 4303 isActive = false, 4304 returnValue = void(0), 4305 command = {}, 4306 clone = null, 4307 commands = [], 4308 i = -1; 4309 4310 this.callActions('beforeHandleClick', arguments); 4311 4312 this.pending = 0; 4313 4314 mixer = self.bound[0]; 4315 4316 if (!self.selector) { 4317 button = self.el; 4318 } else { 4319 button = h.closestParent(e.target, mixer.config.selectors.control + self.selector, true, mixer.dom.document); 4320 } 4321 4322 if (!button) { 4323 self.callActions('afterHandleClick', arguments); 4324 4325 return; 4326 } 4327 4328 switch (self.type) { 4329 case 'filter': 4330 command.filter = self.filter || button.getAttribute('data-filter'); 4331 4332 break; 4333 case 'sort': 4334 command.sort = self.sort || button.getAttribute('data-sort'); 4335 4336 break; 4337 case 'multimix': 4338 command.filter = self.filter || button.getAttribute('data-filter'); 4339 command.sort = self.sort || button.getAttribute('data-sort'); 4340 4341 break; 4342 case 'toggle': 4343 command.filter = self.filter || button.getAttribute('data-toggle'); 4344 4345 if (self.status === 'live') { 4346 isActive = h.hasClass(button, self.classNames.active); 4347 } else { 4348 isActive = self.status === 'active'; 4349 } 4350 4351 break; 4352 } 4353 4354 for (i = 0; i < self.bound.length; i++) { 4355 // Create a clone of the command for each bound mixer instance 4356 4357 clone = new mixitup.CommandMultimix(); 4358 4359 h.extend(clone, command); 4360 4361 commands.push(clone); 4362 } 4363 4364 commands = self.callFilters('commandsHandleClick', commands, arguments); 4365 4366 self.pending = self.bound.length; 4367 4368 for (i = 0; mixer = self.bound[i]; i++) { 4369 command = commands[i]; 4370 4371 if (!command) { 4372 // An extension may set a command null to indicate that the click should not be handled 4373 4374 continue; 4375 } 4376 4377 if (!mixer.lastClicked) { 4378 mixer.lastClicked = button; 4379 } 4380 4381 mixitup.events.fire('mixClick', mixer.dom.container, { 4382 state: mixer.state, 4383 instance: mixer, 4384 originalEvent: e, 4385 control: mixer.lastClicked 4386 }, mixer.dom.document); 4387 4388 if (typeof mixer.config.callbacks.onMixClick === 'function') { 4389 returnValue = mixer.config.callbacks.onMixClick.call(mixer.lastClicked, mixer.state, e, mixer); 4390 4391 if (returnValue === false) { 4392 // User has returned `false` from the callback, so do not handle click 4393 4394 continue; 4395 } 4396 } 4397 4398 if (self.type === 'toggle') { 4399 isActive ? mixer.toggleOff(command.filter) : mixer.toggleOn(command.filter); 4400 } else { 4401 mixer.multimix(command); 4402 } 4403 } 4404 4405 this.callActions('afterHandleClick', arguments); 4406 }, 4407 4408 /** 4409 * @param {object} command 4410 * @param {Array<string>} toggleArray 4411 * @return {void} 4412 */ 4413 4414 update: function(command, toggleArray) { 4415 var self = this, 4416 actions = new mixitup.CommandMultimix(); 4417 4418 self.callActions('beforeUpdate', arguments); 4419 4420 self.pending--; 4421 4422 self.pending = Math.max(0, self.pending); 4423 4424 if (self.pending > 0) return; 4425 4426 if (self.status === 'live') { 4427 // Live control (status unknown) 4428 4429 self.updateLive(command, toggleArray); 4430 } else { 4431 // Static control 4432 4433 actions.sort = self.sort; 4434 actions.filter = self.filter; 4435 4436 self.callFilters('actionsUpdate', actions, arguments); 4437 4438 self.parseStatusChange(self.el, command, actions, toggleArray); 4439 } 4440 4441 self.callActions('afterUpdate', arguments); 4442 }, 4443 4444 /** 4445 * @param {mixitup.CommandMultimix} command 4446 * @param {Array<string>} toggleArray 4447 * @return {void} 4448 */ 4449 4450 updateLive: function(command, toggleArray) { 4451 var self = this, 4452 controlButtons = null, 4453 actions = null, 4454 button = null, 4455 i = -1; 4456 4457 self.callActions('beforeUpdateLive', arguments); 4458 4459 if (!self.el) return; 4460 4461 controlButtons = self.el.querySelectorAll(self.selector); 4462 4463 for (i = 0; button = controlButtons[i]; i++) { 4464 actions = new mixitup.CommandMultimix(); 4465 4466 switch (self.type) { 4467 case 'filter': 4468 actions.filter = button.getAttribute('data-filter'); 4469 4470 break; 4471 case 'sort': 4472 actions.sort = button.getAttribute('data-sort'); 4473 4474 break; 4475 case 'multimix': 4476 actions.filter = button.getAttribute('data-filter'); 4477 actions.sort = button.getAttribute('data-sort'); 4478 4479 break; 4480 case 'toggle': 4481 actions.filter = button.getAttribute('data-toggle'); 4482 4483 break; 4484 } 4485 4486 actions = self.callFilters('actionsUpdateLive', actions, arguments); 4487 4488 self.parseStatusChange(button, command, actions, toggleArray); 4489 } 4490 4491 self.callActions('afterUpdateLive', arguments); 4492 }, 4493 4494 /** 4495 * @param {HTMLElement} button 4496 * @param {mixitup.CommandMultimix} command 4497 * @param {mixitup.CommandMultimix} actions 4498 * @param {Array<string>} toggleArray 4499 * @return {void} 4500 */ 4501 4502 parseStatusChange: function(button, command, actions, toggleArray) { 4503 var self = this, 4504 alias = '', 4505 toggle = '', 4506 i = -1; 4507 4508 self.callActions('beforeParseStatusChange', arguments); 4509 4510 switch (self.type) { 4511 case 'filter': 4512 if (command.filter === actions.filter) { 4513 self.renderStatus(button, 'active'); 4514 } else { 4515 self.renderStatus(button, 'inactive'); 4516 } 4517 4518 break; 4519 case 'multimix': 4520 if (command.sort === actions.sort && command.filter === actions.filter) { 4521 self.renderStatus(button, 'active'); 4522 } else { 4523 self.renderStatus(button, 'inactive'); 4524 } 4525 4526 break; 4527 case 'sort': 4528 if (command.sort.match(/:asc/g)) { 4529 alias = command.sort.replace(/:asc/g, ''); 4530 } 4531 4532 if (command.sort === actions.sort || alias === actions.sort) { 4533 self.renderStatus(button, 'active'); 4534 } else { 4535 self.renderStatus(button, 'inactive'); 4536 } 4537 4538 break; 4539 case 'toggle': 4540 if (toggleArray.length < 1) self.renderStatus(button, 'inactive'); 4541 4542 if (command.filter === actions.filter) { 4543 self.renderStatus(button, 'active'); 4544 } 4545 4546 for (i = 0; i < toggleArray.length; i++) { 4547 toggle = toggleArray[i]; 4548 4549 if (toggle === actions.filter) { 4550 // Button matches one active toggle 4551 4552 self.renderStatus(button, 'active'); 4553 4554 break; 4555 } 4556 4557 self.renderStatus(button, 'inactive'); 4558 } 4559 4560 break; 4561 } 4562 4563 self.callActions('afterParseStatusChange', arguments); 4564 }, 4565 4566 /** 4567 * @param {HTMLElement} button 4568 * @param {string} status 4569 * @return {void} 4570 */ 4571 4572 renderStatus: function(button, status) { 4573 var self = this; 4574 4575 self.callActions('beforeRenderStatus', arguments); 4576 4577 switch (status) { 4578 case 'active': 4579 h.addClass(button, self.classNames.active); 4580 h.removeClass(button, self.classNames.disabled); 4581 4582 if (self.canDisable) self.el.disabled = false; 4583 4584 break; 4585 case 'inactive': 4586 h.removeClass(button, self.classNames.active); 4587 h.removeClass(button, self.classNames.disabled); 4588 4589 if (self.canDisable) self.el.disabled = false; 4590 4591 break; 4592 case 'disabled': 4593 if (self.canDisable) self.el.disabled = true; 4594 4595 h.addClass(button, self.classNames.disabled); 4596 h.removeClass(button, self.classNames.active); 4597 4598 break; 4599 } 4600 4601 if (self.status !== 'live') { 4602 // Update the control's status propery if not live 4603 4604 self.status = status; 4605 } 4606 4607 self.callActions('afterRenderStatus', arguments); 4608 } 4609 }); 4610 4611 mixitup.controls = []; 4612 4613 /** 4614 * @constructor 4615 * @memberof mixitup 4616 * @private 4617 * @since 3.0.0 4618 */ 4619 4620 mixitup.StyleData = function() { 4621 mixitup.Base.call(this); 4622 4623 this.callActions('beforeConstruct'); 4624 4625 this.x = 0; 4626 this.y = 0; 4627 this.top = 0; 4628 this.right = 0; 4629 this.bottom = 0; 4630 this.left = 0; 4631 this.width = 0; 4632 this.height = 0; 4633 this.marginRight = 0; 4634 this.marginBottom = 0; 4635 this.opacity = 0; 4636 this.scale = new mixitup.TransformData(); 4637 this.translateX = new mixitup.TransformData(); 4638 this.translateY = new mixitup.TransformData(); 4639 this.translateZ = new mixitup.TransformData(); 4640 this.rotateX = new mixitup.TransformData(); 4641 this.rotateY = new mixitup.TransformData(); 4642 this.rotateZ = new mixitup.TransformData(); 4643 4644 this.callActions('afterConstruct'); 4645 4646 h.seal(this); 4647 }; 4648 4649 mixitup.BaseStatic.call(mixitup.StyleData); 4650 4651 mixitup.StyleData.prototype = Object.create(mixitup.Base.prototype); 4652 4653 mixitup.StyleData.prototype.constructor = mixitup.StyleData; 4654 4655 /** 4656 * @constructor 4657 * @memberof mixitup 4658 * @private 4659 * @since 3.0.0 4660 */ 4661 4662 mixitup.TransformData = function() { 4663 mixitup.Base.call(this); 4664 4665 this.callActions('beforeConstruct'); 4666 4667 this.value = 0; 4668 this.unit = ''; 4669 4670 this.callActions('afterConstruct'); 4671 4672 h.seal(this); 4673 }; 4674 4675 mixitup.BaseStatic.call(mixitup.TransformData); 4676 4677 mixitup.TransformData.prototype = Object.create(mixitup.Base.prototype); 4678 4679 mixitup.TransformData.prototype.constructor = mixitup.TransformData; 4680 4681 /** 4682 * @constructor 4683 * @memberof mixitup 4684 * @private 4685 * @since 3.0.0 4686 */ 4687 4688 mixitup.TransformDefaults = function() { 4689 mixitup.StyleData.apply(this); 4690 4691 this.callActions('beforeConstruct'); 4692 4693 this.scale.value = 0.01; 4694 this.scale.unit = ''; 4695 4696 this.translateX.value = 20; 4697 this.translateX.unit = 'px'; 4698 4699 this.translateY.value = 20; 4700 this.translateY.unit = 'px'; 4701 4702 this.translateZ.value = 20; 4703 this.translateZ.unit = 'px'; 4704 4705 this.rotateX.value = 90; 4706 this.rotateX.unit = 'deg'; 4707 4708 this.rotateY.value = 90; 4709 this.rotateY.unit = 'deg'; 4710 4711 this.rotateX.value = 90; 4712 this.rotateX.unit = 'deg'; 4713 4714 this.rotateZ.value = 180; 4715 this.rotateZ.unit = 'deg'; 4716 4717 this.callActions('afterConstruct'); 4718 4719 h.seal(this); 4720 }; 4721 4722 mixitup.BaseStatic.call(mixitup.TransformDefaults); 4723 4724 mixitup.TransformDefaults.prototype = Object.create(mixitup.StyleData.prototype); 4725 4726 mixitup.TransformDefaults.prototype.constructor = mixitup.TransformDefaults; 4727 4728 /** 4729 * @private 4730 * @static 4731 * @since 3.0.0 4732 * @type {mixitup.TransformDefaults} 4733 */ 4734 4735 mixitup.transformDefaults = new mixitup.TransformDefaults(); 4736 4737 /** 4738 * @constructor 4739 * @memberof mixitup 4740 * @private 4741 * @since 3.0.0 4742 */ 4743 4744 mixitup.EventDetail = function() { 4745 this.state = null; 4746 this.futureState = null; 4747 this.instance = null; 4748 this.originalEvent = null; 4749 }; 4750 4751 /** 4752 * The `mixitup.Events` class contains all custom events dispatched by MixItUp at various 4753 * points within the lifecycle of a mixer operation. 4754 * 4755 * Each event is analogous to the callback function of the same name defined in 4756 * the `callbacks` configuration object, and is triggered immediately before it. 4757 * 4758 * Events are always triggered from the container element on which MixItUp is instantiated 4759 * upon. 4760 * 4761 * As with any event, registered event handlers receive the event object as a parameter 4762 * which includes a `detail` property containting references to the current `state`, 4763 * the `mixer` instance, and other event-specific properties described below. 4764 * 4765 * @constructor 4766 * @namespace 4767 * @memberof mixitup 4768 * @public 4769 * @since 3.0.0 4770 */ 4771 4772 mixitup.Events = function() { 4773 mixitup.Base.call(this); 4774 4775 this.callActions('beforeConstruct'); 4776 4777 /** 4778 * A custom event triggered immediately after any MixItUp operation is requested 4779 * and before animations have begun. 4780 * 4781 * The `mixStart` event also exposes a `futureState` property via the 4782 * `event.detail` object, which represents the final state of the mixer once 4783 * the requested operation has completed. 4784 * 4785 * @name mixStart 4786 * @memberof mixitup.Events 4787 * @static 4788 * @type {CustomEvent} 4789 */ 4790 4791 this.mixStart = null; 4792 4793 /** 4794 * A custom event triggered when a MixItUp operation is requested while another 4795 * operation is in progress, and the animation queue is full, or queueing 4796 * is disabled. 4797 * 4798 * @name mixBusy 4799 * @memberof mixitup.Events 4800 * @static 4801 * @type {CustomEvent} 4802 */ 4803 4804 this.mixBusy = null; 4805 4806 /** 4807 * A custom event triggered after any MixItUp operation has completed, and the 4808 * state has been updated. 4809 * 4810 * @name mixEnd 4811 * @memberof mixitup.Events 4812 * @static 4813 * @type {CustomEvent} 4814 */ 4815 4816 this.mixEnd = null; 4817 4818 /** 4819 * A custom event triggered whenever a filter operation "fails", i.e. no targets 4820 * could be found matching the requested filter. 4821 * 4822 * @name mixFail 4823 * @memberof mixitup.Events 4824 * @static 4825 * @type {CustomEvent} 4826 */ 4827 4828 this.mixFail = null; 4829 4830 /** 4831 * A custom event triggered whenever a MixItUp control is clicked, and before its 4832 * respective operation is requested. 4833 * 4834 * This event also exposes an `originalEvent` property via the `event.detail` 4835 * object, which holds a reference to the original click event. 4836 * 4837 * @name mixClick 4838 * @memberof mixitup.Events 4839 * @static 4840 * @type {CustomEvent} 4841 */ 4842 4843 this.mixClick = null; 4844 4845 this.callActions('afterConstruct'); 4846 4847 h.seal(this); 4848 }; 4849 4850 mixitup.BaseStatic.call(mixitup.Events); 4851 4852 mixitup.Events.prototype = Object.create(mixitup.Base.prototype); 4853 4854 mixitup.Events.prototype.constructor = mixitup.Events; 4855 4856 /** 4857 * @private 4858 * @param {string} eventType 4859 * @param {Element} el 4860 * @param {object} detail 4861 * @param {Document} [doc] 4862 */ 4863 4864 mixitup.Events.prototype.fire = function(eventType, el, detail, doc) { 4865 var self = this, 4866 event = null, 4867 eventDetail = new mixitup.EventDetail(); 4868 4869 self.callActions('beforeFire', arguments); 4870 4871 if (typeof self[eventType] === 'undefined') { 4872 throw new Error('Event type "' + eventType + '" not found.'); 4873 } 4874 4875 eventDetail.state = new mixitup.State(); 4876 4877 h.extend(eventDetail.state, detail.state); 4878 4879 if (detail.futureState) { 4880 eventDetail.futureState = new mixitup.State(); 4881 4882 h.extend(eventDetail.futureState, detail.futureState); 4883 } 4884 4885 eventDetail.instance = detail.instance; 4886 4887 if (detail.originalEvent) { 4888 eventDetail.originalEvent = detail.originalEvent; 4889 } 4890 4891 event = h.getCustomEvent(eventType, eventDetail, doc); 4892 4893 self.callFilters('eventFire', event, arguments); 4894 4895 el.dispatchEvent(event); 4896 }; 4897 4898 // Asign a singleton instance to `mixitup.events`: 4899 4900 mixitup.events = new mixitup.Events(); 4901 4902 /** 4903 * @constructor 4904 * @memberof mixitup 4905 * @private 4906 * @since 3.0.0 4907 */ 4908 4909 mixitup.QueueItem = function() { 4910 mixitup.Base.call(this); 4911 4912 this.callActions('beforeConstruct'); 4913 4914 this.args = []; 4915 this.instruction = null; 4916 this.triggerElement = null; 4917 this.deferred = null; 4918 this.isToggling = false; 4919 4920 this.callActions('afterConstruct'); 4921 4922 h.seal(this); 4923 }; 4924 4925 mixitup.BaseStatic.call(mixitup.QueueItem); 4926 4927 mixitup.QueueItem.prototype = Object.create(mixitup.Base.prototype); 4928 4929 mixitup.QueueItem.prototype.constructor = mixitup.QueueItem; 4930 4931 /** 4932 * The `mixitup.Mixer` class is used to hold discreet, user-configured 4933 * instances of MixItUp on a provided container element. 4934 * 4935 * Mixer instances are returned whenever the `mixitup()` factory function is called, 4936 * which expose a range of methods enabling API-based filtering, sorting, 4937 * insertion, removal and more. 4938 * 4939 * @constructor 4940 * @namespace 4941 * @memberof mixitup 4942 * @public 4943 * @since 3.0.0 4944 */ 4945 4946 mixitup.Mixer = function() { 4947 mixitup.Base.call(this); 4948 4949 this.callActions('beforeConstruct'); 4950 4951 this.config = new mixitup.Config(); 4952 4953 this.id = ''; 4954 4955 this.isBusy = false; 4956 this.isToggling = false; 4957 this.incPadding = true; 4958 4959 this.controls = []; 4960 this.targets = []; 4961 this.origOrder = []; 4962 this.cache = {}; 4963 4964 this.toggleArray = []; 4965 4966 this.targetsMoved = 0; 4967 this.targetsImmovable = 0; 4968 this.targetsBound = 0; 4969 this.targetsDone = 0; 4970 4971 this.staggerDuration = 0; 4972 this.effectsIn = null; 4973 this.effectsOut = null; 4974 this.transformIn = []; 4975 this.transformOut = []; 4976 this.queue = []; 4977 4978 this.state = null; 4979 this.lastOperation = null; 4980 this.lastClicked = null; 4981 this.userCallback = null; 4982 this.userDeferred = null; 4983 4984 this.dom = new mixitup.MixerDom(); 4985 4986 this.callActions('afterConstruct'); 4987 4988 h.seal(this); 4989 }; 4990 4991 mixitup.BaseStatic.call(mixitup.Mixer); 4992 4993 mixitup.Mixer.prototype = Object.create(mixitup.Base.prototype); 4994 4995 h.extend(mixitup.Mixer.prototype, 4996 /** @lends mixitup.Mixer */ 4997 { 4998 constructor: mixitup.Mixer, 4999 5000 /** 5001 * @private 5002 * @instance 5003 * @since 3.0.0 5004 * @param {HTMLElement} container 5005 * @param {HTMLElement} document 5006 * @param {string} id 5007 * @param {object} [config] 5008 */ 5009 5010 attach: function(container, document, id, config) { 5011 var self = this, 5012 target = null, 5013 i = -1; 5014 5015 self.callActions('beforeAttach', arguments); 5016 5017 self.id = id; 5018 5019 if (config) { 5020 h.extend(self.config, config, true, true); 5021 } 5022 5023 self.sanitizeConfig(); 5024 5025 self.cacheDom(container, document); 5026 5027 if (self.config.layout.containerClassName) { 5028 h.addClass(self.dom.container, self.config.layout.containerClassName); 5029 } 5030 5031 if (!mixitup.features.has.transitions) { 5032 self.config.animation.enable = false; 5033 } 5034 5035 if (typeof window.console === 'undefined') { 5036 self.config.debug.showWarnings = false; 5037 } 5038 5039 if (self.config.data.uidKey) { 5040 // If the dataset API is in use, force disable controls 5041 5042 self.config.controls.enable = false; 5043 } 5044 5045 self.indexTargets(); 5046 5047 self.state = self.getInitialState(); 5048 5049 for (i = 0; target = self.lastOperation.toHide[i]; i++) { 5050 target.hide(); 5051 } 5052 5053 if (self.config.controls.enable) { 5054 self.initControls(); 5055 5056 self.buildToggleArray(null, self.state); 5057 5058 self.updateControls({ 5059 filter: self.state.activeFilter, 5060 sort: self.state.activeSort 5061 }); 5062 } 5063 5064 self.parseEffects(); 5065 5066 self.callActions('afterAttach', arguments); 5067 }, 5068 5069 /** 5070 * @private 5071 * @instance 5072 * @since 3.0.0 5073 * @return {void} 5074 */ 5075 5076 sanitizeConfig: function() { 5077 var self = this; 5078 5079 self.callActions('beforeSanitizeConfig', arguments); 5080 5081 // Sanitize enum/string config options 5082 5083 self.config.controls.scope = self.config.controls.scope.toLowerCase().trim(); 5084 self.config.controls.toggleLogic = self.config.controls.toggleLogic.toLowerCase().trim(); 5085 self.config.controls.toggleDefault = self.config.controls.toggleDefault.toLowerCase().trim(); 5086 5087 self.config.animation.effects = self.config.animation.effects.trim(); 5088 5089 self.callActions('afterSanitizeConfig', arguments); 5090 }, 5091 5092 /** 5093 * @private 5094 * @instance 5095 * @since 3.0.0 5096 * @return {mixitup.State} 5097 */ 5098 5099 getInitialState: function() { 5100 var self = this, 5101 state = new mixitup.State(), 5102 operation = new mixitup.Operation(); 5103 5104 self.callActions('beforeGetInitialState', arguments); 5105 5106 // Map initial values into a mock state object in order to construct an operation 5107 5108 state.activeContainerClassName = self.config.layout.containerClassName; 5109 5110 if (self.config.load.dataset) { 5111 // Dataset API 5112 5113 if (!self.config.data.uidKey || typeof self.config.data.uidKey !== 'string') { 5114 throw new TypeError(mixitup.messages.errorConfigDataUidKeyNotSet()); 5115 } 5116 5117 operation.startDataset = operation.newDataset = state.activeDataset = self.config.load.dataset.slice(); 5118 operation.startContainerClassName = operation.newContainerClassName = state.activeContainerClassName; 5119 operation.show = self.targets.slice(); 5120 5121 state = self.callFilters('stateGetInitialState', state, arguments); 5122 } else { 5123 // DOM API 5124 5125 state.activeFilter = self.parseFilterArgs([self.config.load.filter]).command; 5126 state.activeSort = self.parseSortArgs([self.config.load.sort]).command; 5127 state.totalTargets = self.targets.length; 5128 5129 state = self.callFilters('stateGetInitialState', state, arguments); 5130 5131 if ( 5132 state.activeSort.collection || state.activeSort.attribute || 5133 state.activeSort.order === 'random' || state.activeSort.order === 'desc' 5134 ) { 5135 // Sorting on load 5136 5137 operation.newSort = state.activeSort; 5138 5139 self.sortOperation(operation); 5140 5141 self.printSort(false, operation); 5142 5143 self.targets = operation.newOrder; 5144 } else { 5145 operation.startOrder = operation.newOrder = self.targets; 5146 } 5147 5148 operation.startFilter = operation.newFilter = state.activeFilter; 5149 operation.startSort = operation.newSort = state.activeSort; 5150 operation.startContainerClassName = operation.newContainerClassName = state.activeContainerClassName; 5151 5152 if (operation.newFilter.selector === 'all') { 5153 operation.newFilter.selector = self.config.selectors.target; 5154 } else if (operation.newFilter.selector === 'none') { 5155 operation.newFilter.selector = ''; 5156 } 5157 } 5158 5159 operation = self.callFilters('operationGetInitialState', operation, [state]); 5160 5161 self.lastOperation = operation; 5162 5163 if (operation.newFilter) { 5164 self.filterOperation(operation); 5165 } 5166 5167 state = self.buildState(operation); 5168 5169 return state; 5170 }, 5171 5172 /** 5173 * Caches references of DOM elements neccessary for the mixer's functionality. 5174 * 5175 * @private 5176 * @instance 5177 * @since 3.0.0 5178 * @param {HTMLElement} el 5179 * @param {HTMLHtmlElement} document 5180 * @return {void} 5181 */ 5182 5183 cacheDom: function(el, document) { 5184 var self = this; 5185 5186 self.callActions('beforeCacheDom', arguments); 5187 5188 self.dom.document = document; 5189 self.dom.body = self.dom.document.querySelector('body'); 5190 self.dom.container = el; 5191 self.dom.parent = el; 5192 5193 self.callActions('afterCacheDom', arguments); 5194 }, 5195 5196 /** 5197 * Indexes all child elements of the mixer matching the `selectors.target` 5198 * selector, instantiating a mixitup.Target for each one. 5199 * 5200 * @private 5201 * @instance 5202 * @since 3.0.0 5203 * @return {void} 5204 */ 5205 5206 indexTargets: function() { 5207 var self = this, 5208 target = null, 5209 el = null, 5210 dataset = null, 5211 i = -1; 5212 5213 self.callActions('beforeIndexTargets', arguments); 5214 5215 self.dom.targets = self.config.layout.allowNestedTargets ? 5216 self.dom.container.querySelectorAll(self.config.selectors.target) : 5217 h.children(self.dom.container, self.config.selectors.target, self.dom.document); 5218 5219 self.dom.targets = h.arrayFromList(self.dom.targets); 5220 5221 self.targets = []; 5222 5223 if ((dataset = self.config.load.dataset) && dataset.length !== self.dom.targets.length) { 5224 throw new Error(mixitup.messages.errorDatasetPrerenderedMismatch()); 5225 } 5226 5227 if (self.dom.targets.length) { 5228 for (i = 0; el = self.dom.targets[i]; i++) { 5229 target = new mixitup.Target(); 5230 5231 target.init(el, self, dataset ? dataset[i] : void(0)); 5232 5233 target.isInDom = true; 5234 5235 self.targets.push(target); 5236 } 5237 5238 self.dom.parent = self.dom.targets[0].parentElement === self.dom.container ? 5239 self.dom.container : 5240 self.dom.targets[0].parentElement; 5241 } 5242 5243 self.origOrder = self.targets; 5244 5245 self.callActions('afterIndexTargets', arguments); 5246 }, 5247 5248 initControls: function() { 5249 var self = this, 5250 definition = '', 5251 controlElements = null, 5252 el = null, 5253 parent = null, 5254 delagators = null, 5255 control = null, 5256 i = -1, 5257 j = -1; 5258 5259 self.callActions('beforeInitControls', arguments); 5260 5261 switch (self.config.controls.scope) { 5262 case 'local': 5263 parent = self.dom.container; 5264 5265 break; 5266 case 'global': 5267 parent = self.dom.document; 5268 5269 break; 5270 default: 5271 throw new Error(mixitup.messages.errorConfigInvalidControlsScope()); 5272 } 5273 5274 for (i = 0; definition = mixitup.controlDefinitions[i]; i++) { 5275 if (self.config.controls.live || definition.live) { 5276 if (definition.parent) { 5277 delagators = self.dom[definition.parent]; 5278 5279 if (!delagators || delagators.length < 0) continue; 5280 5281 if (typeof delagators.length !== 'number') { 5282 delagators = [delagators]; 5283 } 5284 } else { 5285 delagators = [parent]; 5286 } 5287 5288 for (j = 0; (el = delagators[j]); j++) { 5289 control = self.getControl(el, definition.type, definition.selector); 5290 5291 self.controls.push(control); 5292 } 5293 } else { 5294 controlElements = parent.querySelectorAll(self.config.selectors.control + definition.selector); 5295 5296 for (j = 0; (el = controlElements[j]); j++) { 5297 control = self.getControl(el, definition.type, ''); 5298 5299 if (!control) continue; 5300 5301 self.controls.push(control); 5302 } 5303 } 5304 } 5305 5306 self.callActions('afterInitControls', arguments); 5307 }, 5308 5309 /** 5310 * @private 5311 * @instance 5312 * @since 3.0.0 5313 * @param {HTMLElement} el 5314 * @param {string} type 5315 * @param {string} selector 5316 * @return {mixitup.Control|null} 5317 */ 5318 5319 getControl: function(el, type, selector) { 5320 var self = this, 5321 control = null, 5322 i = -1; 5323 5324 self.callActions('beforeGetControl', arguments); 5325 5326 if (!selector) { 5327 // Static controls only 5328 5329 for (i = 0; control = mixitup.controls[i]; i++) { 5330 if (control.el === el && control.isBound(self)) { 5331 // Control already bound to this mixer (as another type). 5332 5333 // NB: This prevents duplicate controls from being registered where a selector 5334 // might collide, eg: "[data-filter]" and "[data-filter][data-sort]" 5335 5336 return self.callFilters('controlGetControl', null, arguments); 5337 } else if (control.el === el && control.type === type && control.selector === selector) { 5338 // Another mixer is already using this control, add this mixer as a binding 5339 5340 control.addBinding(self); 5341 5342 return self.callFilters('controlGetControl', control, arguments); 5343 } 5344 } 5345 } 5346 5347 // Create new control 5348 5349 control = new mixitup.Control(); 5350 5351 control.init(el, type, selector); 5352 5353 control.classNames.base = h.getClassname(self.config.classNames, type); 5354 control.classNames.active = h.getClassname(self.config.classNames, type, self.config.classNames.modifierActive); 5355 control.classNames.disabled = h.getClassname(self.config.classNames, type, self.config.classNames.modifierDisabled); 5356 5357 // Add a reference to this mixer as a binding 5358 5359 control.addBinding(self); 5360 5361 return self.callFilters('controlGetControl', control, arguments); 5362 }, 5363 5364 /** 5365 * Creates a compound selector by joining the `toggleArray` value as per the 5366 * defined toggle logic. 5367 * 5368 * @private 5369 * @instance 5370 * @since 3.0.0 5371 * @return {string} 5372 */ 5373 5374 getToggleSelector: function() { 5375 var self = this, 5376 delineator = self.config.controls.toggleLogic === 'or' ? ', ' : '', 5377 toggleSelector = ''; 5378 5379 self.callActions('beforeGetToggleSelector', arguments); 5380 5381 self.toggleArray = h.clean(self.toggleArray); 5382 5383 toggleSelector = self.toggleArray.join(delineator); 5384 5385 if (toggleSelector === '') { 5386 toggleSelector = self.config.controls.toggleDefault; 5387 } 5388 5389 return self.callFilters('selectorGetToggleSelector', toggleSelector, arguments); 5390 }, 5391 5392 /** 5393 * Breaks compound selector strings in an array of discreet selectors, 5394 * as per the active `controls.toggleLogic` configuration option. Accepts 5395 * either a dynamic command object, or a state object. 5396 * 5397 * @private 5398 * @instance 5399 * @since 2.0.0 5400 * @param {object} [command] 5401 * @param {mixitup.State} [state] 5402 * @return {void} 5403 */ 5404 5405 buildToggleArray: function(command, state) { 5406 var self = this, 5407 activeFilterSelector = ''; 5408 5409 self.callActions('beforeBuildToggleArray', arguments); 5410 5411 if (command && command.filter) { 5412 activeFilterSelector = command.filter.selector.replace(/\s/g, ''); 5413 } else if (state) { 5414 activeFilterSelector = state.activeFilter.selector.replace(/\s/g, ''); 5415 } else { 5416 return; 5417 } 5418 5419 if (activeFilterSelector === self.config.selectors.target || activeFilterSelector === 'all') { 5420 activeFilterSelector = ''; 5421 } 5422 5423 if (self.config.controls.toggleLogic === 'or') { 5424 self.toggleArray = activeFilterSelector.split(','); 5425 } else { 5426 self.toggleArray = self.splitCompoundSelector(activeFilterSelector); 5427 } 5428 5429 self.toggleArray = h.clean(self.toggleArray); 5430 5431 self.callActions('afterBuildToggleArray', arguments); 5432 }, 5433 5434 /** 5435 * Takes a compound selector (e.g. `.cat-1.cat-2`, `[data-cat="1"][data-cat="2"]`) 5436 * and breaks into its individual selectors. 5437 * 5438 * @private 5439 * @instance 5440 * @since 3.0.0 5441 * @param {string} compoundSelector 5442 * @return {string[]} 5443 */ 5444 5445 splitCompoundSelector: function(compoundSelector) { 5446 // Break at a `.` or `[`, capturing the delineator 5447 5448 var partials = compoundSelector.split(/([\.\[])/g), 5449 toggleArray = [], 5450 selector = '', 5451 i = -1; 5452 5453 if (partials[0] === '') { 5454 partials.shift(); 5455 } 5456 5457 for (i = 0; i < partials.length; i++) { 5458 if (i % 2 === 0) { 5459 selector = ''; 5460 } 5461 5462 selector += partials[i]; 5463 5464 if (i % 2 !== 0) { 5465 toggleArray.push(selector); 5466 } 5467 } 5468 5469 return toggleArray; 5470 }, 5471 5472 /** 5473 * Updates controls to their active/inactive state based on the command or 5474 * current state of the mixer. 5475 * 5476 * @private 5477 * @instance 5478 * @since 2.0.0 5479 * @param {object} command 5480 * @return {void} 5481 */ 5482 5483 updateControls: function(command) { 5484 var self = this, 5485 control = null, 5486 output = new mixitup.CommandMultimix(), 5487 i = -1; 5488 5489 self.callActions('beforeUpdateControls', arguments); 5490 5491 // Sanitise to defaults 5492 5493 if (command.filter) { 5494 output.filter = command.filter.selector; 5495 } else { 5496 output.filter = self.state.activeFilter.selector; 5497 } 5498 5499 if (command.sort) { 5500 output.sort = self.buildSortString(command.sort); 5501 } else { 5502 output.sort = self.buildSortString(self.state.activeSort); 5503 } 5504 5505 if (output.filter === self.config.selectors.target) { 5506 output.filter = 'all'; 5507 } 5508 5509 if (output.filter === '') { 5510 output.filter = 'none'; 5511 } 5512 5513 h.freeze(output); 5514 5515 for (i = 0; control = self.controls[i]; i++) { 5516 control.update(output, self.toggleArray); 5517 } 5518 5519 self.callActions('afterUpdateControls', arguments); 5520 }, 5521 5522 /** 5523 * @private 5524 * @instance 5525 * @since 3.0.0 5526 * @param {mixitup.CommandSort} command 5527 * @return {string} 5528 */ 5529 5530 buildSortString: function(command) { 5531 var self = this; 5532 var output = ''; 5533 5534 output += command.sortString; 5535 5536 if (command.next) { 5537 output += ' ' + self.buildSortString(command.next); 5538 } 5539 5540 return output; 5541 }, 5542 5543 /** 5544 * @private 5545 * @instance 5546 * @since 3.0.0 5547 * @param {object} command 5548 * @param {Operation} operation 5549 * @return {Promise.<mixitup.State>} 5550 */ 5551 5552 insertTargets: function(command, operation) { 5553 var self = this, 5554 nextSibling = null, 5555 insertionIndex = -1, 5556 frag = null, 5557 target = null, 5558 el = null, 5559 i = -1; 5560 5561 self.callActions('beforeInsertTargets', arguments); 5562 5563 if (typeof command.index === 'undefined') command.index = 0; 5564 5565 nextSibling = self.getNextSibling(command.index, command.sibling, command.position); 5566 frag = self.dom.document.createDocumentFragment(); 5567 5568 if (nextSibling) { 5569 insertionIndex = h.index(nextSibling, self.config.selectors.target); 5570 } else { 5571 insertionIndex = self.targets.length; 5572 } 5573 5574 if (command.collection) { 5575 for (i = 0; el = command.collection[i]; i++) { 5576 if (self.dom.targets.indexOf(el) > -1) { 5577 throw new Error(mixitup.messages.errorInsertPreexistingElement()); 5578 } 5579 5580 // Ensure elements are hidden when they are added to the DOM, so they can 5581 // be animated in gracefully 5582 5583 el.style.display = 'none'; 5584 5585 frag.appendChild(el); 5586 frag.appendChild(self.dom.document.createTextNode(' ')); 5587 5588 if (!h.isElement(el, self.dom.document) || !el.matches(self.config.selectors.target)) continue; 5589 5590 target = new mixitup.Target(); 5591 5592 target.init(el, self); 5593 5594 target.isInDom = true; 5595 5596 self.targets.splice(insertionIndex, 0, target); 5597 5598 insertionIndex++; 5599 } 5600 5601 self.dom.parent.insertBefore(frag, nextSibling); 5602 } 5603 5604 // Since targets have been added, the original order must be updated 5605 5606 operation.startOrder = self.origOrder = self.targets; 5607 5608 self.callActions('afterInsertTargets', arguments); 5609 }, 5610 5611 /** 5612 * @private 5613 * @instance 5614 * @since 3.0.0 5615 * @param {Number} [index] 5616 * @param {Element} [sibling] 5617 * @param {string} [position] 5618 * @return {Element} 5619 */ 5620 5621 getNextSibling: function(index, sibling, position) { 5622 var self = this, 5623 element = null; 5624 5625 index = Math.max(index, 0); 5626 5627 if (sibling && position === 'before') { 5628 // Explicit sibling 5629 5630 element = sibling; 5631 } else if (sibling && position === 'after') { 5632 // Explicit sibling 5633 5634 element = sibling.nextElementSibling || null; 5635 } else if (self.targets.length > 0 && typeof index !== 'undefined') { 5636 // Index and targets exist 5637 5638 element = (index < self.targets.length || !self.targets.length) ? 5639 self.targets[index].dom.el : 5640 self.targets[self.targets.length - 1].dom.el.nextElementSibling; 5641 } else if (self.targets.length === 0 && self.dom.parent.children.length > 0) { 5642 // No targets but other siblings 5643 5644 if (self.config.layout.siblingAfter) { 5645 element = self.config.layout.siblingAfter; 5646 } else if (self.config.layout.siblingBefore) { 5647 element = self.config.layout.siblingBefore.nextElementSibling; 5648 } else { 5649 self.dom.parent.children[0]; 5650 } 5651 } else { 5652 element === null; 5653 } 5654 5655 return self.callFilters('elementGetNextSibling', element, arguments); 5656 }, 5657 5658 /** 5659 * @private 5660 * @instance 5661 * @since 2.0.0 5662 * @param {Operation} operation 5663 * @return {void} 5664 */ 5665 5666 filterOperation: function(operation) { 5667 var self = this, 5668 testResult = false, 5669 index = -1, 5670 action = '', 5671 target = null, 5672 i = -1; 5673 5674 self.callActions('beforeFilterOperation', arguments); 5675 5676 action = operation.newFilter.action; 5677 5678 for (i = 0; target = operation.newOrder[i]; i++) { 5679 if (operation.newFilter.collection) { 5680 // show via collection 5681 5682 testResult = operation.newFilter.collection.indexOf(target.dom.el) > -1; 5683 } else { 5684 // show via selector 5685 5686 if (operation.newFilter.selector === '') { 5687 testResult = false; 5688 } else { 5689 testResult = target.dom.el.matches(operation.newFilter.selector); 5690 } 5691 } 5692 5693 self.evaluateHideShow(testResult, target, action, operation); 5694 } 5695 5696 if (operation.toRemove.length) { 5697 for (i = 0; target = operation.show[i]; i++) { 5698 if (operation.toRemove.indexOf(target) > -1) { 5699 // If any shown targets should be removed, move them into the toHide array 5700 5701 operation.show.splice(i, 1); 5702 5703 if ((index = operation.toShow.indexOf(target)) > -1) { 5704 operation.toShow.splice(index, 1); 5705 } 5706 5707 operation.toHide.push(target); 5708 operation.hide.push(target); 5709 5710 i--; 5711 } 5712 } 5713 } 5714 5715 operation.matching = operation.show.slice(); 5716 5717 if (operation.show.length === 0 && operation.newFilter.selector !== '' && self.targets.length !== 0) { 5718 operation.hasFailed = true; 5719 } 5720 5721 self.callActions('afterFilterOperation', arguments); 5722 }, 5723 5724 /** 5725 * @private 5726 * @instance 5727 * @since 3.0.0 5728 * @param {boolean} testResult 5729 * @param {Element} target 5730 * @param {string} action 5731 * @param {Operation} operation 5732 * @return {void} 5733 */ 5734 5735 evaluateHideShow: function(testResult, target, action, operation) { 5736 var self = this, 5737 filteredTestResult = false, 5738 args = Array.prototype.slice.call(arguments, 1); 5739 5740 filteredTestResult = self.callFilters('testResultEvaluateHideShow', testResult, args); 5741 5742 self.callActions('beforeEvaluateHideShow', arguments); 5743 5744 if ( 5745 filteredTestResult === true && action === 'show' || 5746 filteredTestResult === false && action === 'hide' 5747 ) { 5748 operation.show.push(target); 5749 5750 !target.isShown && operation.toShow.push(target); 5751 } else { 5752 operation.hide.push(target); 5753 5754 target.isShown && operation.toHide.push(target); 5755 } 5756 5757 self.callActions('afterEvaluateHideShow', arguments); 5758 }, 5759 5760 /** 5761 * @private 5762 * @instance 5763 * @since 2.0.0 5764 * @param {Operation} operation 5765 * @return {void} 5766 */ 5767 5768 sortOperation: function(operation) { 5769 var self = this, 5770 newOrder = [], 5771 target = null, 5772 el = null, 5773 i = -1; 5774 5775 self.callActions('beforeSortOperation', arguments); 5776 5777 operation.startOrder = self.targets; 5778 5779 if (operation.newSort.collection) { 5780 // Sort by collection 5781 5782 newOrder = []; 5783 5784 for (i = 0; (el = operation.newSort.collection[i]); i++) { 5785 if (self.dom.targets.indexOf(el) < 0) { 5786 throw new Error(mixitup.messages.errorSortNonExistentElement()); 5787 } 5788 5789 target = new mixitup.Target(); 5790 5791 target.init(el, self); 5792 5793 target.isInDom = true; 5794 5795 newOrder.push(target); 5796 } 5797 5798 operation.newOrder = newOrder; 5799 } else if (operation.newSort.order === 'random') { 5800 // Sort random 5801 5802 operation.newOrder = h.arrayShuffle(operation.startOrder); 5803 } else if (operation.newSort.attribute === '') { 5804 // Sort by default 5805 5806 operation.newOrder = self.origOrder.slice(); 5807 5808 if (operation.newSort.order === 'desc') { 5809 operation.newOrder.reverse(); 5810 } 5811 } else { 5812 // Sort by attribute 5813 5814 operation.newOrder = operation.startOrder.slice(); 5815 5816 operation.newOrder.sort(function(a, b) { 5817 return self.compare(a, b, operation.newSort); 5818 }); 5819 } 5820 5821 if (h.isEqualArray(operation.newOrder, operation.startOrder)) { 5822 operation.willSort = false; 5823 } 5824 5825 self.callActions('afterSortOperation', arguments); 5826 }, 5827 5828 /** 5829 * @private 5830 * @instance 5831 * @since 2.0.0 5832 * @param {mixitup.Target} a 5833 * @param {mixitup.Target} b 5834 * @param {mixitup.CommandSort} command 5835 * @return {Number} 5836 */ 5837 5838 compare: function(a, b, command) { 5839 var self = this, 5840 order = command.order, 5841 attrA = self.getAttributeValue(a, command.attribute), 5842 attrB = self.getAttributeValue(b, command.attribute); 5843 5844 if (isNaN(attrA * 1) || isNaN(attrB * 1)) { 5845 attrA = attrA.toLowerCase(); 5846 attrB = attrB.toLowerCase(); 5847 } else { 5848 attrA = attrA * 1; 5849 attrB = attrB * 1; 5850 } 5851 5852 if (attrA < attrB) { 5853 return order === 'asc' ? -1 : 1; 5854 } 5855 5856 if (attrA > attrB) { 5857 return order === 'asc' ? 1 : -1; 5858 } 5859 5860 if (attrA === attrB && command.next) { 5861 return self.compare(a, b, command.next); 5862 } 5863 5864 return 0; 5865 }, 5866 5867 /** 5868 * Reads the values of any data attributes present the provided target element 5869 * which match the current sort command. 5870 * 5871 * @private 5872 * @instance 5873 * @since 3.0.0 5874 * @param {mixitup.Target} target 5875 * @param {string} [attribute] 5876 * @return {(String|Number)} 5877 */ 5878 5879 getAttributeValue: function(target, attribute) { 5880 var self = this, 5881 value = ''; 5882 5883 value = target.dom.el.getAttribute('data-' + attribute); 5884 5885 if (value === null) { 5886 if (self.config.debug.showWarnings) { 5887 // Encourage users to assign values to all targets to avoid erroneous sorting 5888 // when types are mixed 5889 5890 console.warn(mixitup.messages.warningInconsistentSortingAttributes({ 5891 attribute: 'data-' + attribute 5892 })); 5893 } 5894 } 5895 5896 // If an attribute is not present, return 0 as a safety value 5897 5898 return self.callFilters('valueGetAttributeValue', value || 0, arguments); 5899 }, 5900 5901 /** 5902 * Inserts elements into the DOM in the appropriate 5903 * order using a document fragment for minimal 5904 * DOM thrashing 5905 * 5906 * @private 5907 * @instance 5908 * @since 2.0.0 5909 * @param {boolean} isResetting 5910 * @param {Operation} operation 5911 * @return {void} 5912 */ 5913 5914 printSort: function(isResetting, operation) { 5915 var self = this, 5916 startOrder = isResetting ? operation.newOrder : operation.startOrder, 5917 newOrder = isResetting ? operation.startOrder : operation.newOrder, 5918 nextSibling = startOrder.length ? startOrder[startOrder.length - 1].dom.el.nextElementSibling : null, 5919 frag = window.document.createDocumentFragment(), 5920 whitespace = null, 5921 target = null, 5922 el = null, 5923 i = -1; 5924 5925 self.callActions('beforePrintSort', arguments); 5926 5927 // Empty the container 5928 5929 for (i = 0; target = startOrder[i]; i++) { 5930 el = target.dom.el; 5931 5932 if (el.style.position === 'absolute') continue; 5933 5934 h.removeWhitespace(el.previousSibling); 5935 5936 el.parentElement.removeChild(el); 5937 } 5938 5939 whitespace = nextSibling ? nextSibling.previousSibling : self.dom.parent.lastChild; 5940 5941 if (whitespace && whitespace.nodeName === '#text') { 5942 h.removeWhitespace(whitespace); 5943 } 5944 5945 for (i = 0; target = newOrder[i]; i++) { 5946 // Add targets into a document fragment 5947 5948 el = target.dom.el; 5949 5950 if (h.isElement(frag.lastChild)) { 5951 frag.appendChild(window.document.createTextNode(' ')); 5952 } 5953 5954 frag.appendChild(el); 5955 } 5956 5957 // Insert the document fragment into the container 5958 // before any other non-target elements 5959 5960 if (self.dom.parent.firstChild && self.dom.parent.firstChild !== nextSibling) { 5961 frag.insertBefore(window.document.createTextNode(' '), frag.childNodes[0]); 5962 } 5963 5964 if (nextSibling) { 5965 frag.appendChild(window.document.createTextNode(' ')); 5966 5967 self.dom.parent.insertBefore(frag, nextSibling); 5968 } else { 5969 self.dom.parent.appendChild(frag); 5970 } 5971 5972 self.callActions('afterPrintSort', arguments); 5973 }, 5974 5975 /** 5976 * Parses user-defined sort strings (i.e. `default:asc`) into sort commands objects. 5977 * 5978 * @private 5979 * @instance 5980 * @since 3.0.0 5981 * @param {string} sortString 5982 * @param {mixitup.CommandSort} command 5983 * @return {mixitup.CommandSort} 5984 */ 5985 5986 parseSortString: function(sortString, command) { 5987 var self = this, 5988 rules = sortString.split(' '), 5989 current = command, 5990 rule = [], 5991 i = -1; 5992 5993 // command.sortString = sortString; 5994 5995 for (i = 0; i < rules.length; i++) { 5996 rule = rules[i].split(':'); 5997 5998 current.sortString = rules[i]; 5999 current.attribute = h.dashCase(rule[0]); 6000 current.order = rule[1] || 'asc'; 6001 6002 switch (current.attribute) { 6003 case 'default': 6004 // treat "default" as sorting by no attribute 6005 6006 current.attribute = ''; 6007 6008 break; 6009 case 'random': 6010 // treat "random" as an order not an attribute 6011 6012 current.attribute = ''; 6013 current.order = 'random'; 6014 6015 break; 6016 } 6017 6018 if (!current.attribute || current.order === 'random') break; 6019 6020 if (i < rules.length - 1) { 6021 // Embed reference to the next command 6022 6023 current.next = new mixitup.CommandSort(); 6024 6025 h.freeze(current); 6026 6027 current = current.next; 6028 } 6029 } 6030 6031 return self.callFilters('commandsParseSort', command, arguments); 6032 }, 6033 6034 /** 6035 * Parses all effects out of the user-defined `animation.effects` string into 6036 * their respective properties and units. 6037 * 6038 * @private 6039 * @instance 6040 * @since 2.0.0 6041 * @return {void} 6042 */ 6043 6044 parseEffects: function() { 6045 var self = this, 6046 transformName = '', 6047 effectsIn = self.config.animation.effectsIn || self.config.animation.effects, 6048 effectsOut = self.config.animation.effectsOut || self.config.animation.effects; 6049 6050 self.callActions('beforeParseEffects', arguments); 6051 6052 self.effectsIn = new mixitup.StyleData(); 6053 self.effectsOut = new mixitup.StyleData(); 6054 self.transformIn = []; 6055 self.transformOut = []; 6056 6057 self.effectsIn.opacity = self.effectsOut.opacity = 1; 6058 6059 self.parseEffect('fade', effectsIn, self.effectsIn, self.transformIn); 6060 self.parseEffect('fade', effectsOut, self.effectsOut, self.transformOut, true); 6061 6062 for (transformName in mixitup.transformDefaults) { 6063 if (!(mixitup.transformDefaults[transformName] instanceof mixitup.TransformData)) { 6064 continue; 6065 } 6066 6067 self.parseEffect(transformName, effectsIn, self.effectsIn, self.transformIn); 6068 self.parseEffect(transformName, effectsOut, self.effectsOut, self.transformOut, true); 6069 } 6070 6071 self.parseEffect('stagger', effectsIn, self.effectsIn, self.transformIn); 6072 self.parseEffect('stagger', effectsOut, self.effectsOut, self.transformOut, true); 6073 6074 self.callActions('afterParseEffects', arguments); 6075 }, 6076 6077 /** 6078 * @private 6079 * @instance 6080 * @since 2.0.0 6081 * @param {string} effectName 6082 * @param {string} effectString 6083 * @param {StyleData} effects 6084 * @param {String[]} transform 6085 * @param {boolean} [isOut] 6086 */ 6087 6088 parseEffect: function(effectName, effectString, effects, transform, isOut) { 6089 var self = this, 6090 re = /\(([^)]+)\)/, 6091 propIndex = -1, 6092 str = '', 6093 match = [], 6094 val = '', 6095 units = ['%', 'px', 'em', 'rem', 'vh', 'vw', 'deg'], 6096 unit = '', 6097 i = -1; 6098 6099 self.callActions('beforeParseEffect', arguments); 6100 6101 if (typeof effectString !== 'string') { 6102 throw new TypeError(mixitup.messages.errorConfigInvalidAnimationEffects()); 6103 } 6104 6105 if (effectString.indexOf(effectName) < 0) { 6106 // The effect is not present in the effects string 6107 6108 if (effectName === 'stagger') { 6109 // Reset stagger to 0 6110 6111 self.staggerDuration = 0; 6112 } 6113 6114 return; 6115 } 6116 6117 // The effect is present 6118 6119 propIndex = effectString.indexOf(effectName + '('); 6120 6121 if (propIndex > -1) { 6122 // The effect has a user defined value in parentheses 6123 6124 // Extract from the first parenthesis to the end of string 6125 6126 str = effectString.substring(propIndex); 6127 6128 // Match any number of characters between "(" and ")" 6129 6130 match = re.exec(str); 6131 6132 val = match[1]; 6133 } 6134 6135 switch (effectName) { 6136 case 'fade': 6137 effects.opacity = val ? parseFloat(val) : 0; 6138 6139 break; 6140 case 'stagger': 6141 self.staggerDuration = val ? parseFloat(val) : 100; 6142 6143 // TODO: Currently stagger must be applied globally, but 6144 // if seperate values are specified for in/out, this should 6145 // be respected 6146 6147 break; 6148 default: 6149 // All other effects are transforms following the same structure 6150 6151 if (isOut && self.config.animation.reverseOut && effectName !== 'scale') { 6152 effects[effectName].value = 6153 (val ? parseFloat(val) : mixitup.transformDefaults[effectName].value) * -1; 6154 } else { 6155 effects[effectName].value = 6156 (val ? parseFloat(val) : mixitup.transformDefaults[effectName].value); 6157 } 6158 6159 if (val) { 6160 for (i = 0; unit = units[i]; i++) { 6161 if (val.indexOf(unit) > -1) { 6162 effects[effectName].unit = unit; 6163 6164 break; 6165 } 6166 } 6167 } else { 6168 effects[effectName].unit = mixitup.transformDefaults[effectName].unit; 6169 } 6170 6171 transform.push( 6172 effectName + 6173 '(' + 6174 effects[effectName].value + 6175 effects[effectName].unit + 6176 ')' 6177 ); 6178 } 6179 6180 self.callActions('afterParseEffect', arguments); 6181 }, 6182 6183 /** 6184 * @private 6185 * @instance 6186 * @since 2.0.0 6187 * @param {Operation} operation 6188 * @return {State} 6189 */ 6190 6191 buildState: function(operation) { 6192 var self = this, 6193 state = new mixitup.State(), 6194 target = null, 6195 i = -1; 6196 6197 self.callActions('beforeBuildState', arguments); 6198 6199 // Map target elements into state arrays. 6200 // the real target objects should never be exposed 6201 6202 for (i = 0; target = self.targets[i]; i++) { 6203 if (!operation.toRemove.length || operation.toRemove.indexOf(target) < 0) { 6204 state.targets.push(target.dom.el); 6205 } 6206 } 6207 6208 for (i = 0; target = operation.matching[i]; i++) { 6209 state.matching.push(target.dom.el); 6210 } 6211 6212 for (i = 0; target = operation.show[i]; i++) { 6213 state.show.push(target.dom.el); 6214 } 6215 6216 for (i = 0; target = operation.hide[i]; i++) { 6217 if (!operation.toRemove.length || operation.toRemove.indexOf(target) < 0) { 6218 state.hide.push(target.dom.el); 6219 } 6220 } 6221 6222 state.id = self.id; 6223 state.container = self.dom.container; 6224 state.activeFilter = operation.newFilter; 6225 state.activeSort = operation.newSort; 6226 state.activeDataset = operation.newDataset; 6227 state.activeContainerClassName = operation.newContainerClassName; 6228 state.hasFailed = operation.hasFailed; 6229 state.totalTargets = self.targets.length; 6230 state.totalShow = operation.show.length; 6231 state.totalHide = operation.hide.length; 6232 state.totalMatching = operation.matching.length; 6233 state.triggerElement = operation.triggerElement; 6234 6235 return self.callFilters('stateBuildState', state, arguments); 6236 }, 6237 6238 /** 6239 * @private 6240 * @instance 6241 * @since 2.0.0 6242 * @param {boolean} shouldAnimate 6243 * @param {Operation} operation 6244 * @return {void} 6245 */ 6246 6247 goMix: function(shouldAnimate, operation) { 6248 var self = this, 6249 deferred = null; 6250 6251 self.callActions('beforeGoMix', arguments); 6252 6253 // If the animation duration is set to 0ms, 6254 // or no effects specified, 6255 // or the container is hidden 6256 // then abort animation 6257 6258 if ( 6259 !self.config.animation.duration || !self.config.animation.effects || !h.isVisible(self.dom.container) 6260 ) { 6261 shouldAnimate = false; 6262 } 6263 6264 if ( 6265 !operation.toShow.length && 6266 !operation.toHide.length && 6267 !operation.willSort && 6268 !operation.willChangeLayout 6269 ) { 6270 // If nothing to show or hide, and not sorting or 6271 // changing layout 6272 6273 shouldAnimate = false; 6274 } 6275 6276 if ( 6277 !operation.startState.show.length && 6278 !operation.show.length 6279 ) { 6280 // If nothing currently shown, nothing to show 6281 6282 shouldAnimate = false; 6283 } 6284 6285 mixitup.events.fire('mixStart', self.dom.container, { 6286 state: operation.startState, 6287 futureState: operation.newState, 6288 instance: self 6289 }, self.dom.document); 6290 6291 if (typeof self.config.callbacks.onMixStart === 'function') { 6292 self.config.callbacks.onMixStart.call( 6293 self.dom.container, 6294 operation.startState, 6295 operation.newState, 6296 self 6297 ); 6298 } 6299 6300 h.removeClass(self.dom.container, h.getClassname(self.config.classNames, 'container', self.config.classNames.modifierFailed)); 6301 6302 if (!self.userDeferred) { 6303 // Queue empty, no pending operations 6304 6305 deferred = self.userDeferred = h.defer(mixitup.libraries); 6306 } else { 6307 // Use existing deferred 6308 6309 deferred = self.userDeferred; 6310 } 6311 6312 self.isBusy = true; 6313 6314 if (!shouldAnimate || !mixitup.features.has.transitions) { 6315 // Abort 6316 6317 if (self.config.debug.fauxAsync) { 6318 setTimeout(function() { 6319 self.cleanUp(operation); 6320 }, self.config.animation.duration); 6321 } else { 6322 self.cleanUp(operation); 6323 } 6324 6325 return self.callFilters('promiseGoMix', deferred.promise, arguments); 6326 } 6327 6328 // If we should animate and the platform supports transitions, go for it 6329 6330 if (window.pageYOffset !== operation.docState.scrollTop) { 6331 window.scrollTo(operation.docState.scrollLeft, operation.docState.scrollTop); 6332 } 6333 6334 if (self.config.animation.applyPerspective) { 6335 self.dom.parent.style[mixitup.features.perspectiveProp] = 6336 self.config.animation.perspectiveDistance; 6337 6338 self.dom.parent.style[mixitup.features.perspectiveOriginProp] = 6339 self.config.animation.perspectiveOrigin; 6340 } 6341 6342 if ( 6343 self.config.animation.animateResizeContainer && 6344 operation.startHeight !== operation.newHeight && 6345 operation.viewportDeltaY !== operation.startHeight - operation.newHeight 6346 ) { 6347 self.dom.parent.style.height = operation.startHeight + 'px'; 6348 } 6349 6350 if ( 6351 self.config.animation.animateResizeContainer && 6352 operation.startWidth !== operation.newWidth && 6353 operation.viewportDeltaX !== operation.startWidth - operation.newWidth 6354 ) { 6355 self.dom.parent.style.width = operation.startWidth + 'px'; 6356 } 6357 6358 if (operation.startHeight === operation.newHeight) { 6359 self.dom.parent.style.height = operation.startHeight + 'px'; 6360 } 6361 6362 if (operation.startWidth === operation.newWidth) { 6363 self.dom.parent.style.width = operation.startWidth + 'px'; 6364 } 6365 6366 if (operation.startHeight === operation.newHeight && operation.startWidth === operation.newWidth) { 6367 self.dom.parent.style.overflow = 'hidden'; 6368 } 6369 6370 requestAnimationFrame(function() { 6371 self.moveTargets(operation); 6372 }); 6373 6374 return self.callFilters('promiseGoMix', deferred.promise, arguments); 6375 }, 6376 6377 /** 6378 * @private 6379 * @instance 6380 * @since 2.0.0 6381 * @param {Operation} operation 6382 * @return {void} 6383 */ 6384 6385 getStartMixData: function(operation) { 6386 var self = this, 6387 parentStyle = window.getComputedStyle(self.dom.parent), 6388 parentRect = self.dom.parent.getBoundingClientRect(), 6389 target = null, 6390 data = {}, 6391 i = -1, 6392 boxSizing = parentStyle[mixitup.features.boxSizingProp]; 6393 6394 self.incPadding = (boxSizing === 'border-box'); 6395 6396 self.callActions('beforeGetStartMixData', arguments); 6397 6398 for (i = 0; target = operation.show[i]; i++) { 6399 data = target.getPosData(); 6400 6401 operation.showPosData[i] = { 6402 startPosData: data 6403 }; 6404 } 6405 6406 for (i = 0; target = operation.toHide[i]; i++) { 6407 data = target.getPosData(); 6408 6409 operation.toHidePosData[i] = { 6410 startPosData: data 6411 }; 6412 } 6413 6414 operation.startX = parentRect.left; 6415 operation.startY = parentRect.top; 6416 6417 operation.startHeight = self.incPadding ? 6418 parentRect.height : 6419 parentRect.height - 6420 parseFloat(parentStyle.paddingTop) - 6421 parseFloat(parentStyle.paddingBottom) - 6422 parseFloat(parentStyle.borderTop) - 6423 parseFloat(parentStyle.borderBottom); 6424 6425 operation.startWidth = self.incPadding ? 6426 parentRect.width : 6427 parentRect.width - 6428 parseFloat(parentStyle.paddingLeft) - 6429 parseFloat(parentStyle.paddingRight) - 6430 parseFloat(parentStyle.borderLeft) - 6431 parseFloat(parentStyle.borderRight); 6432 6433 self.callActions('afterGetStartMixData', arguments); 6434 }, 6435 6436 /** 6437 * @private 6438 * @instance 6439 * @since 2.0.0 6440 * @param {Operation} operation 6441 * @return {void} 6442 */ 6443 6444 setInter: function(operation) { 6445 var self = this, 6446 target = null, 6447 i = -1; 6448 6449 self.callActions('beforeSetInter', arguments); 6450 6451 // Prevent scrollbar flicker on non-inertial scroll platforms by clamping height/width 6452 6453 if (self.config.animation.clampHeight) { 6454 self.dom.parent.style.height = operation.startHeight + 'px'; 6455 self.dom.parent.style.overflow = 'hidden'; 6456 } 6457 6458 if (self.config.animation.clampWidth) { 6459 self.dom.parent.style.width = operation.startWidth + 'px'; 6460 self.dom.parent.style.overflow = 'hidden'; 6461 } 6462 6463 for (i = 0; target = operation.toShow[i]; i++) { 6464 target.show(); 6465 } 6466 6467 if (operation.willChangeLayout) { 6468 h.removeClass(self.dom.container, operation.startContainerClassName); 6469 h.addClass(self.dom.container, operation.newContainerClassName); 6470 } 6471 6472 self.callActions('afterSetInter', arguments); 6473 }, 6474 6475 /** 6476 * @private 6477 * @instance 6478 * @since 2.0.0 6479 * @param {Operation} operation 6480 * @return {void} 6481 */ 6482 6483 getInterMixData: function(operation) { 6484 var self = this, 6485 target = null, 6486 i = -1; 6487 6488 self.callActions('beforeGetInterMixData', arguments); 6489 6490 for (i = 0; target = operation.show[i]; i++) { 6491 operation.showPosData[i].interPosData = target.getPosData(); 6492 } 6493 6494 for (i = 0; target = operation.toHide[i]; i++) { 6495 operation.toHidePosData[i].interPosData = target.getPosData(); 6496 } 6497 6498 self.callActions('afterGetInterMixData', arguments); 6499 }, 6500 6501 /** 6502 * @private 6503 * @instance 6504 * @since 2.0.0 6505 * @param {Operation} operation 6506 * @return {void} 6507 */ 6508 6509 setFinal: function(operation) { 6510 var self = this, 6511 target = null, 6512 i = -1; 6513 6514 self.callActions('beforeSetFinal', arguments); 6515 6516 operation.willSort && self.printSort(false, operation); 6517 6518 for (i = 0; target = operation.toHide[i]; i++) { 6519 target.hide(); 6520 } 6521 6522 self.callActions('afterSetFinal', arguments); 6523 }, 6524 6525 /** 6526 * @private 6527 * @instance 6528 * @since 2.0.0 6529 * @param {Operation} operation 6530 * @return {void} 6531 */ 6532 6533 getFinalMixData: function(operation) { 6534 var self = this, 6535 parentStyle = null, 6536 parentRect = null, 6537 target = null, 6538 i = -1; 6539 6540 self.callActions('beforeGetFinalMixData', arguments); 6541 6542 for (i = 0; target = operation.show[i]; i++) { 6543 operation.showPosData[i].finalPosData = target.getPosData(); 6544 } 6545 6546 for (i = 0; target = operation.toHide[i]; i++) { 6547 operation.toHidePosData[i].finalPosData = target.getPosData(); 6548 } 6549 6550 // Remove clamping 6551 6552 if (self.config.animation.clampHeight || self.config.animation.clampWidth) { 6553 self.dom.parent.style.height = 6554 self.dom.parent.style.width = 6555 self.dom.parent.style.overflow = ''; 6556 } 6557 6558 if (!self.incPadding) { 6559 parentStyle = window.getComputedStyle(self.dom.parent); 6560 } 6561 6562 parentRect = self.dom.parent.getBoundingClientRect(); 6563 6564 operation.newX = parentRect.left; 6565 operation.newY = parentRect.top; 6566 6567 operation.newHeight = self.incPadding ? 6568 parentRect.height : 6569 parentRect.height - 6570 parseFloat(parentStyle.paddingTop) - 6571 parseFloat(parentStyle.paddingBottom) - 6572 parseFloat(parentStyle.borderTop) - 6573 parseFloat(parentStyle.borderBottom); 6574 6575 operation.newWidth = self.incPadding ? 6576 parentRect.width : 6577 parentRect.width - 6578 parseFloat(parentStyle.paddingLeft) - 6579 parseFloat(parentStyle.paddingRight) - 6580 parseFloat(parentStyle.borderLeft) - 6581 parseFloat(parentStyle.borderRight); 6582 6583 operation.viewportDeltaX = operation.docState.viewportWidth - this.dom.document.documentElement.clientWidth; 6584 operation.viewportDeltaY = operation.docState.viewportHeight - this.dom.document.documentElement.clientHeight; 6585 6586 if (operation.willSort) { 6587 self.printSort(true, operation); 6588 } 6589 6590 for (i = 0; target = operation.toShow[i]; i++) { 6591 target.hide(); 6592 } 6593 6594 for (i = 0; target = operation.toHide[i]; i++) { 6595 target.show(); 6596 } 6597 6598 if (operation.willChangeLayout) { 6599 h.removeClass(self.dom.container, operation.newContainerClassName); 6600 h.addClass(self.dom.container, self.config.layout.containerClassName); 6601 } 6602 6603 self.callActions('afterGetFinalMixData', arguments); 6604 }, 6605 6606 /** 6607 * @private 6608 * @instance 6609 * @since 3.0.0 6610 * @param {Operation} operation 6611 */ 6612 6613 getTweenData: function(operation) { 6614 var self = this, 6615 target = null, 6616 posData = null, 6617 effectNames = Object.getOwnPropertyNames(self.effectsIn), 6618 effectName = '', 6619 effect = null, 6620 widthChange = -1, 6621 heightChange = -1, 6622 i = -1, 6623 j = -1; 6624 6625 self.callActions('beforeGetTweenData', arguments); 6626 6627 for (i = 0; target = operation.show[i]; i++) { 6628 posData = operation.showPosData[i]; 6629 posData.posIn = new mixitup.StyleData(); 6630 posData.posOut = new mixitup.StyleData(); 6631 posData.tweenData = new mixitup.StyleData(); 6632 6633 // Process x and y 6634 6635 if (target.isShown) { 6636 posData.posIn.x = posData.startPosData.x - posData.interPosData.x; 6637 posData.posIn.y = posData.startPosData.y - posData.interPosData.y; 6638 } else { 6639 posData.posIn.x = posData.posIn.y = 0; 6640 } 6641 6642 posData.posOut.x = posData.finalPosData.x - posData.interPosData.x; 6643 posData.posOut.y = posData.finalPosData.y - posData.interPosData.y; 6644 6645 // Process opacity 6646 6647 posData.posIn.opacity = target.isShown ? 1 : self.effectsIn.opacity; 6648 posData.posOut.opacity = 1; 6649 posData.tweenData.opacity = posData.posOut.opacity - posData.posIn.opacity; 6650 6651 // Adjust x and y if not nudging 6652 6653 if (!target.isShown && !self.config.animation.nudge) { 6654 posData.posIn.x = posData.posOut.x; 6655 posData.posIn.y = posData.posOut.y; 6656 } 6657 6658 posData.tweenData.x = posData.posOut.x - posData.posIn.x; 6659 posData.tweenData.y = posData.posOut.y - posData.posIn.y; 6660 6661 // Process width, height, and margins 6662 6663 if (self.config.animation.animateResizeTargets) { 6664 posData.posIn.width = posData.startPosData.width; 6665 posData.posIn.height = posData.startPosData.height; 6666 6667 // "||" Prevents width/height change from including 0 width/height if hiding or showing 6668 6669 widthChange = (posData.startPosData.width || posData.finalPosData.width) - posData.interPosData.width; 6670 6671 posData.posIn.marginRight = posData.startPosData.marginRight - widthChange; 6672 6673 heightChange = (posData.startPosData.height || posData.finalPosData.height) - posData.interPosData.height; 6674 6675 posData.posIn.marginBottom = posData.startPosData.marginBottom - heightChange; 6676 6677 posData.posOut.width = posData.finalPosData.width; 6678 posData.posOut.height = posData.finalPosData.height; 6679 6680 widthChange = (posData.finalPosData.width || posData.startPosData.width) - posData.interPosData.width; 6681 6682 posData.posOut.marginRight = posData.finalPosData.marginRight - widthChange; 6683 6684 heightChange = (posData.finalPosData.height || posData.startPosData.height) - posData.interPosData.height; 6685 6686 posData.posOut.marginBottom = posData.finalPosData.marginBottom - heightChange; 6687 6688 posData.tweenData.width = posData.posOut.width - posData.posIn.width; 6689 posData.tweenData.height = posData.posOut.height - posData.posIn.height; 6690 posData.tweenData.marginRight = posData.posOut.marginRight - posData.posIn.marginRight; 6691 posData.tweenData.marginBottom = posData.posOut.marginBottom - posData.posIn.marginBottom; 6692 } 6693 6694 // Process transforms 6695 6696 for (j = 0; effectName = effectNames[j]; j++) { 6697 effect = self.effectsIn[effectName]; 6698 6699 if (!(effect instanceof mixitup.TransformData) || !effect.value) continue; 6700 6701 posData.posIn[effectName].value = effect.value; 6702 posData.posOut[effectName].value = 0; 6703 6704 posData.tweenData[effectName].value = 6705 posData.posOut[effectName].value - posData.posIn[effectName].value; 6706 6707 posData.posIn[effectName].unit = 6708 posData.posOut[effectName].unit = 6709 posData.tweenData[effectName].unit = 6710 effect.unit; 6711 } 6712 } 6713 6714 for (i = 0; target = operation.toHide[i]; i++) { 6715 posData = operation.toHidePosData[i]; 6716 posData.posIn = new mixitup.StyleData(); 6717 posData.posOut = new mixitup.StyleData(); 6718 posData.tweenData = new mixitup.StyleData(); 6719 6720 // Process x and y 6721 6722 posData.posIn.x = target.isShown ? posData.startPosData.x - posData.interPosData.x : 0; 6723 posData.posIn.y = target.isShown ? posData.startPosData.y - posData.interPosData.y : 0; 6724 posData.posOut.x = self.config.animation.nudge ? 0 : posData.posIn.x; 6725 posData.posOut.y = self.config.animation.nudge ? 0 : posData.posIn.y; 6726 posData.tweenData.x = posData.posOut.x - posData.posIn.x; 6727 posData.tweenData.y = posData.posOut.y - posData.posIn.y; 6728 6729 // Process width, height, and margins 6730 6731 if (self.config.animation.animateResizeTargets) { 6732 posData.posIn.width = posData.startPosData.width; 6733 posData.posIn.height = posData.startPosData.height; 6734 6735 widthChange = posData.startPosData.width - posData.interPosData.width; 6736 6737 posData.posIn.marginRight = posData.startPosData.marginRight - widthChange; 6738 6739 heightChange = posData.startPosData.height - posData.interPosData.height; 6740 6741 posData.posIn.marginBottom = posData.startPosData.marginBottom - heightChange; 6742 } 6743 6744 // Process opacity 6745 6746 posData.posIn.opacity = 1; 6747 posData.posOut.opacity = self.effectsOut.opacity; 6748 posData.tweenData.opacity = posData.posOut.opacity - posData.posIn.opacity; 6749 6750 // Process transforms 6751 6752 for (j = 0; effectName = effectNames[j]; j++) { 6753 effect = self.effectsOut[effectName]; 6754 6755 if (!(effect instanceof mixitup.TransformData) || !effect.value) continue; 6756 6757 posData.posIn[effectName].value = 0; 6758 posData.posOut[effectName].value = effect.value; 6759 6760 posData.tweenData[effectName].value = 6761 posData.posOut[effectName].value - posData.posIn[effectName].value; 6762 6763 posData.posIn[effectName].unit = 6764 posData.posOut[effectName].unit = 6765 posData.tweenData[effectName].unit = 6766 effect.unit; 6767 } 6768 } 6769 6770 self.callActions('afterGetTweenData', arguments); 6771 }, 6772 6773 /** 6774 * @private 6775 * @instance 6776 * @since 3.0.0 6777 * @param {Operation} operation 6778 * @return {void} 6779 */ 6780 6781 moveTargets: function(operation) { 6782 var self = this, 6783 target = null, 6784 moveData = null, 6785 posData = null, 6786 statusChange = '', 6787 willTransition = false, 6788 staggerIndex = -1, 6789 i = -1, 6790 checkProgress = self.checkProgress.bind(self); 6791 6792 self.callActions('beforeMoveTargets', arguments); 6793 6794 // TODO: this is an extra loop in addition to the calcs 6795 // done in getOperation, could some of this be done there? 6796 6797 for (i = 0; target = operation.show[i]; i++) { 6798 moveData = new mixitup.IMoveData(); 6799 posData = operation.showPosData[i]; 6800 6801 statusChange = target.isShown ? 'none' : 'show'; 6802 6803 willTransition = self.willTransition( 6804 statusChange, 6805 operation.hasEffect, 6806 posData.posIn, 6807 posData.posOut 6808 ); 6809 6810 if (willTransition) { 6811 // Prevent non-transitioning targets from incrementing the staggerIndex 6812 6813 staggerIndex++; 6814 } 6815 6816 target.show(); 6817 6818 moveData.posIn = posData.posIn; 6819 moveData.posOut = posData.posOut; 6820 moveData.statusChange = statusChange; 6821 moveData.staggerIndex = staggerIndex; 6822 moveData.operation = operation; 6823 moveData.callback = willTransition ? checkProgress : null; 6824 6825 target.move(moveData); 6826 } 6827 6828 for (i = 0; target = operation.toHide[i]; i++) { 6829 posData = operation.toHidePosData[i]; 6830 moveData = new mixitup.IMoveData(); 6831 6832 statusChange = 'hide'; 6833 6834 willTransition = self.willTransition(statusChange, posData.posIn, posData.posOut); 6835 6836 moveData.posIn = posData.posIn; 6837 moveData.posOut = posData.posOut; 6838 moveData.statusChange = statusChange; 6839 moveData.staggerIndex = i; 6840 moveData.operation = operation; 6841 moveData.callback = willTransition ? checkProgress : null; 6842 6843 target.move(moveData); 6844 } 6845 6846 if (self.config.animation.animateResizeContainer) { 6847 self.dom.parent.style[mixitup.features.transitionProp] = 6848 'height ' + self.config.animation.duration + 'ms ease, ' + 6849 'width ' + self.config.animation.duration + 'ms ease '; 6850 6851 requestAnimationFrame(function() { 6852 if ( 6853 operation.startHeight !== operation.newHeight && 6854 operation.viewportDeltaY !== operation.startHeight - operation.newHeight 6855 ) { 6856 self.dom.parent.style.height = operation.newHeight + 'px'; 6857 } 6858 6859 if ( 6860 operation.startWidth !== operation.newWidth && 6861 operation.viewportDeltaX !== operation.startWidth - operation.newWidth 6862 ) { 6863 self.dom.parent.style.width = operation.newWidth + 'px'; 6864 } 6865 }); 6866 } 6867 6868 if (operation.willChangeLayout) { 6869 h.removeClass(self.dom.container, self.config.layout.ContainerClassName); 6870 h.addClass(self.dom.container, operation.newContainerClassName); 6871 } 6872 6873 self.callActions('afterMoveTargets', arguments); 6874 }, 6875 6876 /** 6877 * @private 6878 * @instance 6879 * @return {boolean} 6880 */ 6881 6882 hasEffect: function() { 6883 var self = this, 6884 EFFECTABLES = [ 6885 'scale', 6886 'translateX', 'translateY', 'translateZ', 6887 'rotateX', 'rotateY', 'rotateZ' 6888 ], 6889 effectName = '', 6890 effect = null, 6891 result = false, 6892 value = -1, 6893 i = -1; 6894 6895 if (self.effectsIn.opacity !== 1) { 6896 return self.callFilters('resultHasEffect', true, arguments); 6897 } 6898 6899 for (i = 0; effectName = EFFECTABLES[i]; i++) { 6900 effect = self.effectsIn[effectName]; 6901 value = (typeof effect && effect.value !== 'undefined') ? 6902 effect.value : effect; 6903 6904 if (value !== 0) { 6905 result = true; 6906 6907 break; 6908 } 6909 } 6910 6911 return self.callFilters('resultHasEffect', result, arguments); 6912 }, 6913 6914 /** 6915 * Determines if a target element will transition in 6916 * some fasion and therefore requires binding of 6917 * transitionEnd 6918 * 6919 * @private 6920 * @instance 6921 * @since 3.0.0 6922 * @param {string} statusChange 6923 * @param {boolean} hasEffect 6924 * @param {StyleData} posIn 6925 * @param {StyleData} posOut 6926 * @return {boolean} 6927 */ 6928 6929 willTransition: function(statusChange, hasEffect, posIn, posOut) { 6930 var self = this, 6931 result = false; 6932 6933 if (!h.isVisible(self.dom.container)) { 6934 // If the container is not visible, the transitionEnd 6935 // event will not occur and MixItUp will hang 6936 6937 result = false; 6938 } else if ( 6939 (statusChange !== 'none' && hasEffect) || 6940 posIn.x !== posOut.x || 6941 posIn.y !== posOut.y 6942 ) { 6943 // If opacity and/or translate will change 6944 6945 result = true; 6946 } else if (self.config.animation.animateResizeTargets) { 6947 // Check if width, height or margins will change 6948 6949 result = ( 6950 posIn.width !== posOut.width || 6951 posIn.height !== posOut.height || 6952 posIn.marginRight !== posOut.marginRight || 6953 posIn.marginTop !== posOut.marginTop 6954 ); 6955 } else { 6956 result = false; 6957 } 6958 6959 return self.callFilters('resultWillTransition', result, arguments); 6960 }, 6961 6962 /** 6963 * @private 6964 * @instance 6965 * @since 2.0.0 6966 * @param {Operation} operation 6967 * @return {void} 6968 */ 6969 6970 checkProgress: function(operation) { 6971 var self = this; 6972 6973 self.targetsDone++; 6974 6975 if (self.targetsBound === self.targetsDone) { 6976 self.cleanUp(operation); 6977 } 6978 }, 6979 6980 /** 6981 * @private 6982 * @instance 6983 * @since 2.0.0 6984 * @param {Operation} operation 6985 * @return {void} 6986 */ 6987 6988 cleanUp: function(operation) { 6989 var self = this, 6990 target = null, 6991 whitespaceBefore = null, 6992 whitespaceAfter = null, 6993 nextInQueue = null, 6994 i = -1; 6995 6996 self.callActions('beforeCleanUp', arguments); 6997 6998 self.targetsMoved = 6999 self.targetsImmovable = 7000 self.targetsBound = 7001 self.targetsDone = 0; 7002 7003 for (i = 0; target = operation.show[i]; i++) { 7004 target.cleanUp(); 7005 7006 target.show(); 7007 } 7008 7009 for (i = 0; target = operation.toHide[i]; i++) { 7010 target.cleanUp(); 7011 7012 target.hide(); 7013 } 7014 7015 if (operation.willSort) { 7016 self.printSort(false, operation); 7017 } 7018 7019 // Remove any styles applied to the parent container 7020 7021 self.dom.parent.style[mixitup.features.transitionProp] = 7022 self.dom.parent.style.height = 7023 self.dom.parent.style.width = 7024 self.dom.parent.style.overflow = 7025 self.dom.parent.style[mixitup.features.perspectiveProp] = 7026 self.dom.parent.style[mixitup.features.perspectiveOriginProp] = ''; 7027 7028 if (operation.willChangeLayout) { 7029 h.removeClass(self.dom.container, operation.startContainerClassName); 7030 h.addClass(self.dom.container, operation.newContainerClassName); 7031 } 7032 7033 if (operation.toRemove.length) { 7034 for (i = 0; target = self.targets[i]; i++) { 7035 if (operation.toRemove.indexOf(target) > -1) { 7036 if ( 7037 (whitespaceBefore = target.dom.el.previousSibling) && whitespaceBefore.nodeName === '#text' && 7038 (whitespaceAfter = target.dom.el.nextSibling) && whitespaceAfter.nodeName === '#text' 7039 ) { 7040 h.removeWhitespace(whitespaceBefore); 7041 } 7042 7043 if (!operation.willSort) { 7044 // NB: Sorting will remove targets as a bi-product of `printSort()` 7045 7046 self.dom.parent.removeChild(target.dom.el); 7047 } 7048 7049 self.targets.splice(i, 1); 7050 7051 target.isInDom = false; 7052 7053 i--; 7054 } 7055 } 7056 7057 // Since targets have been removed, the original order must be updated 7058 7059 self.origOrder = self.targets; 7060 } 7061 7062 if (operation.willSort) { 7063 self.targets = operation.newOrder; 7064 } 7065 7066 self.state = operation.newState; 7067 self.lastOperation = operation; 7068 7069 self.dom.targets = self.state.targets; 7070 7071 // mixEnd 7072 7073 mixitup.events.fire('mixEnd', self.dom.container, { 7074 state: self.state, 7075 instance: self 7076 }, self.dom.document); 7077 7078 if (typeof self.config.callbacks.onMixEnd === 'function') { 7079 self.config.callbacks.onMixEnd.call(self.dom.container, self.state, self); 7080 } 7081 7082 if (operation.hasFailed) { 7083 // mixFail 7084 7085 mixitup.events.fire('mixFail', self.dom.container, { 7086 state: self.state, 7087 instance: self 7088 }, self.dom.document); 7089 7090 if (typeof self.config.callbacks.onMixFail === 'function') { 7091 self.config.callbacks.onMixFail.call(self.dom.container, self.state, self); 7092 } 7093 7094 h.addClass(self.dom.container, h.getClassname(self.config.classNames, 'container', self.config.classNames.modifierFailed)); 7095 } 7096 7097 // User-defined callback function 7098 7099 if (typeof self.userCallback === 'function') { 7100 self.userCallback.call(self.dom.container, self.state, self); 7101 } 7102 7103 if (typeof self.userDeferred.resolve === 'function') { 7104 self.userDeferred.resolve(self.state); 7105 } 7106 7107 self.userCallback = null; 7108 self.userDeferred = null; 7109 self.lastClicked = null; 7110 self.isToggling = false; 7111 self.isBusy = false; 7112 7113 if (self.queue.length) { 7114 self.callActions('beforeReadQueueCleanUp', arguments); 7115 7116 nextInQueue = self.queue.shift(); 7117 7118 // Update non-public API properties stored in queue 7119 7120 self.userDeferred = nextInQueue.deferred; 7121 self.isToggling = nextInQueue.isToggling; 7122 self.lastClicked = nextInQueue.triggerElement; 7123 7124 if (nextInQueue.instruction.command instanceof mixitup.CommandMultimix) { 7125 self.multimix.apply(self, nextInQueue.args); 7126 } else { 7127 self.dataset.apply(self, nextInQueue.args); 7128 } 7129 } 7130 7131 self.callActions('afterCleanUp', arguments); 7132 }, 7133 7134 /** 7135 * @private 7136 * @instance 7137 * @since 2.0.0 7138 * @param {Array<*>} args 7139 * @return {mixitup.UserInstruction} 7140 */ 7141 7142 parseMultimixArgs: function(args) { 7143 var self = this, 7144 instruction = new mixitup.UserInstruction(), 7145 arg = null, 7146 i = -1; 7147 7148 instruction.animate = self.config.animation.enable; 7149 instruction.command = new mixitup.CommandMultimix(); 7150 7151 for (i = 0; i < args.length; i++) { 7152 arg = args[i]; 7153 7154 if (arg === null) continue; 7155 7156 if (typeof arg === 'object') { 7157 h.extend(instruction.command, arg); 7158 } else if (typeof arg === 'boolean') { 7159 instruction.animate = arg; 7160 } else if (typeof arg === 'function') { 7161 instruction.callback = arg; 7162 } 7163 } 7164 7165 // Coerce arbitrary command arguments into typed command objects 7166 7167 if (instruction.command.insert && !(instruction.command.insert instanceof mixitup.CommandInsert)) { 7168 instruction.command.insert = self.parseInsertArgs([instruction.command.insert]).command; 7169 } 7170 7171 if (instruction.command.remove && !(instruction.command.remove instanceof mixitup.CommandRemove)) { 7172 instruction.command.remove = self.parseRemoveArgs([instruction.command.remove]).command; 7173 } 7174 7175 if (instruction.command.filter && !(instruction.command.filter instanceof mixitup.CommandFilter)) { 7176 instruction.command.filter = self.parseFilterArgs([instruction.command.filter]).command; 7177 } 7178 7179 if (instruction.command.sort && !(instruction.command.sort instanceof mixitup.CommandSort)) { 7180 instruction.command.sort = self.parseSortArgs([instruction.command.sort]).command; 7181 } 7182 7183 if (instruction.command.changeLayout && !(instruction.command.changeLayout instanceof mixitup.CommandChangeLayout)) { 7184 instruction.command.changeLayout = self.parseChangeLayoutArgs([instruction.command.changeLayout]).command; 7185 } 7186 7187 instruction = self.callFilters('instructionParseMultimixArgs', instruction, arguments); 7188 7189 h.freeze(instruction); 7190 7191 return instruction; 7192 }, 7193 7194 /** 7195 * @private 7196 * @instance 7197 * @since 2.0.0 7198 * @param {Array<*>} args 7199 * @return {mixitup.UserInstruction} 7200 */ 7201 7202 parseFilterArgs: function(args) { 7203 var self = this, 7204 instruction = new mixitup.UserInstruction(), 7205 arg = null, 7206 i = -1; 7207 7208 instruction.animate = self.config.animation.enable; 7209 instruction.command = new mixitup.CommandFilter(); 7210 7211 for (i = 0; i < args.length; i++) { 7212 arg = args[i]; 7213 7214 if (typeof arg === 'string') { 7215 // Selector 7216 7217 instruction.command.selector = arg; 7218 } else if (arg === null) { 7219 instruction.command.collection = []; 7220 } else if (typeof arg === 'object' && h.isElement(arg, self.dom.document)) { 7221 // Single element 7222 7223 instruction.command.collection = [arg]; 7224 } else if (typeof arg === 'object' && typeof arg.length !== 'undefined') { 7225 // Multiple elements in array, NodeList or jQuery collection 7226 7227 instruction.command.collection = h.arrayFromList(arg); 7228 } else if (typeof arg === 'object') { 7229 // Filter command 7230 7231 h.extend(instruction.command, arg); 7232 } else if (typeof arg === 'boolean') { 7233 instruction.animate = arg; 7234 } else if (typeof arg === 'function') { 7235 instruction.callback = arg; 7236 } 7237 } 7238 7239 if (instruction.command.selector && instruction.command.collection) { 7240 throw new Error(mixitup.messages.errorFilterInvalidArguments()); 7241 } 7242 7243 instruction = self.callFilters('instructionParseFilterArgs', instruction, arguments); 7244 7245 h.freeze(instruction); 7246 7247 return instruction; 7248 }, 7249 7250 parseSortArgs: function(args) { 7251 var self = this, 7252 instruction = new mixitup.UserInstruction(), 7253 arg = null, 7254 sortString = '', 7255 i = -1; 7256 7257 instruction.animate = self.config.animation.enable; 7258 instruction.command = new mixitup.CommandSort(); 7259 7260 for (i = 0; i < args.length; i++) { 7261 arg = args[i]; 7262 7263 if (arg === null) continue; 7264 7265 switch (typeof arg) { 7266 case 'string': 7267 // Sort string 7268 7269 sortString = arg; 7270 7271 break; 7272 case 'object': 7273 // Array of element references 7274 7275 if (arg.length) { 7276 instruction.command.collection = h.arrayFromList(arg); 7277 } 7278 7279 break; 7280 case 'boolean': 7281 instruction.animate = arg; 7282 7283 break; 7284 case 'function': 7285 instruction.callback = arg; 7286 7287 break; 7288 } 7289 } 7290 7291 if (sortString) { 7292 instruction.command = self.parseSortString(sortString, instruction.command); 7293 } 7294 7295 instruction = self.callFilters('instructionParseSortArgs', instruction, arguments); 7296 7297 h.freeze(instruction); 7298 7299 return instruction; 7300 }, 7301 7302 /** 7303 * @private 7304 * @instance 7305 * @since 2.0.0 7306 * @param {Array<*>} args 7307 * @return {mixitup.UserInstruction} 7308 */ 7309 7310 parseInsertArgs: function(args) { 7311 var self = this, 7312 instruction = new mixitup.UserInstruction(), 7313 arg = null, 7314 i = -1; 7315 7316 instruction.animate = self.config.animation.enable; 7317 instruction.command = new mixitup.CommandInsert(); 7318 7319 for (i = 0; i < args.length; i++) { 7320 arg = args[i]; 7321 7322 if (arg === null) continue; 7323 7324 if (typeof arg === 'number') { 7325 // Insert index 7326 7327 instruction.command.index = arg; 7328 } else if (typeof arg === 'string' && ['before', 'after'].indexOf(arg) > -1) { 7329 // 'before'/'after' 7330 7331 instruction.command.position = arg; 7332 } else if (typeof arg === 'string') { 7333 // Markup 7334 7335 instruction.command.collection = 7336 h.arrayFromList(h.createElement(arg).childNodes); 7337 } else if (typeof arg === 'object' && h.isElement(arg, self.dom.document)) { 7338 // Single element 7339 7340 !instruction.command.collection.length ? 7341 (instruction.command.collection = [arg]) : 7342 (instruction.command.sibling = arg); 7343 } else if (typeof arg === 'object' && arg.length) { 7344 // Multiple elements in array or jQuery collection 7345 7346 !instruction.command.collection.length ? 7347 (instruction.command.collection = arg) : 7348 instruction.command.sibling = arg[0]; 7349 } else if (typeof arg === 'object' && arg.childNodes && arg.childNodes.length) { 7350 // Document fragment 7351 7352 !instruction.command.collection.length ? 7353 instruction.command.collection = h.arrayFromList(arg.childNodes) : 7354 instruction.command.sibling = arg.childNodes[0]; 7355 } else if (typeof arg === 'object') { 7356 // Insert command 7357 7358 h.extend(instruction.command, arg); 7359 } else if (typeof arg === 'boolean') { 7360 instruction.animate = arg; 7361 } else if (typeof arg === 'function') { 7362 instruction.callback = arg; 7363 } 7364 } 7365 7366 if (instruction.command.index && instruction.command.sibling) { 7367 throw new Error(mixitup.messages.errorInsertInvalidArguments()); 7368 } 7369 7370 if (!instruction.command.collection.length && self.config.debug.showWarnings) { 7371 console.warn(mixitup.messages.warningInsertNoElements()); 7372 } 7373 7374 instruction = self.callFilters('instructionParseInsertArgs', instruction, arguments); 7375 7376 h.freeze(instruction); 7377 7378 return instruction; 7379 }, 7380 7381 /** 7382 * @private 7383 * @instance 7384 * @since 3.0.0 7385 * @param {Array<*>} args 7386 * @return {mixitup.UserInstruction} 7387 */ 7388 7389 parseRemoveArgs: function(args) { 7390 var self = this, 7391 instruction = new mixitup.UserInstruction(), 7392 target = null, 7393 arg = null, 7394 i = -1; 7395 7396 instruction.animate = self.config.animation.enable; 7397 instruction.command = new mixitup.CommandRemove(); 7398 7399 for (i = 0; i < args.length; i++) { 7400 arg = args[i]; 7401 7402 if (arg === null) continue; 7403 7404 switch (typeof arg) { 7405 case 'number': 7406 if (self.targets[arg]) { 7407 instruction.command.targets[0] = self.targets[arg]; 7408 } 7409 7410 break; 7411 case 'string': 7412 instruction.command.collection = h.arrayFromList(self.dom.parent.querySelectorAll(arg)); 7413 7414 break; 7415 case 'object': 7416 if (arg && arg.length) { 7417 instruction.command.collection = arg; 7418 } else if (h.isElement(arg, self.dom.document)) { 7419 instruction.command.collection = [arg]; 7420 } else { 7421 // Remove command 7422 7423 h.extend(instruction.command, arg); 7424 } 7425 7426 break; 7427 case 'boolean': 7428 instruction.animate = arg; 7429 7430 break; 7431 case 'function': 7432 instruction.callback = arg; 7433 7434 break; 7435 } 7436 } 7437 7438 if (instruction.command.collection.length) { 7439 for (i = 0; target = self.targets[i]; i++) { 7440 if (instruction.command.collection.indexOf(target.dom.el) > -1) { 7441 instruction.command.targets.push(target); 7442 } 7443 } 7444 } 7445 7446 if (!instruction.command.targets.length && self.config.debug.showWarnings) { 7447 console.warn(mixitup.messages.warningRemoveNoElements()); 7448 } 7449 7450 h.freeze(instruction); 7451 7452 return instruction; 7453 }, 7454 7455 /** 7456 * @private 7457 * @instance 7458 * @since 3.0.0 7459 * @param {Array<*>} args 7460 * @return {mixitup.UserInstruction} 7461 */ 7462 7463 parseDatasetArgs: function(args) { 7464 var self = this, 7465 instruction = new mixitup.UserInstruction(), 7466 arg = null, 7467 i = -1; 7468 7469 instruction.animate = self.config.animation.enable; 7470 instruction.command = new mixitup.CommandDataset(); 7471 7472 for (i = 0; i < args.length; i++) { 7473 arg = args[i]; 7474 7475 if (arg === null) continue; 7476 7477 switch (typeof arg) { 7478 case 'object': 7479 if (Array.isArray(arg) || typeof arg.length === 'number') { 7480 instruction.command.dataset = arg; 7481 } else { 7482 // Change layout command 7483 7484 h.extend(instruction.command, arg); 7485 } 7486 7487 break; 7488 case 'boolean': 7489 instruction.animate = arg; 7490 7491 break; 7492 case 'function': 7493 instruction.callback = arg; 7494 7495 break; 7496 } 7497 } 7498 7499 h.freeze(instruction); 7500 7501 return instruction; 7502 }, 7503 7504 /** 7505 * @private 7506 * @instance 7507 * @since 3.0.0 7508 * @param {Array<*>} args 7509 * @return {mixitup.UserInstruction} 7510 */ 7511 7512 parseChangeLayoutArgs: function(args) { 7513 var self = this, 7514 instruction = new mixitup.UserInstruction(), 7515 arg = null, 7516 i = -1; 7517 7518 instruction.animate = self.config.animation.enable; 7519 instruction.command = new mixitup.CommandChangeLayout(); 7520 7521 for (i = 0; i < args.length; i++) { 7522 arg = args[i]; 7523 7524 if (arg === null) continue; 7525 7526 switch (typeof arg) { 7527 case 'string': 7528 instruction.command.containerClassName = arg; 7529 7530 break; 7531 case 'object': 7532 // Change layout command 7533 7534 h.extend(instruction.command, arg); 7535 7536 break; 7537 case 'boolean': 7538 instruction.animate = arg; 7539 7540 break; 7541 case 'function': 7542 instruction.callback = arg; 7543 7544 break; 7545 } 7546 } 7547 7548 h.freeze(instruction); 7549 7550 return instruction; 7551 }, 7552 7553 /** 7554 * @private 7555 * @instance 7556 * @since 3.0.0 7557 * @param {mixitup.QueueItem} queueItem 7558 * @return {Promise.<mixitup.State>} 7559 */ 7560 7561 queueMix: function(queueItem) { 7562 var self = this, 7563 deferred = null, 7564 toggleSelector = ''; 7565 7566 self.callActions('beforeQueueMix', arguments); 7567 7568 deferred = h.defer(mixitup.libraries); 7569 7570 if (self.config.animation.queue && self.queue.length < self.config.animation.queueLimit) { 7571 queueItem.deferred = deferred; 7572 7573 self.queue.push(queueItem); 7574 7575 // Keep controls in sync with user interactions. Mixer will catch up as it drains the queue. 7576 7577 if (self.config.controls.enable) { 7578 if (self.isToggling) { 7579 self.buildToggleArray(queueItem.instruction.command); 7580 7581 toggleSelector = self.getToggleSelector(); 7582 7583 self.updateControls({ 7584 filter: { 7585 selector: toggleSelector 7586 } 7587 }); 7588 } else { 7589 self.updateControls(queueItem.instruction.command); 7590 } 7591 } 7592 } else { 7593 if (self.config.debug.showWarnings) { 7594 console.warn(mixitup.messages.warningMultimixInstanceQueueFull()); 7595 } 7596 7597 deferred.resolve(self.state); 7598 7599 mixitup.events.fire('mixBusy', self.dom.container, { 7600 state: self.state, 7601 instance: self 7602 }, self.dom.document); 7603 7604 if (typeof self.config.callbacks.onMixBusy === 'function') { 7605 self.config.callbacks.onMixBusy.call(self.dom.container, self.state, self); 7606 } 7607 } 7608 7609 return self.callFilters('promiseQueueMix', deferred.promise, arguments); 7610 }, 7611 7612 /** 7613 * @private 7614 * @instance 7615 * @since 3.0.0 7616 * @param {Array.<object>} newDataset 7617 * @return {Operation} 7618 */ 7619 7620 getDataOperation: function(newDataset) { 7621 var self = this, 7622 operation = new mixitup.Operation(), 7623 startDataset = []; 7624 7625 operation = self.callFilters('operationUnmappedGetDataOperation', operation, arguments); 7626 7627 if (self.dom.targets.length && !(startDataset = (self.state.activeDataset || [])).length) { 7628 throw new Error(mixitup.messages.errorDatasetNotSet()); 7629 } 7630 7631 operation.id = h.randomHex(); 7632 operation.startState = self.state; 7633 operation.startDataset = startDataset; 7634 operation.newDataset = newDataset.slice(); 7635 7636 self.diffDatasets(operation); 7637 7638 operation.startOrder = self.targets; 7639 operation.newOrder = operation.show; 7640 7641 if (self.config.animation.enable) { 7642 self.getStartMixData(operation); 7643 self.setInter(operation); 7644 7645 operation.docState = h.getDocumentState(self.dom.document); 7646 7647 self.getInterMixData(operation); 7648 self.setFinal(operation); 7649 self.getFinalMixData(operation); 7650 7651 self.parseEffects(); 7652 7653 operation.hasEffect = self.hasEffect(); 7654 7655 self.getTweenData(operation); 7656 } 7657 7658 self.targets = operation.show.slice(); 7659 7660 operation.newState = self.buildState(operation); 7661 7662 // NB: Targets to be removed must be included in `self.targets` for removal during clean up, 7663 // but are added after state is built so that state is accurate 7664 7665 Array.prototype.push.apply(self.targets, operation.toRemove); 7666 7667 operation = self.callFilters('operationMappedGetDataOperation', operation, arguments); 7668 7669 return operation; 7670 }, 7671 7672 /** 7673 * @private 7674 * @instance 7675 * @since 3.0.0 7676 * @param {mixitup.Operation} operation 7677 * @return {void} 7678 */ 7679 7680 diffDatasets: function(operation) { 7681 var self = this, 7682 persistantStartIds = [], 7683 persistantNewIds = [], 7684 insertedTargets = [], 7685 data = null, 7686 target = null, 7687 el = null, 7688 frag = null, 7689 nextEl = null, 7690 uids = {}, 7691 id = '', 7692 i = -1; 7693 7694 self.callActions('beforeDiffDatasets', arguments); 7695 7696 for (i = 0; data = operation.newDataset[i]; i++) { 7697 if (typeof (id = data[self.config.data.uidKey]) === 'undefined' || id.toString().length < 1) { 7698 throw new TypeError(mixitup.messages.errorDatasetInvalidUidKey({ 7699 uidKey: self.config.data.uidKey 7700 })); 7701 } 7702 7703 if (!uids[id]) { 7704 uids[id] = true; 7705 } else { 7706 throw new Error(mixitup.messages.errorDatasetDuplicateUid({ 7707 uid: id 7708 })); 7709 } 7710 7711 if ((target = self.cache[id]) instanceof mixitup.Target) { 7712 // Already in cache 7713 7714 if (self.config.data.dirtyCheck && !h.deepEquals(data, target.data)) { 7715 // change detected 7716 7717 el = target.render(data); 7718 7719 target.data = data; 7720 7721 if (el !== target.dom.el) { 7722 // Update target element reference 7723 7724 if (target.isInDom) { 7725 target.unbindEvents(); 7726 7727 self.dom.parent.replaceChild(el, target.dom.el); 7728 } 7729 7730 if (!target.isShown) { 7731 el.style.display = 'none'; 7732 } 7733 7734 target.dom.el = el; 7735 7736 if (target.isInDom) { 7737 target.bindEvents(); 7738 } 7739 } 7740 } 7741 7742 el = target.dom.el; 7743 } else { 7744 // New target 7745 7746 target = new mixitup.Target(); 7747 7748 target.init(null, self, data); 7749 7750 target.hide(); 7751 } 7752 7753 if (!target.isInDom) { 7754 // Adding to DOM 7755 7756 if (!frag) { 7757 // Open frag 7758 7759 frag = self.dom.document.createDocumentFragment(); 7760 } 7761 7762 if (frag.lastElementChild) { 7763 frag.appendChild(self.dom.document.createTextNode(' ')); 7764 } 7765 7766 frag.appendChild(target.dom.el); 7767 7768 target.isInDom = true; 7769 7770 target.unbindEvents(); 7771 target.bindEvents(); 7772 target.hide(); 7773 7774 operation.toShow.push(target); 7775 7776 insertedTargets.push(target); 7777 } else { 7778 // Already in DOM 7779 7780 nextEl = target.dom.el.nextElementSibling; 7781 7782 persistantNewIds.push(id); 7783 7784 if (frag) { 7785 // Close and insert previously opened frag 7786 7787 if (frag.lastElementChild) { 7788 frag.appendChild(self.dom.document.createTextNode(' ')); 7789 } 7790 7791 self.insertDatasetFrag(frag, target.dom.el, insertedTargets); 7792 7793 frag = null; 7794 } 7795 } 7796 7797 operation.show.push(target); 7798 } 7799 7800 if (frag) { 7801 // Unclosed frag remaining 7802 7803 nextEl = nextEl || self.config.layout.siblingAfter; 7804 7805 if (nextEl) { 7806 frag.appendChild(self.dom.document.createTextNode(' ')); 7807 } 7808 7809 self.insertDatasetFrag(frag, nextEl, insertedTargets); 7810 } 7811 7812 for (i = 0; data = operation.startDataset[i]; i++) { 7813 id = data[self.config.data.uidKey]; 7814 7815 target = self.cache[id]; 7816 7817 if (operation.show.indexOf(target) < 0) { 7818 // Previously shown but now absent 7819 7820 operation.hide.push(target); 7821 operation.toHide.push(target); 7822 operation.toRemove.push(target); 7823 } else { 7824 persistantStartIds.push(id); 7825 } 7826 } 7827 7828 if (!h.isEqualArray(persistantStartIds, persistantNewIds)) { 7829 operation.willSort = true; 7830 } 7831 7832 self.callActions('afterDiffDatasets', arguments); 7833 }, 7834 7835 /** 7836 * @private 7837 * @instance 7838 * @since 3.1.5 7839 * @param {DocumentFragment} frag 7840 * @param {(HTMLElement|null)} nextEl 7841 * @param {Array.<mixitup.Target>} targets 7842 * @return {void} 7843 */ 7844 7845 insertDatasetFrag: function(frag, nextEl, targets) { 7846 var self = this; 7847 var insertAt = nextEl ? h.arrayFromList(self.dom.parent.children).indexOf(nextEl) : self.targets.length; 7848 7849 self.dom.parent.insertBefore(frag, nextEl); 7850 7851 while (targets.length) { 7852 self.targets.splice(insertAt, 0, targets.shift()); 7853 7854 insertAt++; 7855 } 7856 }, 7857 7858 /** 7859 * @private 7860 * @instance 7861 * @since 3.0.0 7862 * @param {mixitup.CommandSort} sortCommandA 7863 * @param {mixitup.CommandSort} sortCommandB 7864 * @return {boolean} 7865 */ 7866 7867 willSort: function(sortCommandA, sortCommandB) { 7868 var self = this, 7869 result = false; 7870 7871 if ( 7872 self.config.behavior.liveSort || 7873 sortCommandA.order === 'random' || 7874 sortCommandA.attribute !== sortCommandB.attribute || 7875 sortCommandA.order !== sortCommandB.order || 7876 sortCommandA.collection !== sortCommandB.collection || 7877 (sortCommandA.next === null && sortCommandB.next) || 7878 (sortCommandA.next && sortCommandB.next === null) 7879 ) { 7880 result = true; 7881 } else if (sortCommandA.next && sortCommandB.next) { 7882 result = self.willSort(sortCommandA.next, sortCommandB.next); 7883 } else { 7884 result = false; 7885 } 7886 7887 return self.callFilters('resultWillSort', result, arguments); 7888 }, 7889 7890 /** 7891 * A shorthand method for `.filter('all')`. Shows all targets in the container. 7892 * 7893 * @example 7894 * 7895 * .show() 7896 * 7897 * @example <caption>Example: Showing all targets</caption> 7898 * 7899 * mixer.show() 7900 * .then(function(state) { 7901 * console.log(state.totalShow === state.totalTargets); // true 7902 * }); 7903 * 7904 * @public 7905 * @instance 7906 * @since 3.0.0 7907 * @return {Promise.<mixitup.State>} 7908 */ 7909 7910 show: function() { 7911 var self = this; 7912 7913 return self.filter('all'); 7914 }, 7915 7916 /** 7917 * A shorthand method for `.filter('none')`. Hides all targets in the container. 7918 * 7919 * @example 7920 * 7921 * .hide() 7922 * 7923 * @example <caption>Example: Hiding all targets</caption> 7924 * 7925 * mixer.hide() 7926 * .then(function(state) { 7927 * console.log(state.totalShow === 0); // true 7928 * console.log(state.totalHide === state.totalTargets); // true 7929 * }); 7930 * 7931 * @public 7932 * @instance 7933 * @since 3.0.0 7934 * @return {Promise.<mixitup.State>} 7935 */ 7936 7937 hide: function() { 7938 var self = this; 7939 7940 return self.filter('none'); 7941 }, 7942 7943 /** 7944 * Returns a boolean indicating whether or not a MixItUp operation is 7945 * currently in progress. 7946 * 7947 * @example 7948 * 7949 * .isMixing() 7950 * 7951 * @example <caption>Example: Checking the status of a mixer</caption> 7952 * 7953 * mixer.sort('random', function() { 7954 * console.log(mixer.isMixing()) // false 7955 * }); 7956 * 7957 * console.log(mixer.isMixing()) // true 7958 * 7959 * @public 7960 * @instance 7961 * @since 2.0.0 7962 * @return {boolean} 7963 */ 7964 7965 isMixing: function() { 7966 var self = this; 7967 7968 return self.isBusy; 7969 }, 7970 7971 /** 7972 * Filters all targets in the container by a provided selector string, or the values `'all'` 7973 * or `'none'`. Only targets matching the selector will be shown. 7974 * 7975 * @example 7976 * 7977 * .filter(selector [, animate] [, callback]) 7978 * 7979 * @example <caption>Example 1: Filtering targets by a class selector</caption> 7980 * 7981 * mixer.filter('.category-a') 7982 * .then(function(state) { 7983 * console.log(state.totalShow === containerEl.querySelectorAll('.category-a').length); // true 7984 * }); 7985 * 7986 * @example <caption>Example 2: Filtering targets by an attribute selector</caption> 7987 * 7988 * mixer.filter('[data-category~="a"]') 7989 * .then(function(state) { 7990 * console.log(state.totalShow === containerEl.querySelectorAll('[data-category~="a"]').length); // true 7991 * }); 7992 * 7993 * @example <caption>Example 3: Filtering targets by a compound selector</caption> 7994 * 7995 * // Show only those targets with the classes 'category-a' AND 'category-b' 7996 * 7997 * mixer.filter('.category-a.category-c') 7998 * .then(function(state) { 7999 * console.log(state.totalShow === containerEl.querySelectorAll('.category-a.category-c').length); // true 8000 * }); 8001 * 8002 * @example <caption>Example 4: Filtering via an element collection</caption> 8003 * 8004 * var collection = Array.from(container.querySelectorAll('.mix')); 8005 * 8006 * console.log(collection.length); // 34 8007 * 8008 * // Filter the collection manually using Array.prototype.filter 8009 * 8010 * var filtered = collection.filter(function(target) { 8011 * return parseInt(target.getAttribute('data-price')) > 10; 8012 * }); 8013 * 8014 * console.log(filtered.length); // 22 8015 * 8016 * // Pass the filtered collection to MixItUp 8017 * 8018 * mixer.filter(filtered) 8019 * .then(function(state) { 8020 * console.log(state.activeFilter.collection.length === 22); // true 8021 * }); 8022 * 8023 * @public 8024 * @instance 8025 * @since 2.0.0 8026 * @param {(string|HTMLElement|Array.<HTMLElement>)} selector 8027 * Any valid CSS selector (i.e. `'.category-a'`), or the values `'all'` or `'none'`. The filter method also accepts a reference to single target element or a collection of target elements to show. 8028 * @param {boolean} [animate=true] 8029 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8030 * @param {function} [callback=null] 8031 * An optional callback function to be invoked after the operation has completed. 8032 * @return {Promise.<mixitup.State>} 8033 * A promise resolving with the current state object. 8034 */ 8035 8036 filter: function() { 8037 var self = this, 8038 instruction = self.parseFilterArgs(arguments); 8039 8040 return self.multimix({ 8041 filter: instruction.command 8042 }, instruction.animate, instruction.callback); 8043 }, 8044 8045 /** 8046 * Adds an additional selector to the currently active filter selector, concatenating 8047 * as per the logic defined in `controls.toggleLogic`. 8048 * 8049 * @example 8050 * 8051 * .toggleOn(selector [, animate] [, callback]) 8052 * 8053 * @example <caption>Example: Toggling on a filter selector</caption> 8054 * 8055 * console.log(mixer.getState().activeFilter.selector); // '.category-a' 8056 * 8057 * mixer.toggleOn('.category-b') 8058 * .then(function(state) { 8059 * console.log(state.activeFilter.selector); // '.category-a, .category-b' 8060 * }); 8061 * 8062 * @public 8063 * @instance 8064 * @since 3.0.0 8065 * @param {string} selector 8066 * Any valid CSS selector (i.e. `'.category-a'`) 8067 * @param {boolean} [animate=true] 8068 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8069 * @param {function} [callback=null] 8070 * An optional callback function to be invoked after the operation has completed. 8071 * @return {Promise.<mixitup.State>} 8072 * A promise resolving with the current state object. 8073 */ 8074 8075 toggleOn: function() { 8076 var self = this, 8077 instruction = self.parseFilterArgs(arguments), 8078 selector = instruction.command.selector, 8079 toggleSelector = ''; 8080 8081 self.isToggling = true; 8082 8083 if (self.toggleArray.indexOf(selector) < 0) { 8084 self.toggleArray.push(selector); 8085 } 8086 8087 toggleSelector = self.getToggleSelector(); 8088 8089 return self.multimix({ 8090 filter: toggleSelector 8091 }, instruction.animate, instruction.callback); 8092 }, 8093 8094 /** 8095 * Removes a selector from the active filter selector. 8096 * 8097 * @example 8098 * 8099 * .toggleOff(selector [, animate] [, callback]) 8100 * 8101 * @example <caption>Example: Toggling off a filter selector</caption> 8102 * 8103 * console.log(mixer.getState().activeFilter.selector); // '.category-a, .category-b' 8104 * 8105 * mixer.toggleOff('.category-b') 8106 * .then(function(state) { 8107 * console.log(state.activeFilter.selector); // '.category-a' 8108 * }); 8109 * 8110 * @public 8111 * @instance 8112 * @since 3.0.0 8113 * @param {string} selector 8114 * Any valid CSS selector (i.e. `'.category-a'`) 8115 * @param {boolean} [animate=true] 8116 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8117 * @param {function} [callback=null] 8118 * An optional callback function to be invoked after the operation has completed. 8119 * @return {Promise.<mixitup.State>} 8120 * A promise resolving with the current state object. 8121 */ 8122 8123 toggleOff: function() { 8124 var self = this, 8125 instruction = self.parseFilterArgs(arguments), 8126 selector = instruction.command.selector, 8127 selectorIndex = self.toggleArray.indexOf(selector), 8128 toggleSelector = ''; 8129 8130 self.isToggling = true; 8131 8132 if (selectorIndex > -1) { 8133 self.toggleArray.splice(selectorIndex, 1); 8134 } 8135 8136 toggleSelector = self.getToggleSelector(); 8137 8138 return self.multimix({ 8139 filter: toggleSelector 8140 }, instruction.animate, instruction.callback); 8141 }, 8142 8143 /** 8144 * Sorts all targets in the container according to a provided sort string. 8145 * 8146 * @example 8147 * 8148 * .sort(sortString [, animate] [, callback]) 8149 * 8150 * @example <caption>Example 1: Sorting by the default DOM order</caption> 8151 * 8152 * // Reverse the default order of the targets 8153 * 8154 * mixer.sort('default:desc') 8155 * .then(function(state) { 8156 * console.log(state.activeSort.attribute === 'default'); // true 8157 * console.log(state.activeSort.order === 'desc'); // true 8158 * }); 8159 * 8160 * @example <caption>Example 2: Sorting by a custom data-attribute</caption> 8161 * 8162 * // Sort the targets by the value of a `data-published-date` attribute 8163 * 8164 * mixer.sort('published-date:asc') 8165 * .then(function(state) { 8166 * console.log(state.activeSort.attribute === 'published-date'); // true 8167 * console.log(state.activeSort.order === 'asc'); // true 8168 * }); 8169 * 8170 * @example <caption>Example 3: Sorting by multiple attributes</caption> 8171 * 8172 * // Sort the targets by the value of a `data-published-date` attribute, then by `data-title` 8173 * 8174 * mixer.sort('published-date:desc data-title:asc') 8175 * .then(function(state) { 8176 * console.log(state.activeSort.attribute === 'published-date'); // true 8177 * console.log(state.activeSort.order === 'desc'); // true 8178 * 8179 * console.log(state.activeSort.next.attribute === 'title'); // true 8180 * console.log(state.activeSort.next.order === 'asc'); // true 8181 * }); 8182 * 8183 * @example <caption>Example 4: Sorting by random</caption> 8184 * 8185 * mixer.sort('random') 8186 * .then(function(state) { 8187 * console.log(state.activeSort.order === 'random') // true 8188 * }); 8189 * 8190 * @example <caption>Example 5: Sorting via an element collection</caption> 8191 * 8192 * var collection = Array.from(container.querySelectorAll('.mix')); 8193 * 8194 * // Swap the position of two elements in the collection: 8195 * 8196 * var temp = collection[1]; 8197 * 8198 * collection[1] = collection[0]; 8199 * collection[0] = temp; 8200 * 8201 * // Pass the sorted collection to MixItUp 8202 * 8203 * mixer.sort(collection) 8204 * .then(function(state) { 8205 * console.log(state.targets[0] === collection[0]); // true 8206 * }); 8207 * 8208 * @public 8209 * @instance 8210 * @since 2.0.0 8211 * @param {(string|Array.<HTMLElement>)} sortString 8212 * A valid sort string (e.g. `'default'`, `'published-date:asc'`, or `'random'`). The sort method also accepts an array of all target elements in a user-defined order. 8213 * @param {boolean} [animate=true] 8214 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8215 * @param {function} [callback=null] 8216 * An optional callback function to be invoked after the operation has completed. 8217 * @return {Promise.<mixitup.State>} 8218 * A promise resolving with the current state object. 8219 */ 8220 8221 sort: function() { 8222 var self = this, 8223 instruction = self.parseSortArgs(arguments); 8224 8225 return self.multimix({ 8226 sort: instruction.command 8227 }, instruction.animate, instruction.callback); 8228 }, 8229 8230 /** 8231 * Changes the layout of the container by adding, removing or updating a 8232 * layout-specific class name. If `animation.animateResizetargets` is 8233 * enabled, MixItUp will attempt to gracefully animate the width, height, 8234 * and position of targets between layout states. 8235 * 8236 * @example 8237 * 8238 * .changeLayout(containerClassName [, animate] [, callback]) 8239 * 8240 * @example <caption>Example 1: Adding a new class name to the container</caption> 8241 * 8242 * mixer.changeLayout('container-list') 8243 * .then(function(state) { 8244 * console.log(state.activeContainerClass === 'container-list'); // true 8245 * }); 8246 * 8247 * @example <caption>Example 2: Removing a previously added class name from the container</caption> 8248 * 8249 * mixer.changeLayout('') 8250 * .then(function(state) { 8251 * console.log(state.activeContainerClass === ''); // true 8252 * }); 8253 * 8254 * @public 8255 * @instance 8256 * @since 2.0.0 8257 * @param {string} containerClassName 8258 * A layout-specific class name to add to the container. 8259 * @param {boolean} [animate=true] 8260 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8261 * @param {function} [callback=null] 8262 * An optional callback function to be invoked after the operation has completed. 8263 * @return {Promise.<mixitup.State>} 8264 * A promise resolving with the current state object. 8265 */ 8266 8267 changeLayout: function() { 8268 var self = this, 8269 instruction = self.parseChangeLayoutArgs(arguments); 8270 8271 return self.multimix({ 8272 changeLayout: instruction.command 8273 }, instruction.animate, instruction.callback); 8274 }, 8275 8276 /** 8277 * Updates the contents and order of the container to reflect the provided dataset, 8278 * if the dataset API is in use. 8279 * 8280 * The dataset API is designed for use in API-driven JavaScript applications, and 8281 * can be used instead of DOM-based methods such as `.filter()`, `.sort()`, 8282 * `.insert()`, etc. When used, insertion, removal, sorting and pagination can be 8283 * achieved purely via changes to your data model, without the uglyness of having 8284 * to interact with or query the DOM directly. 8285 * 8286 * @example 8287 * 8288 * .dataset(dataset [, animate] [, callback]) 8289 * 8290 * @example <caption>Example 1: Rendering a dataset</caption> 8291 * 8292 * var myDataset = [ 8293 * {id: 1, ...}, 8294 * {id: 2, ...}, 8295 * {id: 3, ...} 8296 * ]; 8297 * 8298 * mixer.dataset(myDataset) 8299 * .then(function(state) { 8300 * console.log(state.totalShow === 3); // true 8301 * }); 8302 * 8303 * @example <caption>Example 2: Sorting a dataset</caption> 8304 * 8305 * // Create a new dataset in reverse order 8306 * 8307 * var newDataset = myDataset.slice().reverse(); 8308 * 8309 * mixer.dataset(newDataset) 8310 * .then(function(state) { 8311 * console.log(state.activeDataset[0] === myDataset[2]); // true 8312 * }); 8313 * 8314 * @example <caption>Example 3: Removing an item from the dataset</caption> 8315 * 8316 * console.log(myDataset.length); // 3 8317 * 8318 * // Create a new dataset with the last item removed. 8319 * 8320 * var newDataset = myDataset.slice().pop(); 8321 * 8322 * mixer.dataset(newDataset) 8323 * .then(function(state) { 8324 * console.log(state.totalShow === 2); // true 8325 * }); 8326 * 8327 * @public 8328 * @instance 8329 * @since 3.0.0 8330 * @param {Array.<object>} dataset 8331 * An array of objects, each one representing the underlying data model of a target to be rendered. 8332 * @param {boolean} [animate=true] 8333 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8334 * @param {function} [callback=null] 8335 * An optional callback function to be invoked after the operation has completed. 8336 * @return {Promise.<mixitup.State>} 8337 * A promise resolving with the current state object. 8338 */ 8339 8340 dataset: function() { 8341 var self = this, 8342 instruction = self.parseDatasetArgs(arguments), 8343 operation = null, 8344 queueItem = null, 8345 animate = false; 8346 8347 self.callActions('beforeDataset', arguments); 8348 8349 if (!self.isBusy) { 8350 if (instruction.callback) self.userCallback = instruction.callback; 8351 8352 animate = (instruction.animate ^ self.config.animation.enable) ? instruction.animate : self.config.animation.enable; 8353 8354 operation = self.getDataOperation(instruction.command.dataset); 8355 8356 return self.goMix(animate, operation); 8357 } else { 8358 queueItem = new mixitup.QueueItem(); 8359 8360 queueItem.args = arguments; 8361 queueItem.instruction = instruction; 8362 8363 return self.queueMix(queueItem); 8364 } 8365 }, 8366 8367 /** 8368 * Performs simultaneous `filter`, `sort`, `insert`, `remove` and `changeLayout` 8369 * operations as requested. 8370 * 8371 * @example 8372 * 8373 * .multimix(multimixCommand [, animate] [, callback]) 8374 * 8375 * @example <caption>Example 1: Performing simultaneous filtering and sorting</caption> 8376 * 8377 * mixer.multimix({ 8378 * filter: '.category-b', 8379 * sort: 'published-date:desc' 8380 * }) 8381 * .then(function(state) { 8382 * console.log(state.activeFilter.selector === '.category-b'); // true 8383 * console.log(state.activeSort.attribute === 'published-date'); // true 8384 * }); 8385 * 8386 * @example <caption>Example 2: Performing simultaneous sorting, insertion, and removal</caption> 8387 * 8388 * console.log(mixer.getState().totalShow); // 6 8389 * 8390 * // NB: When inserting via `multimix()`, an object should be provided as the value 8391 * // for the `insert` portion of the command, allowing for a collection of elements 8392 * // and an insertion index to be specified. 8393 * 8394 * mixer.multimix({ 8395 * sort: 'published-date:desc', // Sort the container, including any new elements 8396 * insert: { 8397 * collection: [newElementReferenceA, newElementReferenceB], // Add 2 new elements at index 5 8398 * index: 5 8399 * }, 8400 * remove: existingElementReference // Remove 1 existing element 8401 * }) 8402 * .then(function(state) { 8403 * console.log(state.activeSort.attribute === 'published-date'); // true 8404 * console.log(state.totalShow === 7); // true 8405 * }); 8406 * 8407 * @public 8408 * @instance 8409 * @since 2.0.0 8410 * @param {object} multimixCommand 8411 * An object containing one or more things to do 8412 * @param {boolean} [animate=true] 8413 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8414 * @param {function} [callback=null] 8415 * An optional callback function to be invoked after the operation has completed. 8416 * @return {Promise.<mixitup.State>} 8417 * A promise resolving with the current state object. 8418 */ 8419 8420 multimix: function() { 8421 var self = this, 8422 operation = null, 8423 animate = false, 8424 queueItem = null, 8425 instruction = self.parseMultimixArgs(arguments); 8426 8427 self.callActions('beforeMultimix', arguments); 8428 8429 if (!self.isBusy) { 8430 operation = self.getOperation(instruction.command); 8431 8432 if (self.config.controls.enable) { 8433 // Update controls for API calls 8434 8435 if (instruction.command.filter && !self.isToggling) { 8436 // As we are not toggling, reset the toggle array 8437 // so new filter overrides existing toggles 8438 8439 self.toggleArray.length = 0; 8440 self.buildToggleArray(operation.command); 8441 } 8442 8443 if (self.queue.length < 1) { 8444 self.updateControls(operation.command); 8445 } 8446 } 8447 8448 if (instruction.callback) self.userCallback = instruction.callback; 8449 8450 // Always allow the instruction to override the instance setting 8451 8452 animate = (instruction.animate ^ self.config.animation.enable) ? 8453 instruction.animate : 8454 self.config.animation.enable; 8455 8456 self.callFilters('operationMultimix', operation, arguments); 8457 8458 return self.goMix(animate, operation); 8459 } else { 8460 queueItem = new mixitup.QueueItem(); 8461 8462 queueItem.args = arguments; 8463 queueItem.instruction = instruction; 8464 queueItem.triggerElement = self.lastClicked; 8465 queueItem.isToggling = self.isToggling; 8466 8467 return self.queueMix(queueItem); 8468 } 8469 }, 8470 8471 /** 8472 * @private 8473 * @instance 8474 * @since 3.0.0 8475 * @param {object} multimixCommand 8476 * @param {boolean} [isPreFetch] 8477 * An optional boolean indicating that the operation is being pre-fetched for execution at a later time. 8478 * @return {Operation|null} 8479 */ 8480 8481 getOperation: function(multimixCommand) { 8482 var self = this, 8483 sortCommand = multimixCommand.sort, 8484 filterCommand = multimixCommand.filter, 8485 changeLayoutCommand = multimixCommand.changeLayout, 8486 removeCommand = multimixCommand.remove, 8487 insertCommand = multimixCommand.insert, 8488 operation = new mixitup.Operation(); 8489 8490 operation = self.callFilters('operationUnmappedGetOperation', operation, arguments); 8491 8492 operation.id = h.randomHex(); 8493 operation.command = multimixCommand; 8494 operation.startState = self.state; 8495 operation.triggerElement = self.lastClicked; 8496 8497 if (self.isBusy) { 8498 if (self.config.debug.showWarnings) { 8499 console.warn(mixitup.messages.warningGetOperationInstanceBusy()); 8500 } 8501 8502 return null; 8503 } 8504 8505 if (insertCommand) { 8506 self.insertTargets(insertCommand, operation); 8507 } 8508 8509 if (removeCommand) { 8510 operation.toRemove = removeCommand.targets; 8511 } 8512 8513 operation.startSort = operation.newSort = operation.startState.activeSort; 8514 operation.startOrder = operation.newOrder = self.targets; 8515 8516 if (sortCommand) { 8517 operation.startSort = operation.startState.activeSort; 8518 operation.newSort = sortCommand; 8519 8520 operation.willSort = self.willSort(sortCommand, operation.startState.activeSort); 8521 8522 if (operation.willSort) { 8523 self.sortOperation(operation); 8524 } 8525 } 8526 8527 operation.startFilter = operation.startState.activeFilter; 8528 8529 if (filterCommand) { 8530 operation.newFilter = filterCommand; 8531 } else { 8532 operation.newFilter = h.extend(new mixitup.CommandFilter(), operation.startFilter); 8533 } 8534 8535 if (operation.newFilter.selector === 'all') { 8536 operation.newFilter.selector = self.config.selectors.target; 8537 } else if (operation.newFilter.selector === 'none') { 8538 operation.newFilter.selector = ''; 8539 } 8540 8541 self.filterOperation(operation); 8542 8543 operation.startContainerClassName = operation.startState.activeContainerClassName; 8544 8545 if (changeLayoutCommand) { 8546 operation.newContainerClassName = changeLayoutCommand.containerClassName; 8547 8548 if (operation.newContainerClassName !== operation.startContainerClassName) { 8549 operation.willChangeLayout = true; 8550 } 8551 } else { 8552 operation.newContainerClassName = operation.startContainerClassName; 8553 } 8554 8555 if (self.config.animation.enable) { 8556 // Populate the operation's position data 8557 8558 self.getStartMixData(operation); 8559 self.setInter(operation); 8560 8561 operation.docState = h.getDocumentState(self.dom.document); 8562 8563 self.getInterMixData(operation); 8564 self.setFinal(operation); 8565 self.getFinalMixData(operation); 8566 8567 self.parseEffects(); 8568 8569 operation.hasEffect = self.hasEffect(); 8570 8571 self.getTweenData(operation); 8572 } 8573 8574 if (operation.willSort) { 8575 self.targets = operation.newOrder; 8576 } 8577 8578 operation.newState = self.buildState(operation); 8579 8580 return self.callFilters('operationMappedGetOperation', operation, arguments); 8581 }, 8582 8583 /** 8584 * Renders a previously created operation at a specific point in its path, as 8585 * determined by a multiplier between 0 and 1. 8586 * 8587 * @example 8588 * .tween(operation, multiplier) 8589 * 8590 * @private 8591 * @instance 8592 * @since 3.0.0 8593 * @param {mixitup.Operation} operation 8594 * An operation object created via the `getOperation` method 8595 * 8596 * @param {Float} multiplier 8597 * Any number between 0 and 1 representing the percentage complete of the operation 8598 * @return {void} 8599 */ 8600 8601 tween: function(operation, multiplier) { 8602 var target = null, 8603 posData = null, 8604 toHideIndex = -1, 8605 i = -1; 8606 8607 multiplier = Math.min(multiplier, 1); 8608 multiplier = Math.max(multiplier, 0); 8609 8610 for (i = 0; target = operation.show[i]; i++) { 8611 posData = operation.showPosData[i]; 8612 8613 target.applyTween(posData, multiplier); 8614 } 8615 8616 for (i = 0; target = operation.hide[i]; i++) { 8617 if (target.isShown) { 8618 target.hide(); 8619 } 8620 8621 if ((toHideIndex = operation.toHide.indexOf(target)) > -1) { 8622 posData = operation.toHidePosData[toHideIndex]; 8623 8624 if (!target.isShown) { 8625 target.show(); 8626 } 8627 8628 target.applyTween(posData, multiplier); 8629 } 8630 } 8631 }, 8632 8633 /** 8634 * Inserts one or more new target elements into the container at a specified 8635 * index. 8636 * 8637 * To be indexed as targets, new elements must match the `selectors.target` 8638 * selector (`'.mix'` by default). 8639 * 8640 * @example 8641 * 8642 * .insert(newElements [, index] [, animate], [, callback]) 8643 * 8644 * @example <caption>Example 1: Inserting a single element via reference</caption> 8645 * 8646 * console.log(mixer.getState().totalShow); // 0 8647 * 8648 * // Create a new element 8649 * 8650 * var newElement = document.createElement('div'); 8651 * newElement.classList.add('mix'); 8652 * 8653 * mixer.insert(newElement) 8654 * .then(function(state) { 8655 * console.log(state.totalShow === 1); // true 8656 * }); 8657 * 8658 * @example <caption>Example 2: Inserting a single element via HTML string</caption> 8659 * 8660 * console.log(mixer.getState().totalShow); // 1 8661 * 8662 * // Create a new element via reference 8663 * 8664 * var newElementHtml = '<div class="mix"></div>'; 8665 * 8666 * // Create and insert the new element at index 1 8667 * 8668 * mixer.insert(newElementHtml, 1) 8669 * .then(function(state) { 8670 * console.log(state.totalShow === 2); // true 8671 * console.log(state.show[1].outerHTML === newElementHtml); // true 8672 * }); 8673 * 8674 * @example <caption>Example 3: Inserting multiple elements via reference</caption> 8675 * 8676 * console.log(mixer.getState().totalShow); // 2 8677 * 8678 * // Create an array of new elements to insert. 8679 * 8680 * var newElement1 = document.createElement('div'); 8681 * var newElement2 = document.createElement('div'); 8682 * 8683 * newElement1.classList.add('mix'); 8684 * newElement2.classList.add('mix'); 8685 * 8686 * var newElementsCollection = [newElement1, newElement2]; 8687 * 8688 * // Insert the new elements starting at index 1 8689 * 8690 * mixer.insert(newElementsCollection, 1) 8691 * .then(function(state) { 8692 * console.log(state.totalShow === 4); // true 8693 * console.log(state.show[1] === newElement1); // true 8694 * console.log(state.show[2] === newElement2); // true 8695 * }); 8696 * 8697 * @example <caption>Example 4: Inserting a jQuery collection object containing one or more elements</caption> 8698 * 8699 * console.log(mixer.getState().totalShow); // 4 8700 * 8701 * var $newElement = $('<div class="mix"></div>'); 8702 * 8703 * // Insert the new elements starting at index 3 8704 * 8705 * mixer.insert($newElement, 3) 8706 * .then(function(state) { 8707 * console.log(state.totalShow === 5); // true 8708 * console.log(state.show[3] === $newElement[0]); // true 8709 * }); 8710 * 8711 * @public 8712 * @instance 8713 * @since 2.0.0 8714 * @param {(HTMLElement|Array.<HTMLElement>|string)} newElements 8715 * A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element. 8716 * @param {number} index=0 8717 * The index at which to insert the new element(s). `0` by default. 8718 * @param {boolean} [animate=true] 8719 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8720 * @param {function} [callback=null] 8721 * An optional callback function to be invoked after the operation has completed. 8722 * @return {Promise.<mixitup.State>} 8723 * A promise resolving with the current state object. 8724 */ 8725 8726 insert: function() { 8727 var self = this, 8728 args = self.parseInsertArgs(arguments); 8729 8730 return self.multimix({ 8731 insert: args.command 8732 }, args.animate, args.callback); 8733 }, 8734 8735 /** 8736 * Inserts one or more new elements before a provided reference element. 8737 * 8738 * @example 8739 * 8740 * .insertBefore(newElements, referenceElement [, animate] [, callback]) 8741 * 8742 * @example <caption>Example: Inserting a new element before a reference element</caption> 8743 * 8744 * // An existing reference element is chosen at index 2 8745 * 8746 * var referenceElement = mixer.getState().show[2]; 8747 * 8748 * // Create a new element 8749 * 8750 * var newElement = document.createElement('div'); 8751 * newElement.classList.add('mix'); 8752 * 8753 * mixer.insertBefore(newElement, referenceElement) 8754 * .then(function(state) { 8755 * // The new element is inserted into the container at index 2, before the reference element 8756 * 8757 * console.log(state.show[2] === newElement); // true 8758 * 8759 * // The reference element is now at index 3 8760 * 8761 * console.log(state.show[3] === referenceElement); // true 8762 * }); 8763 * 8764 * @public 8765 * @instance 8766 * @since 3.0.0 8767 * @param {(HTMLElement|Array.<HTMLElement>|string)} newElements 8768 * A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element. 8769 * @param {HTMLElement} referenceElement 8770 * A reference to an existing element in the container to insert new elements before. 8771 *@param {boolean} [animate=true] 8772 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8773 * @param {function} [callback=null] 8774 * An optional callback function to be invoked after the operation has completed. 8775 * @return {Promise.<mixitup.State>} 8776 * A promise resolving with the current state object. 8777 */ 8778 8779 insertBefore: function() { 8780 var self = this, 8781 args = self.parseInsertArgs(arguments); 8782 8783 return self.insert(args.command.collection, 'before', args.command.sibling, args.animate, args.callback); 8784 }, 8785 8786 /** 8787 * Inserts one or more new elements after a provided reference element. 8788 * 8789 * @example 8790 * 8791 * .insertAfter(newElements, referenceElement [, animate] [, callback]) 8792 * 8793 * @example <caption>Example: Inserting a new element after a reference element</caption> 8794 * 8795 * // An existing reference element is chosen at index 2 8796 * 8797 * var referenceElement = mixer.getState().show[2]; 8798 * 8799 * // Create a new element 8800 * 8801 * var newElement = document.createElement('div'); 8802 * newElement.classList.add('mix'); 8803 * 8804 * mixer.insertAfter(newElement, referenceElement) 8805 * .then(function(state) { 8806 * // The new element is inserted into the container at index 3, after the reference element 8807 * 8808 * console.log(state.show[3] === newElement); // true 8809 * }); 8810 * 8811 * @public 8812 * @instance 8813 * @since 3.0.0 8814 * @param {(HTMLElement|Array.<HTMLElement>|string)} newElements 8815 * A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element. 8816 * @param {HTMLElement} referenceElement 8817 * A reference to an existing element in the container to insert new elements after. 8818 * @param {boolean} [animate=true] 8819 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8820 * @param {function} [callback=null] 8821 * An optional callback function to be invoked after the operation has completed. 8822 * @return {Promise.<mixitup.State>} 8823 * A promise resolving with the current state object. 8824 */ 8825 8826 insertAfter: function() { 8827 var self = this, 8828 args = self.parseInsertArgs(arguments); 8829 8830 return self.insert(args.command.collection, 'after', args.command.sibling, args.animate, args.callback); 8831 }, 8832 8833 /** 8834 * Inserts one or more new elements into the container before all existing targets. 8835 * 8836 * @example 8837 * 8838 * .prepend(newElements [,animate] [,callback]) 8839 * 8840 * @example <caption>Example: Prepending a new element</caption> 8841 * 8842 * // Create a new element 8843 * 8844 * var newElement = document.createElement('div'); 8845 * newElement.classList.add('mix'); 8846 * 8847 * // Insert the element into the container 8848 * 8849 * mixer.prepend(newElement) 8850 * .then(function(state) { 8851 * console.log(state.show[0] === newElement); // true 8852 * }); 8853 * 8854 * @public 8855 * @instance 8856 * @since 3.0.0 8857 * @param {(HTMLElement|Array.<HTMLElement>|string)} newElements 8858 * A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element. 8859 * @param {boolean} [animate=true] 8860 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8861 * @param {function} [callback=null] 8862 * An optional callback function to be invoked after the operation has completed. 8863 * @return {Promise.<mixitup.State>} 8864 * A promise resolving with the current state object. 8865 */ 8866 8867 prepend: function() { 8868 var self = this, 8869 args = self.parseInsertArgs(arguments); 8870 8871 return self.insert(0, args.command.collection, args.animate, args.callback); 8872 }, 8873 8874 /** 8875 * Inserts one or more new elements into the container after all existing targets. 8876 * 8877 * @example 8878 * 8879 * .append(newElements [,animate] [,callback]) 8880 * 8881 * @example <caption>Example: Appending a new element</caption> 8882 * 8883 * // Create a new element 8884 * 8885 * var newElement = document.createElement('div'); 8886 * newElement.classList.add('mix'); 8887 * 8888 * // Insert the element into the container 8889 * 8890 * mixer.append(newElement) 8891 * .then(function(state) { 8892 * console.log(state.show[state.show.length - 1] === newElement); // true 8893 * }); 8894 * 8895 * @public 8896 * @instance 8897 * @since 3.0.0 8898 * @param {(HTMLElement|Array.<HTMLElement>|string)} newElements 8899 * A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element. 8900 * @param {boolean} [animate=true] 8901 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8902 * @param {function} [callback=null] 8903 * An optional callback function to be invoked after the operation has completed. 8904 * @return {Promise.<mixitup.State>} 8905 * A promise resolving with the current state object. 8906 */ 8907 8908 append: function() { 8909 var self = this, 8910 args = self.parseInsertArgs(arguments); 8911 8912 return self.insert(self.state.totalTargets, args.command.collection, args.animate, args.callback); 8913 }, 8914 8915 /** 8916 * Removes one or more existing target elements from the container. 8917 * 8918 * @example 8919 * 8920 * .remove(elements [, animate] [, callback]) 8921 * 8922 * @example <caption>Example 1: Removing an element by reference</caption> 8923 * 8924 * var elementToRemove = containerEl.firstElementChild; 8925 * 8926 * mixer.remove(elementToRemove) 8927 * .then(function(state) { 8928 * console.log(state.targets.indexOf(elementToRemove) === -1); // true 8929 * }); 8930 * 8931 * @example <caption>Example 2: Removing a collection of elements by reference</caption> 8932 * 8933 * var elementsToRemove = containerEl.querySelectorAll('.category-a'); 8934 * 8935 * console.log(elementsToRemove.length) // 3 8936 * 8937 * mixer.remove(elementsToRemove) 8938 * .then(function() { 8939 * console.log(containerEl.querySelectorAll('.category-a').length); // 0 8940 * }); 8941 * 8942 * @example <caption>Example 3: Removing one or more elements by selector</caption> 8943 * 8944 * mixer.remove('.category-a') 8945 * .then(function() { 8946 * console.log(containerEl.querySelectorAll('.category-a').length); // 0 8947 * }); 8948 * 8949 * @example <caption>Example 4: Removing an element by index</caption> 8950 * 8951 * console.log(mixer.getState.totalShow); // 4 8952 * 8953 * // Remove the element at index 3 8954 * 8955 * mixer.remove(3) 8956 * .then(function(state) { 8957 * console.log(state.totalShow); // 3 8958 * console.log(state.show[3]); // undefined 8959 * }); 8960 * 8961 * 8962 * @public 8963 * @instance 8964 * @since 3.0.0 8965 * @param {(HTMLElement|Array.<HTMLElement>|string|number)} elements 8966 * A reference to a single element to remove, an array-like collection of elements, a selector string, or the index of an element to remove. 8967 * @param {boolean} [animate=true] 8968 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 8969 * @param {function} [callback=null] 8970 * An optional callback function to be invoked after the operation has completed. 8971 * @return {Promise.<mixitup.State>} 8972 * A promise resolving with the current state object. 8973 */ 8974 8975 remove: function() { 8976 var self = this, 8977 args = self.parseRemoveArgs(arguments); 8978 8979 return self.multimix({ 8980 remove: args.command 8981 }, args.animate, args.callback); 8982 }, 8983 8984 /** 8985 * Retrieves the the value of any property or sub-object within the current 8986 * mixitup configuration, or the whole configuration object. 8987 * 8988 * @example 8989 * 8990 * .getConfig([stringKey]) 8991 * 8992 * @example <caption>Example 1: retrieve the entire configuration object</caption> 8993 * 8994 * var config = mixer.getConfig(); // Config { ... } 8995 * 8996 * @example <caption>Example 2: retrieve a named sub-object of configuration object</caption> 8997 * 8998 * var animation = mixer.getConfig('animation'); // ConfigAnimation { ... } 8999 * 9000 * @example <caption>Example 3: retrieve a value of configuration object via a dot-notation string key</caption> 9001 * 9002 * var effects = mixer.getConfig('animation.effects'); // 'fade scale' 9003 * 9004 * @public 9005 * @instance 9006 * @since 2.0.0 9007 * @param {string} [stringKey] A "dot-notation" string key 9008 * @return {*} 9009 */ 9010 9011 getConfig: function(stringKey) { 9012 var self = this, 9013 value = null; 9014 9015 if (!stringKey) { 9016 value = self.config; 9017 } else { 9018 value = h.getProperty(self.config, stringKey); 9019 } 9020 9021 return self.callFilters('valueGetConfig', value, arguments); 9022 }, 9023 9024 /** 9025 * Updates the configuration of the mixer, after it has been instantiated. 9026 * 9027 * See the Configuration Object documentation for a full list of avilable 9028 * configuration options. 9029 * 9030 * @example 9031 * 9032 * .configure(config) 9033 * 9034 * @example <caption>Example 1: Updating animation options</caption> 9035 * 9036 * mixer.configure({ 9037 * animation: { 9038 * effects: 'fade translateX(-100%)', 9039 * duration: 300 9040 * } 9041 * }); 9042 * 9043 * @example <caption>Example 2: Removing a callback after it has been set</caption> 9044 * 9045 * var mixer; 9046 * 9047 * function handleMixEndOnce() { 9048 * // Do something .. 9049 * 9050 * // Then nullify the callback 9051 * 9052 * mixer.configure({ 9053 * callbacks: { 9054 * onMixEnd: null 9055 * } 9056 * }); 9057 * }; 9058 * 9059 * // Instantiate a mixer with a callback defined 9060 * 9061 * mixer = mixitup(containerEl, { 9062 * callbacks: { 9063 * onMixEnd: handleMixEndOnce 9064 * } 9065 * }); 9066 * 9067 * @public 9068 * @instance 9069 * @since 3.0.0 9070 * @param {object} config 9071 * An object containing one of more configuration options. 9072 * @return {void} 9073 */ 9074 9075 configure: function(config) { 9076 var self = this; 9077 9078 self.callActions('beforeConfigure', arguments); 9079 9080 h.extend(self.config, config, true, true); 9081 9082 self.callActions('afterConfigure', arguments); 9083 }, 9084 9085 /** 9086 * Returns an object containing information about the current state of the 9087 * mixer. See the State Object documentation for more information. 9088 * 9089 * NB: State objects are immutable and should therefore be regenerated 9090 * after any operation. 9091 * 9092 * @example 9093 * 9094 * .getState(); 9095 * 9096 * @example <caption>Example: Retrieving a state object</caption> 9097 * 9098 * var state = mixer.getState(); 9099 * 9100 * console.log(state.totalShow + 'targets are currently shown'); 9101 * 9102 * @public 9103 * @instance 9104 * @since 2.0.0 9105 * @return {mixitup.State} An object reflecting the current state of the mixer. 9106 */ 9107 9108 getState: function() { 9109 var self = this, 9110 state = null; 9111 9112 state = new mixitup.State(); 9113 9114 h.extend(state, self.state); 9115 9116 h.freeze(state); 9117 9118 return self.callFilters('stateGetState', state, arguments); 9119 }, 9120 9121 /** 9122 * Forces the re-indexing all targets within the container. 9123 * 9124 * This should only be used if some other piece of code in your application 9125 * has manipulated the contents of your container, which should be avoided. 9126 * 9127 * If you need to add or remove target elements from the container, use 9128 * the built-in `.insert()` or `.remove()` methods, and MixItUp will keep 9129 * itself up to date. 9130 * 9131 * @example 9132 * 9133 * .forceRefresh() 9134 * 9135 * @example <caption>Example: Force refreshing the mixer after external DOM manipulation</caption> 9136 * 9137 * console.log(mixer.getState().totalShow); // 3 9138 * 9139 * // An element is removed from the container via some external DOM manipulation code: 9140 * 9141 * containerEl.removeChild(containerEl.firstElementChild); 9142 * 9143 * // The mixer does not know that the number of targets has changed: 9144 * 9145 * console.log(mixer.getState().totalShow); // 3 9146 * 9147 * mixer.forceRefresh(); 9148 * 9149 * // After forceRefresh, the mixer is in sync again: 9150 * 9151 * console.log(mixer.getState().totalShow); // 2 9152 * 9153 * @public 9154 * @instance 9155 * @since 2.1.2 9156 * @return {void} 9157 */ 9158 9159 forceRefresh: function() { 9160 var self = this; 9161 9162 self.indexTargets(); 9163 }, 9164 9165 /** 9166 * Forces the re-rendering of all targets when using the Dataset API. 9167 * 9168 * By default, targets are only re-rendered when `data.dirtyCheck` is 9169 * enabled, and an item's data has changed when `dataset()` is called. 9170 * 9171 * The `forceRender()` method allows for the re-rendering of all targets 9172 * in response to some arbitrary event, such as the changing of the target 9173 * render function. 9174 * 9175 * Targets are rendered against their existing data. 9176 * 9177 * @example 9178 * 9179 * .forceRender() 9180 * 9181 * @example <caption>Example: Force render targets after changing the target render function</caption> 9182 * 9183 * console.log(container.innerHTML); // ... <span class="mix">Foo</span> ... 9184 * 9185 * mixer.configure({ 9186 * render: { 9187 * target: (item) => `<a href="/${item.slug}/" class="mix">${item.title}</a>` 9188 * } 9189 * }); 9190 * 9191 * mixer.forceRender(); 9192 * 9193 * console.log(container.innerHTML); // ... <a href="/foo/" class="mix">Foo</a> ... 9194 * 9195 * @public 9196 * @instance 9197 * @since 3.2.1 9198 * @return {void} 9199 */ 9200 9201 forceRender: function() { 9202 var self = this, 9203 target = null, 9204 el = null, 9205 id = ''; 9206 9207 for (id in self.cache) { 9208 target = self.cache[id]; 9209 9210 el = target.render(target.data); 9211 9212 if (el !== target.dom.el) { 9213 // Update target element reference 9214 9215 if (target.isInDom) { 9216 target.unbindEvents(); 9217 9218 self.dom.parent.replaceChild(el, target.dom.el); 9219 } 9220 9221 if (!target.isShown) { 9222 el.style.display = 'none'; 9223 } 9224 9225 target.dom.el = el; 9226 9227 if (target.isInDom) { 9228 target.bindEvents(); 9229 } 9230 } 9231 } 9232 9233 self.state = self.buildState(self.lastOperation); 9234 }, 9235 9236 /** 9237 * Removes mixitup functionality from the container, unbinds all control 9238 * event handlers, and deletes the mixer instance from MixItUp's internal 9239 * cache. 9240 * 9241 * This should be performed whenever a mixer's container is removed from 9242 * the DOM, such as during a page change in a single page application, 9243 * or React's `componentWillUnmount()`. 9244 * 9245 * @example 9246 * 9247 * .destroy([cleanUp]) 9248 * 9249 * @example <caption>Example: Destroying the mixer before removing its container element</caption> 9250 * 9251 * mixer.destroy(); 9252 * 9253 * containerEl.parentElement.removeChild(containerEl); 9254 * 9255 * @public 9256 * @instance 9257 * @since 2.0.0 9258 * @param {boolean} [cleanUp=false] 9259 * An optional boolean dictating whether or not to clean up any inline `display: none;` styling applied to hidden targets. 9260 * @return {void} 9261 */ 9262 9263 destroy: function(cleanUp) { 9264 var self = this, 9265 control = null, 9266 target = null, 9267 i = 0; 9268 9269 self.callActions('beforeDestroy', arguments); 9270 9271 for (i = 0; control = self.controls[i]; i++) { 9272 control.removeBinding(self); 9273 } 9274 9275 for (i = 0; target = self.targets[i]; i++) { 9276 if (cleanUp) { 9277 target.show(); 9278 } 9279 9280 target.unbindEvents(); 9281 } 9282 9283 if (self.dom.container.id.match(/^MixItUp/)) { 9284 self.dom.container.removeAttribute('id'); 9285 } 9286 9287 delete mixitup.instances[self.id]; 9288 9289 self.callActions('afterDestroy', arguments); 9290 } 9291 }); 9292 9293 /** 9294 * @constructor 9295 * @memberof mixitup 9296 * @private 9297 * @since 3.0.0 9298 */ 9299 9300 mixitup.IMoveData = function() { 9301 mixitup.Base.call(this); 9302 9303 this.callActions('beforeConstruct'); 9304 9305 this.posIn = null; 9306 this.posOut = null; 9307 this.operation = null; 9308 this.callback = null; 9309 this.statusChange = ''; 9310 this.duration = -1; 9311 this.staggerIndex = -1; 9312 9313 this.callActions('afterConstruct'); 9314 9315 h.seal(this); 9316 }; 9317 9318 mixitup.BaseStatic.call(mixitup.IMoveData); 9319 9320 mixitup.IMoveData.prototype = Object.create(mixitup.Base.prototype); 9321 9322 mixitup.IMoveData.prototype.constructor = mixitup.IMoveData; 9323 9324 /** 9325 * @constructor 9326 * @memberof mixitup 9327 * @private 9328 * @since 3.0.0 9329 */ 9330 9331 mixitup.TargetDom = function() { 9332 mixitup.Base.call(this); 9333 9334 this.callActions('beforeConstruct'); 9335 9336 this.el = null; 9337 9338 this.callActions('afterConstruct'); 9339 9340 h.seal(this); 9341 }; 9342 9343 mixitup.BaseStatic.call(mixitup.TargetDom); 9344 9345 mixitup.TargetDom.prototype = Object.create(mixitup.Base.prototype); 9346 9347 mixitup.TargetDom.prototype.constructor = mixitup.TargetDom; 9348 9349 /** 9350 * @constructor 9351 * @namespace 9352 * @memberof mixitup 9353 * @private 9354 * @since 3.0.0 9355 */ 9356 9357 mixitup.Target = function() { 9358 mixitup.Base.call(this); 9359 9360 this.callActions('beforeConstruct'); 9361 9362 this.id = ''; 9363 this.sortString = ''; 9364 this.mixer = null; 9365 this.callback = null; 9366 this.isShown = false; 9367 this.isBound = false; 9368 this.isExcluded = false; 9369 this.isInDom = false; 9370 this.handler = null; 9371 this.operation = null; 9372 this.data = null; 9373 this.dom = new mixitup.TargetDom(); 9374 9375 this.callActions('afterConstruct'); 9376 9377 h.seal(this); 9378 }; 9379 9380 mixitup.BaseStatic.call(mixitup.Target); 9381 9382 mixitup.Target.prototype = Object.create(mixitup.Base.prototype); 9383 9384 h.extend(mixitup.Target.prototype, { 9385 constructor: mixitup.Target, 9386 9387 /** 9388 * Initialises a newly instantiated Target. 9389 * 9390 * @private 9391 * @instance 9392 * @since 3.0.0 9393 * @param {(Element|null)} el 9394 * @param {object} mixer 9395 * @param {object} [data] 9396 * @return {void} 9397 */ 9398 9399 init: function(el, mixer, data) { 9400 var self = this, 9401 id = ''; 9402 9403 self.callActions('beforeInit', arguments); 9404 9405 self.mixer = mixer; 9406 9407 if (!el) { 9408 // If no element is provided, render it 9409 9410 el = self.render(data); 9411 } 9412 9413 self.cacheDom(el); 9414 9415 self.bindEvents(); 9416 9417 if (self.dom.el.style.display !== 'none') { 9418 self.isShown = true; 9419 } 9420 9421 if (data && mixer.config.data.uidKey) { 9422 if (typeof (id = data[mixer.config.data.uidKey]) === 'undefined' || id.toString().length < 1) { 9423 throw new TypeError(mixitup.messages.errorDatasetInvalidUidKey({ 9424 uidKey: mixer.config.data.uidKey 9425 })); 9426 } 9427 9428 self.id = id; 9429 self.data = data; 9430 9431 mixer.cache[id] = self; 9432 } 9433 9434 self.callActions('afterInit', arguments); 9435 }, 9436 9437 /** 9438 * Renders the target element using a user-defined renderer function. 9439 * 9440 * @private 9441 * @instance 9442 * @since 3.1.4 9443 * @param {object} data 9444 * @return {void} 9445 */ 9446 9447 render: function(data) { 9448 var self = this, 9449 render = null, 9450 el = null, 9451 temp = null, 9452 output = ''; 9453 9454 self.callActions('beforeRender', arguments); 9455 9456 render = self.callFilters('renderRender', self.mixer.config.render.target, arguments); 9457 9458 if (typeof render !== 'function') { 9459 throw new TypeError(mixitup.messages.errorDatasetRendererNotSet()); 9460 } 9461 9462 output = render(data); 9463 9464 if (output && typeof output === 'object' && h.isElement(output)) { 9465 el = output; 9466 } else if (typeof output === 'string') { 9467 temp = document.createElement('div'); 9468 temp.innerHTML = output; 9469 9470 el = temp.firstElementChild; 9471 } 9472 9473 return self.callFilters('elRender', el, arguments); 9474 }, 9475 9476 /** 9477 * Caches references of DOM elements neccessary for the target's functionality. 9478 * 9479 * @private 9480 * @instance 9481 * @since 3.0.0 9482 * @param {Element} el 9483 * @return {void} 9484 */ 9485 9486 cacheDom: function(el) { 9487 var self = this; 9488 9489 self.callActions('beforeCacheDom', arguments); 9490 9491 self.dom.el = el; 9492 9493 self.callActions('afterCacheDom', arguments); 9494 }, 9495 9496 /** 9497 * @private 9498 * @instance 9499 * @since 3.0.0 9500 * @param {string} attributeName 9501 * @return {void} 9502 */ 9503 9504 getSortString: function(attributeName) { 9505 var self = this, 9506 value = self.dom.el.getAttribute('data-' + attributeName) || ''; 9507 9508 self.callActions('beforeGetSortString', arguments); 9509 9510 value = isNaN(value * 1) ? 9511 value.toLowerCase() : 9512 value * 1; 9513 9514 self.sortString = value; 9515 9516 self.callActions('afterGetSortString', arguments); 9517 }, 9518 9519 /** 9520 * @private 9521 * @instance 9522 * @since 3.0.0 9523 * @return {void} 9524 */ 9525 9526 show: function() { 9527 var self = this; 9528 9529 self.callActions('beforeShow', arguments); 9530 9531 if (!self.isShown) { 9532 self.dom.el.style.display = ''; 9533 9534 self.isShown = true; 9535 } 9536 9537 self.callActions('afterShow', arguments); 9538 }, 9539 9540 /** 9541 * @private 9542 * @instance 9543 * @since 3.0.0 9544 * @return {void} 9545 */ 9546 9547 hide: function() { 9548 var self = this; 9549 9550 self.callActions('beforeHide', arguments); 9551 9552 if (self.isShown) { 9553 self.dom.el.style.display = 'none'; 9554 9555 self.isShown = false; 9556 } 9557 9558 self.callActions('afterHide', arguments); 9559 }, 9560 9561 /** 9562 * @private 9563 * @instance 9564 * @since 3.0.0 9565 * @param {mixitup.IMoveData} moveData 9566 * @return {void} 9567 */ 9568 9569 move: function(moveData) { 9570 var self = this; 9571 9572 self.callActions('beforeMove', arguments); 9573 9574 if (!self.isExcluded) { 9575 self.mixer.targetsMoved++; 9576 } 9577 9578 self.applyStylesIn(moveData); 9579 9580 requestAnimationFrame(function() { 9581 self.applyStylesOut(moveData); 9582 }); 9583 9584 self.callActions('afterMove', arguments); 9585 }, 9586 9587 /** 9588 * @private 9589 * @instance 9590 * @since 3.0.0 9591 * @param {object} posData 9592 * @param {number} multiplier 9593 * @return {void} 9594 */ 9595 9596 applyTween: function(posData, multiplier) { 9597 var self = this, 9598 propertyName = '', 9599 tweenData = null, 9600 posIn = posData.posIn, 9601 currentTransformValues = [], 9602 currentValues = new mixitup.StyleData(), 9603 i = -1; 9604 9605 self.callActions('beforeApplyTween', arguments); 9606 9607 currentValues.x = posIn.x; 9608 currentValues.y = posIn.y; 9609 9610 if (multiplier === 0) { 9611 self.hide(); 9612 } else if (!self.isShown) { 9613 self.show(); 9614 } 9615 9616 for (i = 0; propertyName = mixitup.features.TWEENABLE[i]; i++) { 9617 tweenData = posData.tweenData[propertyName]; 9618 9619 if (propertyName === 'x') { 9620 if (!tweenData) continue; 9621 9622 currentValues.x = posIn.x + (tweenData * multiplier); 9623 } else if (propertyName === 'y') { 9624 if (!tweenData) continue; 9625 9626 currentValues.y = posIn.y + (tweenData * multiplier); 9627 } else if (tweenData instanceof mixitup.TransformData) { 9628 if (!tweenData.value) continue; 9629 9630 currentValues[propertyName].value = 9631 posIn[propertyName].value + (tweenData.value * multiplier); 9632 9633 currentValues[propertyName].unit = tweenData.unit; 9634 9635 currentTransformValues.push( 9636 propertyName + '(' + currentValues[propertyName].value + tweenData.unit + ')' 9637 ); 9638 } else { 9639 if (!tweenData) continue; 9640 9641 currentValues[propertyName] = posIn[propertyName] + (tweenData * multiplier); 9642 9643 self.dom.el.style[propertyName] = currentValues[propertyName]; 9644 } 9645 } 9646 9647 if (currentValues.x || currentValues.y) { 9648 currentTransformValues.unshift('translate(' + currentValues.x + 'px, ' + currentValues.y + 'px)'); 9649 } 9650 9651 if (currentTransformValues.length) { 9652 self.dom.el.style[mixitup.features.transformProp] = currentTransformValues.join(' '); 9653 } 9654 9655 self.callActions('afterApplyTween', arguments); 9656 }, 9657 9658 /** 9659 * Applies the initial styling to a target element before any transition 9660 * is applied. 9661 * 9662 * @private 9663 * @instance 9664 * @param {mixitup.IMoveData} moveData 9665 * @return {void} 9666 */ 9667 9668 applyStylesIn: function(moveData) { 9669 var self = this, 9670 posIn = moveData.posIn, 9671 isFading = self.mixer.effectsIn.opacity !== 1, 9672 transformValues = []; 9673 9674 self.callActions('beforeApplyStylesIn', arguments); 9675 9676 transformValues.push('translate(' + posIn.x + 'px, ' + posIn.y + 'px)'); 9677 9678 if (self.mixer.config.animation.animateResizeTargets) { 9679 if (moveData.statusChange !== 'show') { 9680 // Don't apply posIn width or height or showing, as will be 0 9681 9682 self.dom.el.style.width = posIn.width + 'px'; 9683 self.dom.el.style.height = posIn.height + 'px'; 9684 } 9685 9686 self.dom.el.style.marginRight = posIn.marginRight + 'px'; 9687 self.dom.el.style.marginBottom = posIn.marginBottom + 'px'; 9688 } 9689 9690 isFading && (self.dom.el.style.opacity = posIn.opacity); 9691 9692 if (moveData.statusChange === 'show') { 9693 transformValues = transformValues.concat(self.mixer.transformIn); 9694 } 9695 9696 self.dom.el.style[mixitup.features.transformProp] = transformValues.join(' '); 9697 9698 self.callActions('afterApplyStylesIn', arguments); 9699 }, 9700 9701 /** 9702 * Applies a transition followed by the final styles for the element to 9703 * transition towards. 9704 * 9705 * @private 9706 * @instance 9707 * @param {mixitup.IMoveData} moveData 9708 * @return {void} 9709 */ 9710 9711 applyStylesOut: function(moveData) { 9712 var self = this, 9713 transitionRules = [], 9714 transformValues = [], 9715 isResizing = self.mixer.config.animation.animateResizeTargets, 9716 isFading = typeof self.mixer.effectsIn.opacity !== 'undefined'; 9717 9718 self.callActions('beforeApplyStylesOut', arguments); 9719 9720 // Build the transition rules 9721 9722 transitionRules.push(self.writeTransitionRule( 9723 mixitup.features.transformRule, 9724 moveData.staggerIndex 9725 )); 9726 9727 if (moveData.statusChange !== 'none') { 9728 transitionRules.push(self.writeTransitionRule( 9729 'opacity', 9730 moveData.staggerIndex, 9731 moveData.duration 9732 )); 9733 } 9734 9735 if (isResizing) { 9736 transitionRules.push(self.writeTransitionRule( 9737 'width', 9738 moveData.staggerIndex, 9739 moveData.duration 9740 )); 9741 9742 transitionRules.push(self.writeTransitionRule( 9743 'height', 9744 moveData.staggerIndex, 9745 moveData.duration 9746 )); 9747 9748 transitionRules.push(self.writeTransitionRule( 9749 'margin', 9750 moveData.staggerIndex, 9751 moveData.duration 9752 )); 9753 } 9754 9755 // If no callback was provided, the element will 9756 // not transition in any way so tag it as "immovable" 9757 9758 if (!moveData.callback) { 9759 self.mixer.targetsImmovable++; 9760 9761 if (self.mixer.targetsMoved === self.mixer.targetsImmovable) { 9762 // If the total targets moved is equal to the 9763 // number of immovable targets, the operation 9764 // should be considered finished 9765 9766 self.mixer.cleanUp(moveData.operation); 9767 } 9768 9769 return; 9770 } 9771 9772 // If the target will transition in some fasion, 9773 // assign a callback function 9774 9775 self.operation = moveData.operation; 9776 self.callback = moveData.callback; 9777 9778 // As long as the target is not excluded, increment 9779 // the total number of targets bound 9780 9781 !self.isExcluded && self.mixer.targetsBound++; 9782 9783 // Tag the target as bound to differentiate from transitionEnd 9784 // events that may come from stylesheet driven effects 9785 9786 self.isBound = true; 9787 9788 // Apply the transition 9789 9790 self.applyTransition(transitionRules); 9791 9792 // Apply width, height and margin negation 9793 9794 if (isResizing && moveData.posOut.width > 0 && moveData.posOut.height > 0) { 9795 self.dom.el.style.width = moveData.posOut.width + 'px'; 9796 self.dom.el.style.height = moveData.posOut.height + 'px'; 9797 self.dom.el.style.marginRight = moveData.posOut.marginRight + 'px'; 9798 self.dom.el.style.marginBottom = moveData.posOut.marginBottom + 'px'; 9799 } 9800 9801 if (!self.mixer.config.animation.nudge && moveData.statusChange === 'hide') { 9802 // If we're not nudging, the translation should be 9803 // applied before any other transforms to prevent 9804 // lateral movement 9805 9806 transformValues.push('translate(' + moveData.posOut.x + 'px, ' + moveData.posOut.y + 'px)'); 9807 } 9808 9809 // Apply fade 9810 9811 switch (moveData.statusChange) { 9812 case 'hide': 9813 isFading && (self.dom.el.style.opacity = self.mixer.effectsOut.opacity); 9814 9815 transformValues = transformValues.concat(self.mixer.transformOut); 9816 9817 break; 9818 case 'show': 9819 isFading && (self.dom.el.style.opacity = 1); 9820 } 9821 9822 if ( 9823 self.mixer.config.animation.nudge || 9824 (!self.mixer.config.animation.nudge && moveData.statusChange !== 'hide') 9825 ) { 9826 // Opposite of above - apply translate after 9827 // other transform 9828 9829 transformValues.push('translate(' + moveData.posOut.x + 'px, ' + moveData.posOut.y + 'px)'); 9830 } 9831 9832 // Apply transforms 9833 9834 self.dom.el.style[mixitup.features.transformProp] = transformValues.join(' '); 9835 9836 self.callActions('afterApplyStylesOut', arguments); 9837 }, 9838 9839 /** 9840 * Combines the name of a CSS property with the appropriate duration and delay 9841 * values to created a valid transition rule. 9842 * 9843 * @private 9844 * @instance 9845 * @since 3.0.0 9846 * @param {string} property 9847 * @param {number} staggerIndex 9848 * @param {number} duration 9849 * @return {string} 9850 */ 9851 9852 writeTransitionRule: function(property, staggerIndex, duration) { 9853 var self = this, 9854 delay = self.getDelay(staggerIndex), 9855 rule = ''; 9856 9857 rule = property + ' ' + 9858 (duration > 0 ? duration : self.mixer.config.animation.duration) + 'ms ' + 9859 delay + 'ms ' + 9860 (property === 'opacity' ? 'linear' : self.mixer.config.animation.easing); 9861 9862 return self.callFilters('ruleWriteTransitionRule', rule, arguments); 9863 }, 9864 9865 /** 9866 * Calculates the transition delay for each target element based on its index, if 9867 * staggering is applied. If defined, A custom `animation.staggerSeqeuence` 9868 * function can be used to manipulate the order of indices to produce custom 9869 * stagger effects (e.g. for use in a grid with irregular row lengths). 9870 * 9871 * @private 9872 * @instance 9873 * @since 2.0.0 9874 * @param {number} index 9875 * @return {number} 9876 */ 9877 9878 getDelay: function(index) { 9879 var self = this, 9880 delay = -1; 9881 9882 if (typeof self.mixer.config.animation.staggerSequence === 'function') { 9883 index = self.mixer.config.animation.staggerSequence.call(self, index, self.state); 9884 } 9885 9886 delay = !!self.mixer.staggerDuration ? index * self.mixer.staggerDuration : 0; 9887 9888 return self.callFilters('delayGetDelay', delay, arguments); 9889 }, 9890 9891 /** 9892 * @private 9893 * @instance 9894 * @since 3.0.0 9895 * @param {string[]} rules 9896 * @return {void} 9897 */ 9898 9899 applyTransition: function(rules) { 9900 var self = this, 9901 transitionString = rules.join(', '); 9902 9903 self.callActions('beforeApplyTransition', arguments); 9904 9905 self.dom.el.style[mixitup.features.transitionProp] = transitionString; 9906 9907 self.callActions('afterApplyTransition', arguments); 9908 }, 9909 9910 /** 9911 * @private 9912 * @instance 9913 * @since 3.0.0 9914 * @param {Event} e 9915 * @return {void} 9916 */ 9917 9918 handleTransitionEnd: function(e) { 9919 var self = this, 9920 propName = e.propertyName, 9921 canResize = self.mixer.config.animation.animateResizeTargets; 9922 9923 self.callActions('beforeHandleTransitionEnd', arguments); 9924 9925 if ( 9926 self.isBound && 9927 e.target.matches(self.mixer.config.selectors.target) && 9928 ( 9929 propName.indexOf('transform') > -1 || 9930 propName.indexOf('opacity') > -1 || 9931 canResize && propName.indexOf('height') > -1 || 9932 canResize && propName.indexOf('width') > -1 || 9933 canResize && propName.indexOf('margin') > -1 9934 ) 9935 ) { 9936 self.callback.call(self, self.operation); 9937 9938 self.isBound = false; 9939 self.callback = null; 9940 self.operation = null; 9941 } 9942 9943 self.callActions('afterHandleTransitionEnd', arguments); 9944 }, 9945 9946 /** 9947 * @private 9948 * @instance 9949 * @since 3.0.0 9950 * @param {Event} e 9951 * @return {void} 9952 */ 9953 9954 eventBus: function(e) { 9955 var self = this; 9956 9957 self.callActions('beforeEventBus', arguments); 9958 9959 switch (e.type) { 9960 case 'webkitTransitionEnd': 9961 case 'transitionend': 9962 self.handleTransitionEnd(e); 9963 } 9964 9965 self.callActions('afterEventBus', arguments); 9966 }, 9967 9968 /** 9969 * @private 9970 * @instance 9971 * @since 3.0.0 9972 * @return {void} 9973 */ 9974 9975 unbindEvents: function() { 9976 var self = this; 9977 9978 self.callActions('beforeUnbindEvents', arguments); 9979 9980 h.off(self.dom.el, 'webkitTransitionEnd', self.handler); 9981 h.off(self.dom.el, 'transitionend', self.handler); 9982 9983 self.callActions('afterUnbindEvents', arguments); 9984 }, 9985 9986 /** 9987 * @private 9988 * @instance 9989 * @since 3.0.0 9990 * @return {void} 9991 */ 9992 9993 bindEvents: function() { 9994 var self = this, 9995 transitionEndEvent = ''; 9996 9997 self.callActions('beforeBindEvents', arguments); 9998 9999 transitionEndEvent = mixitup.features.transitionPrefix === 'webkit' ? 'webkitTransitionEnd' : 'transitionend'; 10000 10001 self.handler = function(e) { 10002 return self.eventBus(e); 10003 }; 10004 10005 h.on(self.dom.el, transitionEndEvent, self.handler); 10006 10007 self.callActions('afterBindEvents', arguments); 10008 }, 10009 10010 /** 10011 * @private 10012 * @instance 10013 * @since 3.0.0 10014 * @param {boolean} [getBox] 10015 * @return {PosData} 10016 */ 10017 10018 getPosData: function(getBox) { 10019 var self = this, 10020 styles = {}, 10021 rect = null, 10022 posData = new mixitup.StyleData(); 10023 10024 self.callActions('beforeGetPosData', arguments); 10025 10026 posData.x = self.dom.el.offsetLeft; 10027 posData.y = self.dom.el.offsetTop; 10028 10029 if (self.mixer.config.animation.animateResizeTargets || getBox) { 10030 rect = self.dom.el.getBoundingClientRect(); 10031 10032 posData.top = rect.top; 10033 posData.right = rect.right; 10034 posData.bottom = rect.bottom; 10035 posData.left = rect.left; 10036 10037 posData.width = rect.width; 10038 posData.height = rect.height; 10039 } 10040 10041 if (self.mixer.config.animation.animateResizeTargets) { 10042 styles = window.getComputedStyle(self.dom.el); 10043 10044 posData.marginBottom = parseFloat(styles.marginBottom); 10045 posData.marginRight = parseFloat(styles.marginRight); 10046 } 10047 10048 return self.callFilters('posDataGetPosData', posData, arguments); 10049 }, 10050 10051 /** 10052 * @private 10053 * @instance 10054 * @since 3.0.0 10055 * @return {void} 10056 */ 10057 10058 cleanUp: function() { 10059 var self = this; 10060 10061 self.callActions('beforeCleanUp', arguments); 10062 10063 self.dom.el.style[mixitup.features.transformProp] = ''; 10064 self.dom.el.style[mixitup.features.transitionProp] = ''; 10065 self.dom.el.style.opacity = ''; 10066 10067 if (self.mixer.config.animation.animateResizeTargets) { 10068 self.dom.el.style.width = ''; 10069 self.dom.el.style.height = ''; 10070 self.dom.el.style.marginRight = ''; 10071 self.dom.el.style.marginBottom = ''; 10072 } 10073 10074 self.callActions('afterCleanUp', arguments); 10075 } 10076 }); 10077 10078 /** 10079 * A jQuery-collection-like wrapper around one or more `mixitup.Mixer` instances 10080 * allowing simultaneous control of said instances similar to the MixItUp 2 API. 10081 * 10082 * @example 10083 * new mixitup.Collection(instances) 10084 * 10085 * @constructor 10086 * @namespace 10087 * @memberof mixitup 10088 * @private 10089 * @since 3.0.0 10090 * @param {mixitup.Mixer[]} instances 10091 */ 10092 10093 mixitup.Collection = function(instances) { 10094 var instance = null, 10095 i = -1; 10096 10097 this.callActions('beforeConstruct'); 10098 10099 for (i = 0; instance = instances[i]; i++) { 10100 this[i] = instance; 10101 } 10102 10103 this.length = instances.length; 10104 10105 this.callActions('afterConstruct'); 10106 10107 h.freeze(this); 10108 }; 10109 10110 mixitup.BaseStatic.call(mixitup.Collection); 10111 10112 mixitup.Collection.prototype = Object.create(mixitup.Base.prototype); 10113 10114 h.extend(mixitup.Collection.prototype, 10115 /** @lends mixitup.Collection */ 10116 { 10117 constructor: mixitup.Collection, 10118 10119 /** 10120 * Calls a method on all instances in the collection by passing the method 10121 * name as a string followed by any applicable parameters to be curried into 10122 * to the method. 10123 * 10124 * @example 10125 * .mixitup(methodName[,arg1][,arg2..]); 10126 * 10127 * @example 10128 * var collection = new Collection([mixer1, mixer2]); 10129 * 10130 * return collection.mixitup('filter', '.category-a') 10131 * .then(function(states) { 10132 * state.forEach(function(state) { 10133 * console.log(state.activeFilter.selector); // .category-a 10134 * }); 10135 * }); 10136 * 10137 * @public 10138 * @instance 10139 * @since 3.0.0 10140 * @param {string} methodName 10141 * @return {Promise<Array<mixitup.State>>} 10142 */ 10143 10144 mixitup: function(methodName) { 10145 var self = this, 10146 instance = null, 10147 args = Array.prototype.slice.call(arguments), 10148 tasks = [], 10149 i = -1; 10150 10151 this.callActions('beforeMixitup'); 10152 10153 args.shift(); 10154 10155 for (i = 0; instance = self[i]; i++) { 10156 tasks.push(instance[methodName].apply(instance, args)); 10157 } 10158 10159 return self.callFilters('promiseMixitup', h.all(tasks, mixitup.libraries), arguments); 10160 } 10161 }); 10162 10163 /** 10164 * `mixitup.Operation` objects contain all data neccessary to describe the full 10165 * lifecycle of any MixItUp operation. They can be used to compute and store an 10166 * operation for use at a later time (e.g. programmatic tweening). 10167 * 10168 * @constructor 10169 * @namespace 10170 * @memberof mixitup 10171 * @private 10172 * @since 3.0.0 10173 */ 10174 10175 mixitup.Operation = function() { 10176 mixitup.Base.call(this); 10177 10178 this.callActions('beforeConstruct'); 10179 10180 this.id = ''; 10181 10182 this.args = []; 10183 this.command = null; 10184 this.showPosData = []; 10185 this.toHidePosData = []; 10186 10187 this.startState = null; 10188 this.newState = null; 10189 this.docState = null; 10190 10191 this.willSort = false; 10192 this.willChangeLayout = false; 10193 this.hasEffect = false; 10194 this.hasFailed = false; 10195 10196 this.triggerElement = null; 10197 10198 this.show = []; 10199 this.hide = []; 10200 this.matching = []; 10201 this.toShow = []; 10202 this.toHide = []; 10203 this.toMove = []; 10204 this.toRemove = []; 10205 this.startOrder = []; 10206 this.newOrder = []; 10207 this.startSort = null; 10208 this.newSort = null; 10209 this.startFilter = null; 10210 this.newFilter = null; 10211 this.startDataset = null; 10212 this.newDataset = null; 10213 this.viewportDeltaX = 0; 10214 this.viewportDeltaY = 0; 10215 this.startX = 0; 10216 this.startY = 0; 10217 this.startHeight = 0; 10218 this.startWidth = 0; 10219 this.newX = 0; 10220 this.newY = 0; 10221 this.newHeight = 0; 10222 this.newWidth = 0; 10223 this.startContainerClassName = ''; 10224 this.startDisplay = ''; 10225 this.newContainerClassName = ''; 10226 this.newDisplay = ''; 10227 10228 this.callActions('afterConstruct'); 10229 10230 h.seal(this); 10231 }; 10232 10233 mixitup.BaseStatic.call(mixitup.Operation); 10234 10235 mixitup.Operation.prototype = Object.create(mixitup.Base.prototype); 10236 10237 mixitup.Operation.prototype.constructor = mixitup.Operation; 10238 10239 /** 10240 * `mixitup.State` objects expose various pieces of data detailing the state of 10241 * a MixItUp instance. They are provided at the start and end of any operation via 10242 * callbacks and events, with the most recent state stored between operations 10243 * for retrieval at any time via the API. 10244 * 10245 * @constructor 10246 * @namespace 10247 * @memberof mixitup 10248 * @public 10249 * @since 3.0.0 10250 */ 10251 10252 mixitup.State = function() { 10253 mixitup.Base.call(this); 10254 10255 this.callActions('beforeConstruct'); 10256 10257 /** 10258 * The ID of the mixer instance. 10259 * 10260 * @name id 10261 * @memberof mixitup.State 10262 * @instance 10263 * @type {string} 10264 * @default '' 10265 */ 10266 10267 this.id = ''; 10268 10269 /** 10270 * The currently active filter command as set by a control click or API call. 10271 * 10272 * @name activeFilter 10273 * @memberof mixitup.State 10274 * @instance 10275 * @type {mixitup.CommandFilter} 10276 * @default null 10277 */ 10278 10279 this.activeFilter = null; 10280 10281 /** 10282 * The currently active sort command as set by a control click or API call. 10283 * 10284 * @name activeSort 10285 * @memberof mixitup.State 10286 * @instance 10287 * @type {mixitup.CommandSort} 10288 * @default null 10289 */ 10290 10291 this.activeSort = null; 10292 10293 /** 10294 * The current layout-specific container class name, if applied. 10295 * 10296 * @name activeContainerClassName 10297 * @memberof mixitup.State 10298 * @instance 10299 * @type {string} 10300 * @default '' 10301 */ 10302 10303 this.activeContainerClassName = ''; 10304 10305 /** 10306 * A reference to the container element that the mixer is instantiated on. 10307 * 10308 * @name container 10309 * @memberof mixitup.State 10310 * @instance 10311 * @type {Element} 10312 * @default null 10313 */ 10314 10315 this.container = null; 10316 10317 /** 10318 * An array of all target elements indexed by the mixer. 10319 * 10320 * @name targets 10321 * @memberof mixitup.State 10322 * @instance 10323 * @type {Array.<Element>} 10324 * @default [] 10325 */ 10326 10327 this.targets = []; 10328 10329 /** 10330 * An array of all target elements not matching the current filter. 10331 * 10332 * @name hide 10333 * @memberof mixitup.State 10334 * @instance 10335 * @type {Array.<Element>} 10336 * @default [] 10337 */ 10338 10339 this.hide = []; 10340 10341 /** 10342 * An array of all target elements matching the current filter and any additional 10343 * limits applied such as pagination. 10344 * 10345 * @name show 10346 * @memberof mixitup.State 10347 * @instance 10348 * @type {Array.<Element>} 10349 * @default [] 10350 */ 10351 10352 this.show = []; 10353 10354 /** 10355 * An array of all target elements matching the current filter irrespective of 10356 * any additional limits applied such as pagination. 10357 * 10358 * @name matching 10359 * @memberof mixitup.State 10360 * @instance 10361 * @type {Array.<Element>} 10362 * @default [] 10363 */ 10364 10365 this.matching = []; 10366 10367 /** 10368 * An integer representing the total number of target elements indexed by the 10369 * mixer. Equivalent to `state.targets.length`. 10370 * 10371 * @name totalTargets 10372 * @memberof mixitup.State 10373 * @instance 10374 * @type {number} 10375 * @default -1 10376 */ 10377 10378 this.totalTargets = -1; 10379 10380 /** 10381 * An integer representing the total number of target elements matching the 10382 * current filter and any additional limits applied such as pagination. 10383 * Equivalent to `state.show.length`. 10384 * 10385 * @name totalShow 10386 * @memberof mixitup.State 10387 * @instance 10388 * @type {number} 10389 * @default -1 10390 */ 10391 10392 this.totalShow = -1; 10393 10394 /** 10395 * An integer representing the total number of target elements not matching 10396 * the current filter. Equivalent to `state.hide.length`. 10397 * 10398 * @name totalHide 10399 * @memberof mixitup.State 10400 * @instance 10401 * @type {number} 10402 * @default -1 10403 */ 10404 10405 this.totalHide = -1; 10406 10407 /** 10408 * An integer representing the total number of target elements matching the 10409 * current filter irrespective of any other limits applied such as pagination. 10410 * Equivalent to `state.matching.length`. 10411 * 10412 * @name totalMatching 10413 * @memberof mixitup.State 10414 * @instance 10415 * @type {number} 10416 * @default -1 10417 */ 10418 10419 this.totalMatching = -1; 10420 10421 /** 10422 * A boolean indicating whether the last operation "failed", i.e. no targets 10423 * could be found matching the filter. 10424 * 10425 * @name hasFailed 10426 * @memberof mixitup.State 10427 * @instance 10428 * @type {boolean} 10429 * @default false 10430 */ 10431 10432 this.hasFailed = false; 10433 10434 /** 10435 * The DOM element that was clicked if the last operation was triggered by the 10436 * clicking of a control and not an API call. 10437 * 10438 * @name triggerElement 10439 * @memberof mixitup.State 10440 * @instance 10441 * @type {Element|null} 10442 * @default null 10443 */ 10444 10445 this.triggerElement = null; 10446 10447 /** 10448 * The currently active dataset underlying the rendered targets, if the 10449 * dataset API is in use. 10450 * 10451 * @name activeDataset 10452 * @memberof mixitup.State 10453 * @instance 10454 * @type {Array.<object>} 10455 * @default null 10456 */ 10457 10458 this.activeDataset = null; 10459 10460 this.callActions('afterConstruct'); 10461 10462 h.seal(this); 10463 }; 10464 10465 mixitup.BaseStatic.call(mixitup.State); 10466 10467 mixitup.State.prototype = Object.create(mixitup.Base.prototype); 10468 10469 mixitup.State.prototype.constructor = mixitup.State; 10470 10471 /** 10472 * @constructor 10473 * @memberof mixitup 10474 * @private 10475 * @since 3.0.0 10476 */ 10477 10478 mixitup.UserInstruction = function() { 10479 mixitup.Base.call(this); 10480 10481 this.callActions('beforeConstruct'); 10482 10483 this.command = {}; 10484 this.animate = false; 10485 this.callback = null; 10486 10487 this.callActions('afterConstruct'); 10488 10489 h.seal(this); 10490 }; 10491 10492 mixitup.BaseStatic.call(mixitup.UserInstruction); 10493 10494 mixitup.UserInstruction.prototype = Object.create(mixitup.Base.prototype); 10495 10496 mixitup.UserInstruction.prototype.constructor = mixitup.UserInstruction; 10497 10498 /** 10499 * @constructor 10500 * @memberof mixitup 10501 * @private 10502 * @since 3.0.0 10503 */ 10504 10505 mixitup.Messages = function() { 10506 mixitup.Base.call(this); 10507 10508 this.callActions('beforeConstruct'); 10509 10510 /* Errors 10511 ----------------------------------------------------------------------------- */ 10512 10513 this.ERROR_FACTORY_INVALID_CONTAINER = 10514 '[MixItUp] An invalid selector or element reference was passed to the mixitup factory function'; 10515 10516 this.ERROR_FACTORY_CONTAINER_NOT_FOUND = 10517 '[MixItUp] The provided selector yielded no container element'; 10518 10519 this.ERROR_CONFIG_INVALID_ANIMATION_EFFECTS = 10520 '[MixItUp] Invalid value for `animation.effects`'; 10521 10522 this.ERROR_CONFIG_INVALID_CONTROLS_SCOPE = 10523 '[MixItUp] Invalid value for `controls.scope`'; 10524 10525 this.ERROR_CONFIG_INVALID_PROPERTY = 10526 '[MixitUp] Invalid configuration object property "${erroneous}"${suggestion}'; 10527 10528 this.ERROR_CONFIG_INVALID_PROPERTY_SUGGESTION = 10529 '. Did you mean "${probableMatch}"?'; 10530 10531 this.ERROR_CONFIG_DATA_UID_KEY_NOT_SET = 10532 '[MixItUp] To use the dataset API, a UID key must be specified using `data.uidKey`'; 10533 10534 this.ERROR_DATASET_INVALID_UID_KEY = 10535 '[MixItUp] The specified UID key "${uidKey}" is not present on one or more dataset items'; 10536 10537 this.ERROR_DATASET_DUPLICATE_UID = 10538 '[MixItUp] The UID "${uid}" was found on two or more dataset items. UIDs must be unique.'; 10539 10540 this.ERROR_INSERT_INVALID_ARGUMENTS = 10541 '[MixItUp] Please provider either an index or a sibling and position to insert, not both'; 10542 10543 this.ERROR_INSERT_PREEXISTING_ELEMENT = 10544 '[MixItUp] An element to be inserted already exists in the container'; 10545 10546 this.ERROR_FILTER_INVALID_ARGUMENTS = 10547 '[MixItUp] Please provide either a selector or collection `.filter()`, not both'; 10548 10549 this.ERROR_DATASET_NOT_SET = 10550 '[MixItUp] To use the dataset API with pre-rendered targets, a starting dataset must be set using `load.dataset`'; 10551 10552 this.ERROR_DATASET_PRERENDERED_MISMATCH = 10553 '[MixItUp] `load.dataset` does not match pre-rendered targets'; 10554 10555 this.ERROR_DATASET_RENDERER_NOT_SET = 10556 '[MixItUp] To insert an element via the dataset API, a target renderer function must be provided to `render.target`'; 10557 10558 this.ERROR_SORT_NON_EXISTENT_ELEMENT = 10559 '[MixItUp] An element to be sorted does not already exist in the container'; 10560 10561 /* Warnings 10562 ----------------------------------------------------------------------------- */ 10563 10564 this.WARNING_FACTORY_PREEXISTING_INSTANCE = 10565 '[MixItUp] WARNING: This element already has an active MixItUp instance. The provided configuration object will be ignored.' + 10566 ' If you wish to perform additional methods on this instance, please create a reference.'; 10567 10568 this.WARNING_INSERT_NO_ELEMENTS = 10569 '[MixItUp] WARNING: No valid elements were passed to `.insert()`'; 10570 10571 this.WARNING_REMOVE_NO_ELEMENTS = 10572 '[MixItUp] WARNING: No valid elements were passed to `.remove()`'; 10573 10574 this.WARNING_MULTIMIX_INSTANCE_QUEUE_FULL = 10575 '[MixItUp] WARNING: An operation was requested but the MixItUp instance was busy. The operation was rejected because the ' + 10576 'queue is full or queuing is disabled.'; 10577 10578 this.WARNING_GET_OPERATION_INSTANCE_BUSY = 10579 '[MixItUp] WARNING: Operations can be be created while the MixItUp instance is busy.'; 10580 10581 this.WARNING_NO_PROMISE_IMPLEMENTATION = 10582 '[MixItUp] WARNING: No Promise implementations could be found. If you wish to use promises with MixItUp please install' + 10583 ' an ES6 Promise polyfill.'; 10584 10585 this.WARNING_INCONSISTENT_SORTING_ATTRIBUTES = 10586 '[MixItUp] WARNING: The requested sorting data attribute "${attribute}" was not present on one or more target elements' + 10587 ' which may product unexpected sort output'; 10588 10589 this.callActions('afterConstruct'); 10590 10591 this.compileTemplates(); 10592 10593 h.seal(this); 10594 }; 10595 10596 mixitup.BaseStatic.call(mixitup.Messages); 10597 10598 mixitup.Messages.prototype = Object.create(mixitup.Base.prototype); 10599 10600 mixitup.Messages.prototype.constructor = mixitup.Messages; 10601 10602 /** 10603 * @return {void} 10604 */ 10605 10606 mixitup.Messages.prototype.compileTemplates = function() { 10607 var errorKey = ''; 10608 var errorMessage = ''; 10609 10610 for (errorKey in this) { 10611 if (typeof (errorMessage = this[errorKey]) !== 'string') continue; 10612 10613 this[h.camelCase(errorKey)] = h.template(errorMessage); 10614 } 10615 }; 10616 10617 mixitup.messages = new mixitup.Messages(); 10618 10619 /** 10620 * @constructor 10621 * @memberof mixitup 10622 * @private 10623 * @since 3.0.0 10624 * @param {mixitup.Mixer} mixer 10625 */ 10626 10627 mixitup.Facade = function Mixer(mixer) { 10628 mixitup.Base.call(this); 10629 10630 this.callActions('beforeConstruct', arguments); 10631 10632 this.configure = mixer.configure.bind(mixer); 10633 this.show = mixer.show.bind(mixer); 10634 this.hide = mixer.hide.bind(mixer); 10635 this.filter = mixer.filter.bind(mixer); 10636 this.toggleOn = mixer.toggleOn.bind(mixer); 10637 this.toggleOff = mixer.toggleOff.bind(mixer); 10638 this.sort = mixer.sort.bind(mixer); 10639 this.changeLayout = mixer.changeLayout.bind(mixer); 10640 this.multimix = mixer.multimix.bind(mixer); 10641 this.dataset = mixer.dataset.bind(mixer); 10642 this.tween = mixer.tween.bind(mixer); 10643 this.insert = mixer.insert.bind(mixer); 10644 this.insertBefore = mixer.insertBefore.bind(mixer); 10645 this.insertAfter = mixer.insertAfter.bind(mixer); 10646 this.prepend = mixer.prepend.bind(mixer); 10647 this.append = mixer.append.bind(mixer); 10648 this.remove = mixer.remove.bind(mixer); 10649 this.destroy = mixer.destroy.bind(mixer); 10650 this.forceRefresh = mixer.forceRefresh.bind(mixer); 10651 this.forceRender = mixer.forceRender.bind(mixer); 10652 this.isMixing = mixer.isMixing.bind(mixer); 10653 this.getOperation = mixer.getOperation.bind(mixer); 10654 this.getConfig = mixer.getConfig.bind(mixer); 10655 this.getState = mixer.getState.bind(mixer); 10656 10657 this.callActions('afterConstruct', arguments); 10658 10659 h.freeze(this); 10660 h.seal(this); 10661 }; 10662 10663 mixitup.BaseStatic.call(mixitup.Facade); 10664 10665 mixitup.Facade.prototype = Object.create(mixitup.Base.prototype); 10666 10667 mixitup.Facade.prototype.constructor = mixitup.Facade; 10668 10669 if (typeof exports === 'object' && typeof module === 'object') { 10670 module.exports = mixitup; 10671 } else if (typeof define === 'function' && define.amd) { 10672 define(function() { 10673 return mixitup; 10674 }); 10675 } else if (typeof window.mixitup === 'undefined' || typeof window.mixitup !== 'function') { 10676 window.mixitup = mixitup; 10677 } 10678 mixitup.BaseStatic.call(mixitup.constructor); 10679 10680 mixitup.NAME = 'mixitup'; 10681 mixitup.CORE_VERSION = '3.3.
106811'; 10682})(window);
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.