1/**! 2 * MixItUp MultiFilter v3.3.6 3 * A UI-builder for powerful multidimensional filtering 4 * Build 293e0dda-087e-4a76-aadf-e3e8b311b81f 5 * 6 * Requires mixitup.js >= v^3.1.2 7 * 8 * @copyright Copyright 2014-2020 KunkaLabs Limited. 9 * @author KunkaLabs Limited. 10 * @link https://www.kunkalabs.com/mixitup-multifilter/ 11 * 12 * @license Commercial use requires a commercial license. 13 * https://www.kunkalabs.com/mixitup-multifilter/licenses/ 14 * 15 * Non-commercial use permitted under same terms as license. 16 * http://creativecommons.org/licenses/by-nc/3.0/
vendor: 47,485 bytes, lines 16-1243
16 17 */ 18(function(window) { 19 'use strict'; 20 21 var mixitupMultifilter = function(mixitup) { 22 var h = mixitup.h; 23 var diacriticsMap; 24 25 diacriticsMap = [ 26 ['A', /[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g], 27 ['AA', /[\uA732]/g], 28 ['AE', /[\u00C6\u01FC\u01E2]/g], 29 ['AO', /[\uA734]/g], 30 ['AU', /[\uA736]/g], 31 ['AV', /[\uA738\uA73A]/g], 32 ['AY', /[\uA73C]/g], 33 ['B', /[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g], 34 ['C', /[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g], 35 ['D', /[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g], 36 ['DZ', /[\u01F1\u01C4]/g], 37 ['Dz', /[\u01F2\u01C5]/g], 38 ['E', /[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g], 39 ['F', /[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g], 40 ['G', /[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g], 41 ['H', /[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g], 42 ['I', /[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g], 43 ['J', /[\u004A\u24BF\uFF2A\u0134\u0248]/g], 44 ['K', /[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g], 45 ['L', /[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g], 46 ['LJ', /[\u01C7]/g], 47 ['Lj', /[\u01C8]/g], 48 ['M', /[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g], 49 ['N', /[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g], 50 ['NJ', /[\u01CA]/g], 51 ['Nj', /[\u01CB]/g], 52 ['O', /[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g], 53 ['OI', /[\u01A2]/g], 54 ['OO', /[\uA74E]/g], 55 ['OU', /[\u0222]/g], 56 ['P', /[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g], 57 ['Q', /[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g], 58 ['R', /[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g], 59 ['S', /[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g], 60 ['T', /[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g], 61 ['TZ', /[\uA728]/g], 62 ['U', /[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g], 63 ['V', /[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g], 64 ['VY', /[\uA760]/g], 65 ['W', /[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g], 66 ['X', /[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g], 67 ['Y', /[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g], 68 ['Z', /[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g], 69 ['a', /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g], 70 ['aa', /[\uA733]/g], 71 ['ae', /[\u00E6\u01FD\u01E3]/g], 72 ['ao', /[\uA735]/g], 73 ['au', /[\uA737]/g], 74 ['av', /[\uA739\uA73B]/g], 75 ['ay', /[\uA73D]/g], 76 ['b', /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g], 77 ['c', /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g], 78 ['d', /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g], 79 ['dz', /[\u01F3\u01C6]/g], 80 ['e', /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g], 81 ['f', /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g], 82 ['g', /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g], 83 ['h', /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g], 84 ['hv', /[\u0195]/g], 85 ['i', /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g], 86 ['j', /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g], 87 ['k', /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g], 88 ['l', /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g], 89 ['lj', /[\u01C9]/g], 90 ['m', /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g], 91 ['n', /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g], 92 ['nj', /[\u01CC]/g], 93 ['o', /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g], 94 ['oi', /[\u01A3]/g], 95 ['ou', /[\u0223]/g], 96 ['oo', /[\uA74F]/g], 97 ['p', /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g], 98 ['q', /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g], 99 ['r', /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g], 100 ['s', /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g], 101 ['t', /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g], 102 ['tz', /[\uA729]/g], 103 ['u', /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g], 104 ['v', /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g], 105 ['vy', /[\uA761]/g], 106 ['w', /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g], 107 ['x', /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g], 108 ['y', /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g], 109 ['z', /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g] 110 ]; 111 112 if ( 113 !mixitup.CORE_VERSION || 114 !h.compareVersions(mixitupMultifilter.REQUIRE_CORE_VERSION, mixitup.CORE_VERSION) 115 ) { 116 throw new Error( 117 '[MixItUp Multifilter] MixItUp Multifilter v' + 118 mixitupMultifilter.EXTENSION_VERSION + 119 ' requires at least MixItUp v' + 120 mixitupMultifilter.REQUIRE_CORE_VERSION 121 ); 122 } 123 124 /** 125 * A group of optional callback functions to be invoked at various 126 * points within the lifecycle of a mixer operation. 127 * 128 * @constructor 129 * @memberof mixitup.Config 130 * @name callbacks 131 * @namespace 132 * @public 133 * @since 3.0.0 134 */ 135 136 mixitup.ConfigCallbacks.registerAction('afterConstruct', 'multifilter', function() { 137 /** 138 * A callback function invoked whenever MultiFilter filter groups 139 * are parsed. This occurs whenever the user interacts with filter 140 * group UI, or when the `parseFilterGroups()` API method is called, 141 * but before the resulting filter operation has been triggered. 142 * 143 * By default, this generates the appropriate compound selector and 144 * filters the mixer using a `multimix()` API call internally. This 145 * callback can be used to transform the multimix command object sent 146 * to this API call. 147 * 148 * This is particularly useful when additional behavior such as sorting 149 * or pagination must be taken into account when using the MultiFilter API. 150 * 151 * The callback receives the generated multimix command object, and must 152 * also return a valid multimix command object. 153 * 154 * @example <caption>Example: Overriding the default filtering behavior with `onParseFilterGroups`</caption> 155 * var mixer = mixitup(containerEl, { 156 * callbacks: { 157 * onParseFilterGroups: function(command) { 158 * command.paginate = 3; 159 * command.sort = 'default:desc'; 160 * 161 * return command; 162 * } 163 * } 164 * }); 165 * 166 * @name onParseFilterGroups 167 * @memberof mixitup.Config.callbacks 168 * @instance 169 * @type {function} 170 * @default null 171 */ 172 173 this.onParseFilterGroups = null; 174 }); 175 176 /** 177 * A group of properties defining the behavior of your multifilter UI. 178 * 179 * @constructor 180 * @memberof mixitup.Config 181 * @name multifilter 182 * @namespace 183 * @public 184 * @since 3.0.0 185 */ 186 187 mixitup.ConfigMultifilter = function() { 188 189 /** 190 * A boolean dictating whether or not to enable multifilter functionality. 191 * 192 * If `true`, MixItUp will query the DOM for any elements with a 193 * `data-filter-group` attribute present on instantiation. 194 * 195 * @name enable 196 * @memberof mixitup.Config.multifilter 197 * @instance 198 * @type {boolean} 199 * @default false 200 */ 201 202 this.enable = false; 203 204 /** 205 * A string dictating the logic to use when concatenating selectors within 206 * individual filter groups. 207 * 208 * If set to `'or'` (default), targets will be shown if they match any 209 * active filter in the group. 210 * 211 * If set to `'and'`, targets will be shown only if they match 212 * all active filters in the group. 213 * 214 * @name logicWithinGroup 215 * @memberof mixitup.Config.multifilter 216 * @instance 217 * @type {string} 218 * @default 'or' 219 */ 220 221 this.logicWithinGroup = 'or'; 222 223 /** 224 * A string dictating the logic to use when concatenating each group's 225 * selectors into one single selector. 226 * 227 * If set to `'and'` (default), targets will be shown only if they match 228 * the combined active selectors of all groups. 229 * 230 * If set to `'or'`, targets will be shown if they match the active selectors 231 * of any individual group. 232 * 233 * @name logicBetweenGroups 234 * @memberof mixitup.Config.multifilter 235 * @instance 236 * @type {string} 237 * @default 'and' 238 */ 239 240 this.logicBetweenGroups = 'and'; 241 242 /** 243 * An integer dictating the minimum number of characters at which the value 244 * of a text input will be included as a multifilter. This prevents short or 245 * incomplete words with many potential matches from triggering 246 * filter operations. 247 * 248 * @name minSearchLength 249 * @memberof mixitup.Config.multifilter 250 * @instance 251 * @type {number} 252 * @default 3 253 */ 254 255 this.minSearchLength = 3; 256 257 /** 258 * A string dictating when the parsing of filter groups should occur. 259 * 260 * If set to `'change'` (default), the mixer will be filtered whenever the 261 * filtering UI is interacted with. The mode provides real-time filtering with 262 * instant feedback. 263 * 264 * If set to `'submit'`, the mixer will only be filtered when a submit button is 265 * clicked (if using a `<form>` element as a parent). This enables the user to firstly 266 * make their selection, and then trigger filtering once they have 267 * finished making their selection. 268 * 269 * Alternatively, the `mixer.parseFilterGroups()` method can be called via the API at any 270 * time to trigger the parsing of filter groups and filter the mixer. 271 * 272 * @name parseOn 273 * @memberof mixitup.Config.multifilter 274 * @instance 275 * @type {string} 276 * @default 'change' 277 */ 278 279 this.parseOn = 'change'; 280 281 /** 282 * An integer dictating the duration in ms that must elapse between keyup 283 * events in order to trigger a change. 284 * 285 * Setting a comfortable delay of ~350ms prevents the mixer from being 286 * thrashed while typing occurs. 287 * 288 * @name keyupThrottleDuration 289 * @memberof mixitup.Config.multifilter 290 * @instance 291 * @type {number} 292 * @default 350 293 */ 294 295 this.keyupThrottleDuration = 350; 296 297 h.seal(this); 298 }; 299 300 /** 301 * The MixItUp configuration object is extended with properties relating to 302 * the MultiFilter extension. 303 * 304 * For the full list of configuration options, please refer to the MixItUp 305 * core documentation. 306 * 307 * @constructor 308 * @memberof mixitup 309 * @name Config 310 * @namespace 311 * @public 312 * @since 2.0.0 313 */ 314 315 mixitup.Config.registerAction('beforeConstruct', 'multifilter', function() { 316 this.multifilter = new mixitup.ConfigMultifilter(); 317 }); 318 319 mixitup.MultifilterFormEventTracker = function() { 320 this.form = null; 321 this.totalBound = 0; 322 this.totalHandled = 0; 323 324 h.seal(this); 325 }; 326 327 mixitup.FilterGroupDom = function() { 328 this.el = null; 329 this.form = null; 330 331 h.seal(this); 332 }; 333 334 mixitup.FilterGroup = function() { 335 this.name = ''; 336 this.dom = new mixitup.FilterGroupDom(); 337 this.activeSelectors = []; 338 this.activeFilters = []; 339 this.activeToggles = []; 340 this.handler = null; 341 this.mixer = null; 342 this.logic = 'or'; 343 this.parseOn = 'change'; 344 this.keyupTimeout = -1; 345 346 h.seal(this); 347 }; 348 349 h.extend(mixitup.FilterGroup.prototype, { 350 351 /** 352 * @private 353 * @param {HTMLELement} el 354 * @param {mixitup.Mixer} mixer 355 * @return {void} 356 */ 357 358 init: function(el, mixer) { 359 var self = this, 360 logic = el.getAttribute('data-logic'); 361 362 self.dom.el = el; 363 364 this.name = self.dom.el.getAttribute('data-filter-group') || ''; 365 366 self.cacheDom(); 367 368 if (self.dom.form) { 369 self.enableButtons(); 370 } 371 372 self.mixer = mixer; 373 374 if ((logic && logic.toLowerCase() === 'and') || mixer.config.multifilter.logicWithinGroup === 'and') { 375 // override default group logic 376 377 self.logic = 'and'; 378 379 } 380 381 self.bindEvents(); 382 }, 383 384 /** 385 * @private 386 * @return {void} 387 */ 388 389 cacheDom: function() { 390 var self = this; 391 392 self.dom.form = h.closestParent(self.dom.el, 'form', true); 393 }, 394 395 enableButtons: function() { 396 var self = this, 397 buttons = self.dom.form.querySelectorAll('button[type="submit"]:disabled'), 398 button = null, 399 i = -1; 400 401 for (i = 0; button = buttons[i]; i++) { 402 if (button.disabled) { 403 button.disabled = false; 404 } 405 } 406 }, 407 408 /** 409 * @private 410 * @return {void} 411 */ 412 413 bindEvents: function() { 414 var self = this; 415 416 self.handler = function(e) { 417 switch (e.type) { 418 case 'reset': 419 case 'submit': 420 self.handleFormEvent(e); 421 422 break; 423 default: 424 self['handle' + h.pascalCase(e.type)](e); 425 } 426 }; 427 428 h.on(self.dom.el, 'click', self.handler); 429 h.on(self.dom.el, 'change', self.handler); 430 h.on(self.dom.el, 'keyup', self.handler); 431 432 if (self.dom.form) { 433 h.on(self.dom.form, 'reset', self.handler); 434 h.on(self.dom.form, 'submit', self.handler); 435 } 436 }, 437 438 /** 439 * @private 440 * @return {void} 441 */ 442 443 unbindEvents: function() { 444 var self = this; 445 446 h.off(self.dom.el, 'click', self.handler); 447 h.off(self.dom.el, 'change', self.handler); 448 h.off(self.dom.el, 'keyup', self.handler); 449 450 if (self.dom.form) { 451 h.off(self.dom.form, 'reset', self.handler); 452 h.off(self.dom.form, 'submit', self.handler); 453 } 454 455 self.handler = null; 456 }, 457 458 /** 459 * @private 460 * @param {MouseEvent} e 461 * @return {void} 462 */ 463 464 handleClick: function(e) { 465 var self = this, 466 mixer = self.mixer, 467 returnValue = null, 468 controlEl = h.closestParent(e.target, '[data-filter], [data-toggle]', true), 469 controlSelector = '', 470 index = -1, 471 selector = ''; 472 473 if (!controlEl) return; 474 475 if ((controlSelector = self.mixer.config.selectors.control) && !controlEl.matches(controlSelector)) { 476 return; 477 } 478 479 e.stopPropagation(); 480 481 if (!mixer.lastClicked) { 482 mixer.lastClicked = controlEl; 483 } 484 485 if (typeof mixer.config.callbacks.onMixClick === 'function') { 486 returnValue = mixer.config.callbacks.onMixClick.call(mixer.lastClicked, mixer.state, e, self); 487 488 if (returnValue === false) { 489 // User has returned `false` from the callback, so do not handle click 490 491 return; 492 } 493 } 494 495 if (controlEl.matches('[data-filter]')) { 496 selector = controlEl.getAttribute('data-filter'); 497 498 self.activeToggles = []; 499 self.activeSelectors = self.activeFilters = [selector]; 500 } else if (controlEl.matches('[data-toggle]')) { 501 selector = controlEl.getAttribute('data-toggle'); 502 503 self.activeFilters = []; 504 505 if ((index = self.activeToggles.indexOf(selector)) > -1) { 506 self.activeToggles.splice(index, 1); 507 } else { 508 self.activeToggles.push(selector); 509 } 510 511 if (self.logic === 'and') { 512 // Compress into single node 513 514 self.activeSelectors = [self.activeToggles]; 515 } else { 516 self.activeSelectors = self.activeToggles; 517 } 518 } 519 520 self.updateControls(); 521 522 if (self.mixer.config.multifilter.parseOn === 'change') { 523 self.mixer.parseFilterGroups(); 524 } 525 }, 526 527 /** 528 * @private 529 * @param {Event} e 530 * @return {void} 531 */ 532 533 handleChange: function(e) { 534 var self = this, 535 input = e.target; 536 537 e.stopPropagation(); 538 539 switch(input.type) { 540 case 'text': 541 case 'search': 542 case 'email': 543 case 'select-one': 544 case 'radio': 545 self.getSingleValue(input); 546 547 break; 548 case 'checkbox': 549 case 'select-multiple': 550 self.getMultipleValues(input); 551 552 break; 553 } 554 555 if (self.mixer.config.multifilter.parseOn === 'change') { 556 self.mixer.parseFilterGroups(); 557 } 558 }, 559 560 handleKeyup: function(e) { 561 var self = this, 562 input = e.target; 563 564 // NB: Selects can fire keyup events (e.g. multiselect, textual search) 565 566 if (['text', 'search', 'email'].indexOf(input.type) < 0) return; 567 568 if (self.mixer.config.multifilter.parseOn !== 'change') { 569 self.mixer.getSingleValue(input); 570 571 return; 572 } 573 574 clearTimeout(self.keyupTimeout); 575 576 self.keyupTimeout = setTimeout(function() { 577 self.getSingleValue(input); 578 self.mixer.parseFilterGroups(); 579 }, self.mixer.config.multifilter.keyupThrottleDuration); 580 }, 581 582 /** 583 * @private 584 * @param {Event} e 585 * @return {void} 586 */ 587 588 handleFormEvent: function(e) { 589 var self = this, 590 tracker = null, 591 group = null, 592 i = -1; 593 594 if (e.type === 'submit') { 595 e.preventDefault(); 596 } 597 598 if (e.type === 'reset') { 599 self.activeFilters = 600 self.activeToggles = 601 self.activeSelectors = []; 602 603 self.updateControls(); 604 } 605 606 if (!self.mixer.multifilterFormEventTracker) { 607 tracker = self.mixer.multifilterFormEventTracker = new mixitup.MultifilterFormEventTracker(); 608 609 tracker.form = e.target; 610 611 for (i = 0; group = self.mixer.filterGroups[i]; i++) { 612 if (group.dom.form !== e.target) continue; 613 614 tracker.totalBound++; 615 } 616 } else { 617 tracker = self.mixer.multifilterFormEventTracker; 618 } 619 620 if (e.target === tracker.form) { 621 tracker.totalHandled++; 622 623 if (tracker.totalHandled === tracker.totalBound) { 624 self.mixer.multifilterFormEventTracker = null; 625 626 if (e.type === 'submit' || self.mixer.config.multifilter.parseOn === 'change') { 627 self.mixer.parseFilterGroups(); 628 } 629 } 630 } 631 }, 632 633 /** 634 * @private 635 * @param {HTMLELement} input 636 * @return {void} 637 */ 638 639 getSingleValue: function(input) { 640 var self = this, 641 diacriticMap = null, 642 attributeName = '', 643 selector = '', 644 value = '', 645 i = -1; 646 647 if (input.type.match(/text|search|email/g)) { 648 attributeName = input.getAttribute('data-search-attribute'); 649 650 if (!attributeName) { 651 throw new Error('[MixItUp MultiFilter] A valid `data-search-attribute` must be present on text inputs'); 652 } 653 654 if (input.value.length < self.mixer.config.multifilter.minSearchLength) { 655 self.activeSelectors = self.activeFilters = self.activeToggles = ['']; 656 657 return; 658 } 659 660 // Lowercase and trim 661 662 value = input.value.toLowerCase().trim(); 663 664 // Replace diacritics 665 666 for (i = 0; (diacriticMap = diacriticsMap[i]); i++) { 667 value = value.replace(diacriticMap[1], diacriticMap[0]); 668 } 669 670 // Strip non-word characters 671 672 value = value.replace(/\W+/g, ' '); 673 674 selector = '[' + attributeName + '*="' + value + '"]'; 675 } else { 676 selector = input.value; 677 } 678 679 if (typeof input.value === 'string') { 680 self.activeSelectors = 681 self.activeToggles = 682 self.activeFilters = 683 selector ? [selector] : []; 684 } 685 }, 686 687 /** 688 * @private 689 * @param {HTMLELement} input 690 * @return {void} 691 */ 692 693 getMultipleValues: function(input) { 694 var self = this, 695 activeToggles = [], 696 query = '', 697 item = null, 698 items = null, 699 i = -1; 700 701 switch (input.type) { 702 case 'checkbox': 703 query = 'input[type="checkbox"]'; 704 705 break; 706 case 'select-multiple': 707 query = 'option'; 708 } 709 710 items = self.dom.el.querySelectorAll(query); 711 712 for (i = 0; item = items[i]; i++) { 713 if ((item.checked || item.selected) && item.value) { 714 activeToggles.push(item.value); 715 } 716 } 717 718 self.activeFilters = []; 719 self.activeToggles = activeToggles; 720 721 if (self.logic === 'and') { 722 // Compress into single node 723 724 self.activeSelectors = [activeToggles]; 725 } else { 726 self.activeSelectors = activeToggles; 727 } 728 }, 729 730 /** 731 * @private 732 * @param {Array.<HTMLELement>} [controlEls] 733 * @return {void} 734 */ 735 736 updateControls: function(controlEls) { 737 var self = this, 738 controlEl = null, 739 controlSelector = '', 740 controlsSelector = '', 741 type = '', 742 i = -1; 743 744 controlSelector = self.mixer.config.selectors.control.trim(); 745 746 controlsSelector = [ 747 '[data-filter]' + controlSelector, 748 '[data-toggle]' + controlSelector 749 ].join(', '); 750 751 controlEls = controlEls || self.dom.el.querySelectorAll(controlsSelector); 752 753 for (i = 0; controlEl = controlEls[i]; i++) { 754 type = Boolean(controlEl.getAttribute('data-toggle')) ? 'toggle' : 'filter'; 755 756 self.updateControl(controlEl, type); 757 } 758 }, 759 760 /** 761 * @private 762 * @param {HTMLELement} controlEl 763 * @param {string} type 764 * @return {void} 765 */ 766 767 updateControl: function(controlEl, type) { 768 var self = this, 769 selector = controlEl.getAttribute('data-' + type), 770 activeControls = self.activeToggles.concat(self.activeFilters), 771 activeClassName = ''; 772 773 activeClassName = h.getClassname(self.mixer.config.classNames, type, self.mixer.config.classNames.modifierActive); 774 775 if (activeControls.indexOf(selector) > -1) { 776 h.addClass(controlEl, activeClassName); 777 } else { 778 h.removeClass(controlEl, activeClassName); 779 } 780 }, 781 782 /** 783 * @private 784 */ 785 786 updateUi: function() { 787 var self = this, 788 controlEls = self.dom.el.querySelectorAll('[data-filter], [data-toggle]'), 789 inputEls = self.dom.el.querySelectorAll('input[type="radio"], input[type="checkbox"], option'), 790 activeControls = self.activeToggles.concat(self.activeFilters), 791 isActive = false, 792 inputEl = null, 793 i = -1; 794 795 if (controlEls.length) { 796 self.updateControls(controlEls, true); 797 } 798 799 for (i = 0; inputEl = inputEls[i]; i++) { 800 isActive = activeControls.indexOf(inputEl.value) > -1; 801 802 switch (inputEl.tagName.toLowerCase()) { 803 case 'option': 804 inputEl.selected = isActive; 805 806 break; 807 case 'input': 808 inputEl.checked = isActive; 809 810 break; 811 } 812 } 813 } 814 }); 815 816 mixitup.MixerDom.registerAction('afterConstruct', 'multifilter', function() { 817 this.filterGroups = []; 818 }); 819 820 /** 821 * The `mixitup.Mixer` class is extended with API methods relating to 822 * the MultiFilter extension. 823 * 824 * For the full list of API methods, please refer to the MixItUp 825 * core documentation. 826 * 827 * @constructor 828 * @namespace 829 * @name Mixer 830 * @memberof mixitup 831 * @public 832 * @since 3.0.0 833 */ 834 835 mixitup.Mixer.registerAction('afterConstruct', 'multifilter', function() { 836 this.filterGroups = []; 837 this.filterGroupsHash = {}; 838 this.multifilterFormEventTracker = null; 839 }); 840 841 mixitup.Mixer.registerAction('afterCacheDom', 'multifilter', function() { 842 var self = this, 843 parent = null; 844 845 if (!self.config.multifilter.enable) return; 846 847 switch (self.config.controls.scope) { 848 case 'local': 849 parent = self.dom.container; 850 851 break; 852 case 'global': 853 parent = self.dom.document; 854 855 break; 856 default: 857 throw new Error(mixitup.messages.ERROR_CONFIG_INVALID_CONTROLS_SCOPE); 858 } 859 860 self.dom.filterGroups = parent.querySelectorAll('[data-filter-group]'); 861 }); 862 863 mixitup.Mixer.registerAction('beforeInitControls', 'multifilter', function() { 864 var self = this; 865 866 if (!self.config.multifilter.enable) return; 867 868 self.config.controls.live = true; // force live controls if multifilter is enabled 869 }); 870 871 mixitup.Mixer.registerAction('afterSanitizeConfig', 'multifilter', function() { 872 var self = this; 873 874 self.config.multifilter.logicBetweenGroups = self.config.multifilter.logicBetweenGroups.toLowerCase().trim(); 875 self.config.multifilter.logicWithinGroup = self.config.multifilter.logicWithinGroup.toLowerCase().trim(); 876 }); 877 878 mixitup.Mixer.registerAction('afterAttach', 'multifilter', function() { 879 var self = this; 880 881 if (self.dom.filterGroups.length) { 882 self.indexFilterGroups(); 883 } 884 }); 885 886 mixitup.Mixer.registerAction('afterUpdateControls', 'multifilter', function() { 887 var self = this, 888 group = null, 889 i = -1; 890 891 for (i = 0; group = self.filterGroups[i]; i++) { 892 group.updateControls(); 893 } 894 }); 895 896 mixitup.Mixer.registerAction('beforeDestroy', 'multifilter', function() { 897 var self = this, 898 group = null, 899 i = -1; 900 901 for (i = 0; group = self.filterGroups[i]; i++) { 902 group.unbindEvents(); 903 } 904 }); 905 906 mixitup.Mixer.extend( 907 /** @lends mixitup.Mixer */ 908 { 909 /** 910 * @private 911 * @return {void} 912 */ 913 914 indexFilterGroups: function() { 915 var self = this, 916 filterGroup = null, 917 el = null, 918 i = -1; 919 920 for (i = 0; el = self.dom.filterGroups[i]; i++) { 921 filterGroup = new mixitup.FilterGroup(); 922 923 filterGroup.init(el, self); 924 925 self.filterGroups.push(filterGroup); 926 927 if (filterGroup.name) { 928 // If present, also index by name 929 930 if (typeof self.filterGroupsHash[filterGroup.name] !== 'undefined') { 931 throw new Error('[MixItUp MultiFilter] A filter group with name "' + filterGroup.name + '" already exists'); 932 } 933 934 self.filterGroupsHash[filterGroup.name] = filterGroup; 935 } 936 } 937 }, 938 939 /** 940 * @private 941 * @instance 942 * @since 2.0.0 943 * @param {Array<*>} args 944 * @return {mixitup.UserInstruction} 945 */ 946 947 parseParseFilterGroupsArgs: function(args) { 948 var self = this, 949 instruction = new mixitup.UserInstruction(), 950 arg = null, 951 i = -1; 952 953 instruction.animate = self.config.animation.enable; 954 instruction.command = new mixitup.CommandFilter(); 955 956 for (i = 0; i < args.length; i++) { 957 arg = args[i]; 958 959 if (typeof arg === 'boolean') { 960 instruction.animate = arg; 961 } else if (typeof arg === 'function') { 962 instruction.callback = arg; 963 } 964 } 965 966 h.freeze(instruction); 967 968 return instruction; 969 }, 970 971 /** 972 * Recursively builds up paths between all possible permutations 973 * of filter group nodes according to the defined logic. 974 * 975 * @private 976 * @return {Array.<Array.<string>>} 977 */ 978 979 getFilterGroupPaths: function() { 980 var self = this, 981 buildPath = null, 982 crawl = null, 983 nodes = null, 984 matrix = [], 985 paths = [], 986 trackers = [], 987 i = -1; 988 989 for (i = 0; i < self.filterGroups.length; i++) { 990 // Filter out groups without any active filters 991 992 if ((nodes = self.filterGroups[i].activeSelectors).length) { 993 matrix.push(nodes); 994 995 // Initialise tracker for each group 996 997 trackers.push(0); 998 } 999 } 1000 1001 buildPath = function() { 1002 var node = null, 1003 path = [], 1004 i = -1; 1005 1006 for (i = 0; i < matrix.length; i++) { 1007 node = matrix[i][trackers[i]]; 1008 1009 if (Array.isArray(node)) { 1010 // AND logic within group 1011 1012 node = node.join(''); 1013 } 1014 1015 path.push(node); 1016 } 1017 1018 path = h.clean(path); 1019 1020 paths.push(path); 1021 }; 1022 1023 crawl = function(index) { 1024 index = index || 0; 1025 1026 var nodes = matrix[index]; 1027 1028 while (trackers[index] < nodes.length) { 1029 if (index < matrix.length - 1) { 1030 // If not last, recurse 1031 1032 crawl(index + 1); 1033 } else { 1034 // Last, build selector 1035 1036 buildPath(); 1037 } 1038 1039 trackers[index]++; 1040 } 1041 1042 trackers[index] = 0; 1043 }; 1044 1045 if (!matrix.length) return ''; 1046 1047 crawl(); 1048 1049 return paths; 1050 }, 1051 1052 /** 1053 * Builds up a selector string from a provided paths array. 1054 * 1055 * @private 1056 * @param {Array.<Array.<string>>} paths 1057 * @return {string} 1058 */ 1059 1060 buildSelectorFromPaths: function(paths) { 1061 var self = this, 1062 path = null, 1063 output = [], 1064 pathSelector = '', 1065 nodeDelineator = '', 1066 i = -1; 1067 1068 if (!paths.length) { 1069 return ''; 1070 } 1071 1072 if (self.config.multifilter.logicBetweenGroups === 'or') { 1073 nodeDelineator = ', '; 1074 } 1075 1076 if (paths.length > 1) { 1077 for (i = 0; i < paths.length; i++) { 1078 path = paths[i]; 1079 1080 pathSelector = path.join(nodeDelineator); 1081 1082 if (output.indexOf(pathSelector) < 0) { 1083 output.push(pathSelector); 1084 } 1085 } 1086 1087 return output.join(', '); 1088 } else { 1089 return paths[0].join(nodeDelineator); 1090 } 1091 }, 1092 1093 /** 1094 * Traverses the currently active filters in all groups, building up a 1095 * compound selector string as per the defined logic. A filter operation 1096 * is then called on the mixer using the resulting selector. 1097 * 1098 * This method can be used to programmatically trigger the parsing of 1099 * filter groups after manipulations to a group's active selector(s) by 1100 * the `.setFilterGroupSelectors()` API method. 1101 * 1102 * @example 1103 * 1104 * .parseFilterGroups([animate] [, callback]) 1105 * 1106 * @example <caption>Example: Triggering parsing after programmatically changing the values of a filter group</caption> 1107 * 1108 * mixer.setFilterGroupSelectors('color', ['.green', '.blue']); 1109 * 1110 * mixer.parseFilterGroups(); 1111 * 1112 * @public 1113 * @since 3.0.0 1114 * @param {boolean} [animate=true] 1115 * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. 1116 * @param {function} [callback=null] 1117 * An optional callback function to be invoked after the operation has completed. 1118 * @return {Promise.<mixitup.State>} 1119 * A promise resolving with the current state object. 1120 */ 1121 1122 parseFilterGroups: function() { 1123 var self = this, 1124 instruction = self.parseFilterArgs(arguments), 1125 paths = self.getFilterGroupPaths(), 1126 selector = self.buildSelectorFromPaths(paths), 1127 callback = null, 1128 command = {}; 1129 1130 if (selector === '') { 1131 selector = self.config.controls.toggleDefault; 1132 } 1133 1134 instruction.command.selector = selector; 1135 1136 command.filter = instruction.command; 1137 1138 if (typeof (callback = self.config.callbacks.onParseFilterGroups) === 'function') { 1139 command = callback(command); 1140 } 1141 1142 return self.multimix(command, instruction.animate, instruction.callback); 1143 }, 1144 1145 /** 1146 * Programmatically sets one or more active selectors for a specific filter 1147 * group and updates the group's UI. 1148 * 1149 * Because MixItUp has no way of knowing how to break down a provided 1150 * compound selector into its component groups, we can not use the 1151 * standard `.filter()` or `toggleOn()/toggleOff()` API methods when using 1152 * the MultiFilter extension. Instead, this method allows us to perform 1153 * multi-dimensional filtering via the API by setting the active selectors of 1154 * individual groups and then triggering the `.parseFilterGroups()` method. 1155 * 1156 * If setting multiple active selectors, do not pass a compound selector. 1157 * Instead, pass an array with each item containing a single selector 1158 * string as in example 2. 1159 * 1160 * @example 1161 * 1162 * .setFilterGroupSelectors(groupName, selectors) 1163 * 1164 * @example <caption>Example 1: Setting a single active selector for a "color" group</caption> 1165 * 1166 * mixer.setFilterGroupSelectors('color', '.green'); 1167 * 1168 * mixer.parseFilterGroups(); 1169 * 1170 * @example <caption>Example 2: Setting multiple active selectors for a "size" group</caption> 1171 * 1172 * mixer.setFilterGroupSelectors('size', ['.small', '.large']); 1173 * 1174 * mixer.parseFilterGroups(); 1175 * 1176 * @public 1177 * @since 3.2.0 1178 * @param {string} groupName The name of the filter group as defined in the markup via the `data-filter-group` attribute. 1179 * @param {(string|Array.<string>)} selectors A single selector string, or multiple selector strings as an array. 1180 * @return {void} 1181 */ 1182 1183 setFilterGroupSelectors: function(groupName, selectors) { 1184 var self = this, 1185 filterGroup = null; 1186 1187 selectors = Array.isArray(selectors) ? selectors : [selectors]; 1188 1189 if (typeof (filterGroup = self.filterGroupsHash[groupName]) === 'undefined') { 1190 throw new Error('[MixItUp MultiFilter] No filter group could be found with the name "' + groupName + '"'); 1191 } 1192 1193 filterGroup.activeToggles = selectors.slice(); 1194 1195 if (filterGroup.logic === 'and') { 1196 // Compress into single node 1197 1198 filterGroup.activeSelectors = [filterGroup.activeToggles]; 1199 } else { 1200 filterGroup.activeSelectors = filterGroup.activeToggles; 1201 } 1202 1203 filterGroup.updateUi(filterGroup.activeToggles); 1204 }, 1205 1206 /** 1207 * Returns an array of active selectors for a specific filter group. 1208 * 1209 * @example 1210 * 1211 * .getFilterGroupSelectors(groupName) 1212 * 1213 * @example <caption>Example: Retrieving the active selectors for a "size" group</caption> 1214 * 1215 * mixer.getFilterGroupSelectors('size'); // ['.small', '.large'] 1216 * 1217 * @public 1218 * @since 3.2.0 1219 * @param {string} groupName The name of the filter group as defined in the markup via the `data-filter-group` attribute. 1220 * @return {void} 1221 */ 1222 1223 getFilterGroupSelectors: function(groupName) { 1224 var self = this, 1225 filterGroup = null; 1226 1227 if (typeof (filterGroup = self.filterGroupsHash[groupName]) === 'undefined') { 1228 throw new Error('[MixItUp MultiFilter] No filter group could be found with the name "' + groupName + '"'); 1229 } 1230 1231 return filterGroup.activeSelectors.slice(); 1232 } 1233 }); 1234 1235 mixitup.Facade.registerAction('afterConstruct', 'multifilter', function(mixer) { 1236 this.parseFilterGroups = mixer.parseFilterGroups.bind(mixer); 1237 this.setFilterGroupSelectors = mixer.setFilterGroupSelectors.bind(mixer); 1238 this.getFilterGroupSelectors = mixer.getFilterGroupSelectors.bind(mixer); 1239 }); }; 1240 1241 mixitupMultifilter.TYPE = 'mixitup-extension'; 1242 mixitupMultifilter.NAME = 'mixitup-multifilter'; 1243 mixitupMultifilter.EXTENSION_VERSION = '3.3.
12436'; 1244 mixitupMultifilter.REQUIRE_CORE_VERSION = '^3.1.2'; 1245 1246 if (typeof exports === 'object' && typeof module === 'object') { 1247 module.exports = mixitupMultifilter; 1248 } else if (typeof define === 'function' && define.amd) { 1249 define(function() { 1250 return mixitupMultifilter; 1251 }); 1252 } else if (window.mixitup && typeof window.mixitup === 'function') { 1253 mixitupMultifilter(window.mixitup); 1254 } else { 1255 throw new Error('[MixItUp MultiFilter] MixItUp core not found'); 1256 }})(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.