commit acd5401790747dc75b556c0a733a2db28f0b2b7e Author: Pascal Martineau Date: Fri Apr 10 08:15:15 2020 -0400 Initial v3.3.6 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f2fdb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +components +.tmp +.DS_Store +.log +coverage/ \ No newline at end of file diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..2d5d2e4 --- /dev/null +++ b/.jscsrc @@ -0,0 +1,95 @@ +{ + "requireCurlyBraces": [ + "else", + "for", + "while", + "do", + "try", + "catch" + ], + "requireSpaceAfterKeywords": [ + "if", + "else", + "for", + "while", + "do", + "case", + "return", + "try", + "typeof" + ], + "safeContextKeyword": ["self"], + "maximumLineLength": { + "value": 140, + "allowComments": true, + "allowRegex": true + }, + "requireSpaceBeforeBlockStatements": true, + "requireParenthesesAroundIIFE": true, + "requireSpaceAfterLineComment": { + "allExcept": ["#", "="] + }, + "requireSpacesInConditionalExpression": true, + "disallowSpacesInNamedFunctionExpression": { + "beforeOpeningRoundBrace": true + }, + "disallowSpacesInFunctionDeclaration": { + "beforeOpeningRoundBrace": true + }, + "disallowFunctionDeclarations": false, + "requireSpaceBetweenArguments": true, + "requireMultipleVarDecl": false, + "requireBlocksOnNewline": true, + "requireSemicolons": true, + "disallowEmptyBlocks": true, + "disallowSpacesInsideArrayBrackets": true, + "disallowSpacesInsideParentheses": true, + "requireCommaBeforeLineBreak": true, + "requireLineBreakAfterVariableAssignment": true, + "requirePaddingNewlinesBeforeKeywords": [ + "do", + "for", + "if", + "switch", + "try", + "void", + "while", + "return" + ], + "requirePaddingNewLinesInObjects": true, + "disallowSpaceAfterPrefixUnaryOperators": true, + "disallowSpaceBeforePostfixUnaryOperators": true, + "disallowSpaceBeforeBinaryOperators": [ + "," + ], + "requireSpacesInForStatement": true, + "requireSpaceBeforeBinaryOperators": true, + "requireSpaceAfterBinaryOperators": true, + "disallowKeywords": [ + "with" + ], + "validateIndentation": 4, + "disallowMixedSpacesAndTabs": true, + "disallowTrailingWhitespace": true, + "disallowTrailingComma": true, + "disallowKeywordsOnNewLine": [ + "else" + ], + "requireCapitalizedConstructors": true, + "disallowNewlineBeforeBlockStatements": true, + "disallowMultipleLineStrings": true, + "disallowMultipleLineBreaks": true, + "requireSpaceBeforeObjectValues": true, + "validateQuoteMarks": "'", + "jsDoc": { + "checkAnnotations": true, + "requireParamTypes": true, + "checkParamNames": true, + "checkParamExistence": true, + // "checkRedundantParams": true, + "checkReturnTypes": true, + "requireNewlineAfterDescription": true, + // "requireParamDescription": true, + "requireDescriptionCompleteSentence": true + } +} \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..456c0e9 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,23 @@ +{ + "esnext": true, + "-W030": true, + "-W053": true, + "-W084": true, + "-W055": true, + "-W069": true, + "-W097": true, + "undef": true, + "unused": true, + "laxbreak": true, + "node": true, + "browser": true, + "predef": [ + "window", + "Promise", + "define", + "console", + "describe", + "after", + "it" + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4e37500 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,83 @@ +Change Log +========== + +## 3.3.6 +- Fixes a bug accidentally introduced in v3.3.5 which resulted in a fatal exception on initialisation when using "programmatic filtering by URL" and fieldsets with `` elements within them. + +## 3.3.5 +- Fixes a bug that prevented `.getFilterGroupSelectors()` from returning any values when using data-filter controls. + +## 3.3.4 +- Fixes a bug whereby filters within a group where not deactivated when toggles in the same group were activated. +- Adds a new demo "Filter and Toggle Controls" to demonstrate functionality. + +## 3.3.3 +- Fixes a bug causing filter controls to be treated like toggle controls when in a mixed field group. + +## 3.3.2 + +- Implements diacritics replacement of input value when searching by text input (e.g. "é" -> "e"). Allows loose matching of accented characters when searching providing an equivalent server-side operation is done to all values present in the HTML. + +## 3.3.1 + +- Fixes issue where a `` elements were ignored. + +## 3.1.0 + +- Integrates with `selectors.controls` configuration option added to MixItUp core 3.1.0 to add specificity to control +selectors and prevent interference by third-party markup which may share the mandatory control data attributes. + +## 3.0.3 + +- Trims and removes non-alphanumeric characters from text input values before selector generation. Adds text inputs demo. + +## 3.0.2 + +- Makes text input searching case-insensitive by converting to lowercase before selector generation. + +## 3.0.1 + +- Fixes issue where e.preventDefault() was called on reset events preventing reset functionality. Many additional demos added. + +## 3.0.0 + +- Release + + + + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3910a99 --- /dev/null +++ b/README.md @@ -0,0 +1,346 @@ +# MixItUp MultiFilter + +MixItUp MultiFilter is a premium extension for MixItUp 3 and makes building multidimensional filtering UI effortless. + +### Features + +- Filter content by multiple dimensions or "filter groups" simultaneously +- Use any combination of AND/OR logic within and between filter groups +- Combine a wide variety of native form UI: buttons, selects, checkboxes, radio, and text inputs +- New API methods +- New configuration options + +### Uses + +- User interfaces where content must be searchable by two or more attributes (e.g. type, color, size) +- Perfect for E-commerce interfaces + +### Limitations + +- Client-side only - the entire target collection must exist in the DOM + +*NB: If you're looking to integrate server-side ajax multidimensional with MixItUp, consider using MixItUp 3's Dataset API.* + +## Get Started + +### Installing Extensions + +Premium extensions are not publicly available via GitHub or NPM and must therefore be downloaded from your KunkaLabs account after purchase. Once downloaded they can be included in your project in a directory of your choosing, and then required as modules, or globally via a script tag. + +#### Script Tag + +If using a script tag, you simply need to load the multifilter distribution script (i.e. `mixitup-multifilter.min.js`) **after** mixitup, and the extension will automatically detect the `mixitup` global variable and install itself. + +```html + ... + + + + + +``` + +#### Module Import + +If you are building a modular JavaScript project with Webpack, Browserify, or RequireJS, no global variables are exposed. Firstly require both the MixItUp core *and* the MultiFilter extension into your module. Then call `mixitup.use()` with the extension passed in as an argument. Your extension will be installed and made available to all MixItUp instances throughout your project. + +```js +// ES2015 + +import mixitup from 'mixitup'; // loaded from node_modules +import mixitupMultifilter from '../path/to/mixitup-multifilter'; // loaded from a directory of your choice within your project + +// Call the mixitup factory's .use() method, passing in the extension to install it + +mixitup.use(mixitupMultifilter); +``` + +```js +// CommonJS + +var mixitup = require('mixitup'); +var mixitupMultifilter = require('../path/to/mixitup-mulfitiler'); + +mixitup.use(mixitupMultifilter); +``` + +```js +// AMD + +require([ + 'mixitup', + '../path/to/mixitup-multifilter' +], function( + mixitup, + mixitupMultifilter +) { + mixitup.use(mixitupMultifilter); +}); +``` + +You need only call the `.use()` function once per project, per extension, as module loaders will cache a single reference to MixItUp inclusive of all changes made. + +### Using MultiFilter + +MixItUp MultiFilter extends MixItUp's API and configuration object with various new methods and properties, and adds the ability to query and index groups of filter controls, known as "filter groups". + +By default, multifilter functionality is disabled so that you can use MixItUp as normal, even with the extension installed. To enable multifilter functionality for a mixer, you simply need set the `multifilter.enable` configuration option to `true`. + +```js +var mixer = mixitup(containerEl, { + multifilter: { + enable: true // enable the multifilter extension for the mixer + } +}); +``` + +### Filtering Dimensions + +MixItUp MultiFilter is designed to be used with content requiring filtering by multiple distinct attributes or "dimensions". Taking a clothing store as an example, some dimensions might include **type** (e.g. sweaters, shirts, jackets), **color** (red, green, blue), and **size** (small, medium, large). + +Another example is our [sandbox demo](https://www.kunkalabs.com/mixitup-multifilter/) which uses the dimensions of **shape** (e.g. square, triangle, circle), **color**, and **size**. + +MixItUp MultiFilter allows us to combine filter selectors within each of these dimensions, using a variety of logic. To learn more about MixItUp filter logic, you may wish to read the [Filtering with MixItUp](https://www.kunkalabs.com/tutorials/filtering-with-mixitup/) tutorial before continuing. + +### Filter Group UI + +Filter groups are an essential part of a multifilter-enabled mixer and are defined in your markup. Each group represents a particular dimension, and can contain a many different types of UI within it. Filter groups are queried by MixItUp based on the presence of a `data-filter-group` attribute. You may use any outer element as a filter group, but `
` makes sense semantically, particularly when using inputs like checkboxes or radios within it. + +You can think of your entire multifilter UI as a `
` with which to interact with your mixer. + +```html + +
+ + + +
+ +
+ + + +
+
+``` + +*Wrapping filter groups in a parent `
` is optional, but provides the ability to use submit and reset buttons as part of your UI.* + +In the example above, **filter control** buttons are used in the first group, and *toggle control* buttons are used in the second group. To recap the [Filtering with MixItUp](https://www.kunkalabs.com/tutorials/filtering-with-mixitup/) tutorial, filter controls allow one active control at a time, and toggle controls allow multiple active controls simultaneously. This behavior is also true when using filter groups, but is confined to within the group. Therefore, the UI above would allow us to select one type of clothing, but multiple colors. + +Filter and toggle controls are just two example of the different types of UI available in MixItUp MultiFilter. Let's take a look at what else is available: + +##### Radios + +```html +
+ + + + + + + + +
+``` + +Providing the same functionality as MixItUp's filter controls (one active input at a time), radios provide a more form-like UI with a smaller footprint. + +##### Checkboxes + +```html +
+ + + + + + + + +
+``` + +Providing the same functionality as MixItUp's toggle controls (multiple active inputs at a time), checkboxes also provide a more form-like UI with a smaller footprint. + +##### Selects + +```html +
+ +
+``` + +Selects can be used to select one value from many values with a very small footprint. If the multiple attribute is added (` +
+``` + +Text inputs can be used to search targets by a partial or complete string. A `data-search-attribute` must be added to the input to tell MixItUp which attribute to search. The example above searches the `class` attribute, consistent with the other examples. For example, entering the string `'Gree'` would show any targets with the class `'green'`. + +NB: The value of your text input will be converted to lowercase before searching, so ensure that the contents of the attribute you intend to search are also lowercase. + +### Configuring Filter Group Logic + +MixItUp MultiFilter allows us to define one type of logic between filter groups and another type within them, using the configuration options `multifilter.logicBetween` and `multifilter.logicWithin`. + +The most common (and default) combination of logic is `'or'` within groups and `'and'` between. It should be noted that this combination will suit the vast majority of multifiltering projects, but you may configure either of these properties to be `'and'` and `'or'` as necessary. + +You may also override the `multifilter.logicWithin` setting on a per-group basis by adding a `data-logic` attribute to your filter group's markup, with a value of either `'or'` or `'and'`: + +###### Example: Overriding multifilter.logicWithin for a specific group + +```html +
+``` + +To visualize what's meant by combining logic between groups, consider the following: + +**Show (shirts OR sweaters) AND (red OR blue) AND Large** + +This would be an example or `'or'` logic within groups, and `'and'` logic between, and as noted above, is the most common combination of logic. Given this combination of active UI elements, MixItUp MultiFilter will generate the following selector string with which to filter the content of your container: + +``` +'.shirt.red.large, .shirt.blue.large, .sweater.red.large, .sweater.blue.large' +``` + +To help with debugging and development you may wish to console log the generated selector using one of MixItUp's callback functions. For example: + +###### Example: Logging the generated filter selector using the onMixStart callback + +```js +var mixer = mixitup('.container', { + multifilter: { + enable: true + }, + callbacks: { + onMixStart: function(state, futureState) { + console.log(futureState.activeFilter.selector); + } + } +}); +``` + +In addition to the configuration options already mentioned, MixItUp MultiFilter also makes use of several existing configuration options from the MixItUp core. These include: + +#### controls.toggleDefault + +Defines the toggle filter state when all controls are deactivated. The available options are 'all' (default), or 'none'. + +#### controls.scope + +Defines whether multifilter UI should be isolated to a specific mixer (`'local'`), or global to the document (`'global'`). + +See the [Configuration Object](https://www.kunkalabs.com/mixitup-multifilter/docs/configuration-object/) documentation page for more information about configuring MixItUp MultiFilter. + +### Additional Form Behavior + +As mentioned previously, we can think of our multifilter UI as a `` with which to interact with our mixer. Adding a parent a `` element to our UI also provides some additional benefits: + +#### Reset Buttons + +Reset buttons can be included within a form of filter groups to clear and reset all active UI and return the mixer to its default filter state (see `controls.toggleDefault`). + +This is a very common UI pattern in search interfaces and allows the user to quickly return to full set of content without having to deactivate every UI element individually. + +```html + +
+ ... +
+ +
+ ... +
+ + + +``` + +Similarly, if we want the ability reset an individual filter group, we can use individual forms as filter group elements, and nest reset buttons within them: + +```html +
+
+ ... + +
+ +
+ ... + +
+
+``` + +#### Submit Buttons + +The default behavior of MixItUp MultiFilter is to trigger a filter operation whenever a control is clicked, or an input value changes. This allows the user to see the content change in real time as they interact with the UI. + +By adding a submit button and changing the value of the `ultifilter.parseOn` configuration option from `'change'` to `'submit'`, we can delay the filter operation until the user has made their selection and is ready to search. The filter operation will then be triggered only when the user clicks the submit button. + +###### Example: Changing the multifilter.parseOn configuration option + +```js +var mixer = mixitup('.container', { + multifilter: { + parseOn: 'submit' + } +}); +``` + +```html +
+
+ ... +
+ +
+ ... +
+ + + +
+``` + +To prevent users from accidentally triggering an HTTP form submission by clicking submit before your application's JavaScript has loaded, you may add a disabled attribute to the submit button, and MixItUp MultiFilter will remove it once it has parsed your markup. + +```html + +``` + +#### Named Filter Groups + +If we wish to interact with our filter groups via the `.setFilterGroupSelectors()` API method (see [Mixer API Methods](https://www.kunkalabs.com/mixitup-multifilter/api-methods/)), we can name our groups by providing a value to `data-filter-group` attribute. + +```html +
+
+ ... +
+ +
+ ... +
+
+``` + +We can then target that group by its name when we call the API method: + +```js +mixer.setFilterGroupSelectors('color', ['.green', '.blue']); + +mixer.parseFilterGroups() +``` \ No newline at end of file diff --git a/demos/checkboxes/index.html b/demos/checkboxes/index.html new file mode 100644 index 0000000..e947293 --- /dev/null +++ b/demos/checkboxes/index.html @@ -0,0 +1,116 @@ + + + + + + + + + MixItUp MultiFilter Demo - Checkboxes + + +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/checkboxes/style.css b/demos/checkboxes/style.css new file mode 100644 index 0000000..97a97d4 --- /dev/null +++ b/demos/checkboxes/style.css @@ -0,0 +1,266 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.checkbox-group { + display: inline-block; + padding: .5rem; + background: #2a2a2a; + margin-left: .75rem; + vertical-align: top; +} + +.checkbox { + text-align: justify; +} + +.checkbox:after { + content: ''; + display: inline-block; + width: 100%; +} + +.checkbox-input, +.checkbox-label { + display: inline-block; +} + +.checkbox-group-label, +.checkbox-label { + color: white; + font-family: 'helvetica-neue', arial, sans-serif; + font-size: .9rem; +} + +.checkbox-group-label { + font-weight: bold; + display: block; + margin-bottom: .5rem; +} + +.checkbox-label { + margin-right: .5rem; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/demos/filters-and-toggles/index.html b/demos/filters-and-toggles/index.html new file mode 100644 index 0000000..6ea5092 --- /dev/null +++ b/demos/filters-and-toggles/index.html @@ -0,0 +1,86 @@ + + + + + + + + + MixItUp MultiFilter Demo - Filter and Toggle Controls + + +
+ + +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + +
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/filters-and-toggles/style.css b/demos/filters-and-toggles/style.css new file mode 100644 index 0000000..177f59e --- /dev/null +++ b/demos/filters-and-toggles/style.css @@ -0,0 +1,301 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + vertical-align: top; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-color:after, +.control-shape:after, +.control-size:after { + content: ''; + position: absolute; +} + +.control-color:after { + width: 10px; + height: 10px; + top: calc(50% - 6px); + left: calc(50% - 6px); + border: 2px solid currentColor; + border-radius: 2px; + background: currentColor; + transition: background-color 150ms, border-color 150ms; +} + +.control-color[data-toggle=".green"] { + color: #91e6c7; +} + +.control-color[data-toggle=".blue"] { + color: #5ecdde; +} + +.control-color[data-toggle=".pink"] { + color: #d595aa; +} + +.control-shape[data-toggle=".square"]:after, +.control-shape[data-toggle=".circle"]:after { + width: 10px; + height: 10px; + top: calc(50% - 5px); + left: calc(50% - 5px); + background: white; + border-radius: 2px; +} + +.control-shape[data-toggle=".circle"]:after { + border-radius: 10px; +} + +.control-shape[data-toggle=".triangle"]:after { + border: 6px solid transparent; + border-bottom: 9px solid white; + top: calc(50% - 10px); + left: calc(50% - 6px); +} + +.control-size:after { + width: 8px; + height: 8px; + top: calc(50% - 4px); + left: calc(50% - 4px); + border: 2px solid white; + border-radius: 2px; +} + +.control-size[data-toggle=".medium"]:after { + width: 12px; + height: 12px; + top: calc(50% - 6px); + left: calc(50% - 6px); +} + +.control-size[data-toggle=".large"]:after { + width: 16px; + height: 16px; + top: calc(50% - 8px); + left: calc(50% - 8px); +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/demos/filters/index.html b/demos/filters/index.html new file mode 100644 index 0000000..45b335f --- /dev/null +++ b/demos/filters/index.html @@ -0,0 +1,83 @@ + + + + + + + + + MixItUp MultiFilter Demo - Filter Controls + + +
+ + +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/filters/style.css b/demos/filters/style.css new file mode 100644 index 0000000..31dc1bd --- /dev/null +++ b/demos/filters/style.css @@ -0,0 +1,300 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-color:after, +.control-shape:after, +.control-size:after { + content: ''; + position: absolute; +} + +.control-color:after { + width: 10px; + height: 10px; + top: calc(50% - 6px); + left: calc(50% - 6px); + border: 2px solid currentColor; + border-radius: 2px; + background: currentColor; + transition: background-color 150ms, border-color 150ms; +} + +.control-color[data-filter=".green"] { + color: #91e6c7; +} + +.control-color[data-filter=".blue"] { + color: #5ecdde; +} + +.control-color[data-filter=".pink"] { + color: #d595aa; +} + +.control-shape[data-filter=".square"]:after, +.control-shape[data-filter=".circle"]:after { + width: 10px; + height: 10px; + top: calc(50% - 5px); + left: calc(50% - 5px); + background: white; + border-radius: 2px; +} + +.control-shape[data-filter=".circle"]:after { + border-radius: 10px; +} + +.control-shape[data-filter=".triangle"]:after { + border: 6px solid transparent; + border-bottom: 9px solid white; + top: calc(50% - 10px); + left: calc(50% - 6px); +} + +.control-size:after { + width: 8px; + height: 8px; + top: calc(50% - 4px); + left: calc(50% - 4px); + border: 2px solid white; + border-radius: 2px; +} + +.control-size[data-filter=".medium"]:after { + width: 12px; + height: 12px; + top: calc(50% - 6px); + left: calc(50% - 6px); +} + +.control-size[data-filter=".large"]:after { + width: 16px; + height: 16px; + top: calc(50% - 8px); + left: calc(50% - 8px); +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/demos/index.html b/demos/index.html new file mode 100644 index 0000000..1471991 --- /dev/null +++ b/demos/index.html @@ -0,0 +1,23 @@ + + + + MixItUp Multifilter Demos + + +

MixItUp Multifilter Demos

+ + + + \ No newline at end of file diff --git a/demos/mixitup-multifilter.min.js b/demos/mixitup-multifilter.min.js new file mode 100644 index 0000000..a216317 --- /dev/null +++ b/demos/mixitup-multifilter.min.js @@ -0,0 +1,18 @@ +/**! + * MixItUp MultiFilter v3.3.6 + * A UI-builder for powerful multidimensional filtering + * Build 293e0dda-087e-4a76-aadf-e3e8b311b81f + * + * Requires mixitup.js >= v^3.1.2 + * + * @copyright Copyright 2014-2020 KunkaLabs Limited. + * @author KunkaLabs Limited. + * @link https://www.kunkalabs.com/mixitup-multifilter/ + * + * @license Commercial use requires a commercial license. + * https://www.kunkalabs.com/mixitup-multifilter/licenses/ + * + * Non-commercial use permitted under same terms as license. + * http://creativecommons.org/licenses/by-nc/3.0/ + */ +!function(u){"use strict";var e=function(u){var t,i=u.h;if(t=[["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],["AA",/[\uA732]/g],["AE",/[\u00C6\u01FC\u01E2]/g],["AO",/[\uA734]/g],["AU",/[\uA736]/g],["AV",/[\uA738\uA73A]/g],["AY",/[\uA73C]/g],["B",/[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g],["C",/[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g],["D",/[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g],["DZ",/[\u01F1\u01C4]/g],["Dz",/[\u01F2\u01C5]/g],["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],["F",/[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g],["G",/[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g],["H",/[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g],["I",/[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g],["J",/[\u004A\u24BF\uFF2A\u0134\u0248]/g],["K",/[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g],["L",/[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g],["LJ",/[\u01C7]/g],["Lj",/[\u01C8]/g],["M",/[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g],["N",/[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g],["NJ",/[\u01CA]/g],["Nj",/[\u01CB]/g],["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],["OI",/[\u01A2]/g],["OO",/[\uA74E]/g],["OU",/[\u0222]/g],["P",/[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g],["Q",/[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g],["R",/[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g],["S",/[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g],["T",/[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g],["TZ",/[\uA728]/g],["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],["V",/[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g],["VY",/[\uA760]/g],["W",/[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g],["X",/[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g],["Y",/[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g],["Z",/[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g],["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],["aa",/[\uA733]/g],["ae",/[\u00E6\u01FD\u01E3]/g],["ao",/[\uA735]/g],["au",/[\uA737]/g],["av",/[\uA739\uA73B]/g],["ay",/[\uA73D]/g],["b",/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g],["c",/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g],["d",/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g],["dz",/[\u01F3\u01C6]/g],["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],["f",/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g],["g",/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g],["h",/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g],["hv",/[\u0195]/g],["i",/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g],["j",/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g],["k",/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g],["l",/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g],["lj",/[\u01C9]/g],["m",/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g],["n",/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g],["nj",/[\u01CC]/g],["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],["oi",/[\u01A3]/g],["ou",/[\u0223]/g],["oo",/[\uA74F]/g],["p",/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g],["q",/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g],["r",/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g],["s",/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g],["t",/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g],["tz",/[\uA729]/g],["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],["v",/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g],["vy",/[\uA761]/g],["w",/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g],["x",/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g],["y",/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g],["z",/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g]],!u.CORE_VERSION||!i.compareVersions(e.REQUIRE_CORE_VERSION,u.CORE_VERSION))throw new Error("[MixItUp Multifilter] MixItUp Multifilter v"+e.EXTENSION_VERSION+" requires at least MixItUp v"+e.REQUIRE_CORE_VERSION);u.ConfigCallbacks.registerAction("afterConstruct","multifilter",function(){this.onParseFilterGroups=null}),u.ConfigMultifilter=function(){this.enable=!1,this.logicWithinGroup="or",this.logicBetweenGroups="and",this.minSearchLength=3,this.parseOn="change",this.keyupThrottleDuration=350,i.seal(this)},u.Config.registerAction("beforeConstruct","multifilter",function(){this.multifilter=new u.ConfigMultifilter}),u.MultifilterFormEventTracker=function(){this.form=null,this.totalBound=0,this.totalHandled=0,i.seal(this)},u.FilterGroupDom=function(){this.el=null,this.form=null,i.seal(this)},u.FilterGroup=function(){this.name="",this.dom=new u.FilterGroupDom,this.activeSelectors=[],this.activeFilters=[],this.activeToggles=[],this.handler=null,this.mixer=null,this.logic="or",this.parseOn="change",this.keyupTimeout=-1,i.seal(this)},i.extend(u.FilterGroup.prototype,{init:function(u,e){var t=this,i=u.getAttribute("data-logic");t.dom.el=u,this.name=t.dom.el.getAttribute("data-filter-group")||"",t.cacheDom(),t.dom.form&&t.enableButtons(),t.mixer=e,(i&&"and"===i.toLowerCase()||"and"===e.config.multifilter.logicWithinGroup)&&(t.logic="and"),t.bindEvents()},cacheDom:function(){var u=this;u.dom.form=i.closestParent(u.dom.el,"form",!0)},enableButtons:function(){var u=this,e=u.dom.form.querySelectorAll('button[type="submit"]:disabled'),t=null,i=-1;for(i=0;t=e[i];i++)t.disabled&&(t.disabled=!1)},bindEvents:function(){var u=this;u.handler=function(e){switch(e.type){case"reset":case"submit":u.handleFormEvent(e);break;default:u["handle"+i.pascalCase(e.type)](e)}},i.on(u.dom.el,"click",u.handler),i.on(u.dom.el,"change",u.handler),i.on(u.dom.el,"keyup",u.handler),u.dom.form&&(i.on(u.dom.form,"reset",u.handler),i.on(u.dom.form,"submit",u.handler))},unbindEvents:function(){var u=this;i.off(u.dom.el,"click",u.handler),i.off(u.dom.el,"change",u.handler),i.off(u.dom.el,"keyup",u.handler),u.dom.form&&(i.off(u.dom.form,"reset",u.handler),i.off(u.dom.form,"submit",u.handler)),u.handler=null},handleClick:function(u){var e=this,t=e.mixer,r=null,l=i.closestParent(u.target,"[data-filter], [data-toggle]",!0),o="",n=-1,E="";l&&((o=e.mixer.config.selectors.control)&&!l.matches(o)||(u.stopPropagation(),t.lastClicked||(t.lastClicked=l),"function"==typeof t.config.callbacks.onMixClick&&(r=t.config.callbacks.onMixClick.call(t.lastClicked,t.state,u,e),r===!1)||(l.matches("[data-filter]")?(E=l.getAttribute("data-filter"),e.activeToggles=[],e.activeSelectors=e.activeFilters=[E]):l.matches("[data-toggle]")&&(E=l.getAttribute("data-toggle"),e.activeFilters=[],(n=e.activeToggles.indexOf(E))>-1?e.activeToggles.splice(n,1):e.activeToggles.push(E),"and"===e.logic?e.activeSelectors=[e.activeToggles]:e.activeSelectors=e.activeToggles),e.updateControls(),"change"===e.mixer.config.multifilter.parseOn&&e.mixer.parseFilterGroups())))},handleChange:function(u){var e=this,t=u.target;switch(u.stopPropagation(),t.type){case"text":case"search":case"email":case"select-one":case"radio":e.getSingleValue(t);break;case"checkbox":case"select-multiple":e.getMultipleValues(t)}"change"===e.mixer.config.multifilter.parseOn&&e.mixer.parseFilterGroups()},handleKeyup:function(u){var e=this,t=u.target;if(!(["text","search","email"].indexOf(t.type)<0)){if("change"!==e.mixer.config.multifilter.parseOn)return void e.mixer.getSingleValue(t);clearTimeout(e.keyupTimeout),e.keyupTimeout=setTimeout(function(){e.getSingleValue(t),e.mixer.parseFilterGroups()},e.mixer.config.multifilter.keyupThrottleDuration)}},handleFormEvent:function(e){var t=this,i=null,r=null,l=-1;if("submit"===e.type&&e.preventDefault(),"reset"===e.type&&(t.activeFilters=t.activeToggles=t.activeSelectors=[],t.updateControls()),t.mixer.multifilterFormEventTracker)i=t.mixer.multifilterFormEventTracker;else for(i=t.mixer.multifilterFormEventTracker=new u.MultifilterFormEventTracker,i.form=e.target,l=0;r=t.mixer.filterGroups[l];l++)r.dom.form===e.target&&i.totalBound++;e.target===i.form&&(i.totalHandled++,i.totalHandled===i.totalBound&&(t.mixer.multifilterFormEventTracker=null,"submit"!==e.type&&"change"!==t.mixer.config.multifilter.parseOn||t.mixer.parseFilterGroups()))},getSingleValue:function(u){var e=this,i=null,r="",l="",o="",n=-1;if(u.type.match(/text|search|email/g)){if(r=u.getAttribute("data-search-attribute"),!r)throw new Error("[MixItUp MultiFilter] A valid `data-search-attribute` must be present on text inputs");if(u.value.length-1?i.addClass(u,o):i.removeClass(u,o)},updateUi:function(){var u=this,e=u.dom.el.querySelectorAll("[data-filter], [data-toggle]"),t=u.dom.el.querySelectorAll('input[type="radio"], input[type="checkbox"], option'),i=u.activeToggles.concat(u.activeFilters),r=!1,l=null,o=-1;for(e.length&&u.updateControls(e,!0),o=0;l=t[o];o++)switch(r=i.indexOf(l.value)>-1,l.tagName.toLowerCase()){case"option":l.selected=r;break;case"input":l.checked=r}}}),u.MixerDom.registerAction("afterConstruct","multifilter",function(){this.filterGroups=[]}),u.Mixer.registerAction("afterConstruct","multifilter",function(){this.filterGroups=[],this.filterGroupsHash={},this.multifilterFormEventTracker=null}),u.Mixer.registerAction("afterCacheDom","multifilter",function(){var e=this,t=null;if(e.config.multifilter.enable){switch(e.config.controls.scope){case"local":t=e.dom.container;break;case"global":t=e.dom.document;break;default:throw new Error(u.messages.ERROR_CONFIG_INVALID_CONTROLS_SCOPE)}e.dom.filterGroups=t.querySelectorAll("[data-filter-group]")}}),u.Mixer.registerAction("beforeInitControls","multifilter",function(){var u=this;u.config.multifilter.enable&&(u.config.controls.live=!0)}),u.Mixer.registerAction("afterSanitizeConfig","multifilter",function(){var u=this;u.config.multifilter.logicBetweenGroups=u.config.multifilter.logicBetweenGroups.toLowerCase().trim(),u.config.multifilter.logicWithinGroup=u.config.multifilter.logicWithinGroup.toLowerCase().trim()}),u.Mixer.registerAction("afterAttach","multifilter",function(){var u=this;u.dom.filterGroups.length&&u.indexFilterGroups()}),u.Mixer.registerAction("afterUpdateControls","multifilter",function(){var u=this,e=null,t=-1;for(t=0;e=u.filterGroups[t];t++)e.updateControls()}),u.Mixer.registerAction("beforeDestroy","multifilter",function(){var u=this,e=null,t=-1;for(t=0;e=u.filterGroups[t];t++)e.unbindEvents()}),u.Mixer.extend({indexFilterGroups:function(){var e=this,t=null,i=null,r=-1;for(r=0;i=e.dom.filterGroups[r];r++)if(t=new u.FilterGroup,t.init(i,e),e.filterGroups.push(t),t.name){if("undefined"!=typeof e.filterGroupsHash[t.name])throw new Error('[MixItUp MultiFilter] A filter group with name "'+t.name+'" already exists');e.filterGroupsHash[t.name]=t}},parseParseFilterGroupsArgs:function(e){var t=this,r=new u.UserInstruction,l=null,o=-1;for(r.animate=t.config.animation.enable,r.command=new u.CommandFilter,o=0;o1){for(o=0;o-1}}(t.Element.prototype),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!1,n=[],a=-1;return e=!{toString:null}.propertyIsEnumerable("toString"),n=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],a=n.length,function(i){var o=[],r="",s=-1;if("object"!=typeof i&&("function"!=typeof i||null===i))throw new TypeError("Object.keys called on non-object");for(r in i)t.call(i,r)&&o.push(r);if(e)for(s=0;s>>0,0===i)return-1;if(e=0,arguments.length>1&&(e=Number(arguments[1]),e!==e?e=0:0!==e&&e!==1/0&&e!==-(1/0)&&(e=(e>0||-1)*Math.floor(Math.abs(e)))),e>=i)return-1;for(n=e>=0?e:Math.max(i-Math.abs(e),0);n0)||s);g++)r.id?d=r.id:(d="MixItUp"+n.randomHex(),r.id=d),e.instances[d]instanceof e.Mixer?(l=e.instances[d],(!i||i&&i.debug&&i.debug.showWarnings!==!1)&&console.warn(e.messages.warningFactoryPreexistingInstance())):(l=new e.Mixer,l.attach(r,u,d,i),e.instances[d]=l),c=new e.Facade(l),i&&i.debug&&i.debug.enable?h.push(l):h.push(c);return f=s?new e.Collection(h):h[0]},e.use=function(t){e.Base.prototype.callActions.call(e,"beforeUse",arguments),"function"==typeof t&&"mixitup-extension"===t.TYPE?"undefined"==typeof e.extensions[t.NAME]&&(t(e),e.extensions[t.NAME]=t):t.fn&&t.fn.jquery&&(e.libraries.$=t),e.Base.prototype.callActions.call(e,"afterUse",arguments)},e.instances={},e.extensions={},e.libraries={},n={hasClass:function(t,e){return!!t.className.match(new RegExp("(\\s|^)"+e+"(\\s|$)"))},addClass:function(t,e){this.hasClass(t,e)||(t.className+=t.className?" "+e:e)},removeClass:function(t,e){if(this.hasClass(t,e)){var n=new RegExp("(\\s|^)"+e+"(\\s|$)");t.className=t.className.replace(n," ").trim()}},extend:function(t,e,n,a){var i=[],o="",r=-1;n=n||!1,a=a||!1;try{if(Array.isArray(e))for(r=0;ru&&(u=f,l=c)}throw u>1&&(s=e.messages.errorConfigInvalidPropertySuggestion({probableMatch:l})),r=e.messages.errorConfigInvalidProperty({erroneous:o,suggestion:s}),new TypeError(r)}throw t},template:function(t){for(var e=/\${([\w]*)}/g,n={},a=null;a=e.exec(t);)n[a[1]]=new RegExp("\\${"+a[1]+"}","g");return function(e){var a="",i=t;e=e||{};for(a in n)i=i.replace(n[a],"undefined"!=typeof e[a]?e[a]:"");return i}},on:function(e,n,a,i){e&&(e.addEventListener?e.addEventListener(n,a,i):e.attachEvent&&(e["e"+n+a]=a,e[n+a]=function(){e["e"+n+a](t.event)},e.attachEvent("on"+n,e[n+a])))},off:function(t,e,n){t&&(t.removeEventListener?t.removeEventListener(e,n,!1):t.detachEvent&&(t.detachEvent("on"+e,t[e+n]),t[e+n]=null))},getCustomEvent:function(e,n,a){var i=null;return a=a||t.document,"function"==typeof t.CustomEvent?i=new t.CustomEvent(e,{detail:n,bubbles:!0,cancelable:!0}):"function"==typeof a.createEvent?(i=a.createEvent("CustomEvent"),i.initCustomEvent(e,!0,!0,n)):(i=a.createEventObject(),i.type=e,i.returnValue=!1,i.cancelBubble=!1,i.detail=n),i},getOriginalEvent:function(t){return t.touches&&t.touches.length?t.touches[0]:t.changedTouches&&t.changedTouches.length?t.changedTouches[0]:t},index:function(t,e){for(var n=0;null!==(t=t.previousElementSibling);)e&&!t.matches(e)||++n;return n},camelCase:function(t){return t.toLowerCase().replace(/([_-][a-z])/g,function(t){return t.toUpperCase().replace(/[_-]/,"")})},pascalCase:function(t){return(t=this.camelCase(t)).charAt(0).toUpperCase()+t.slice(1)},dashCase:function(t){return t.replace(/([A-Z])/g,"-$1").replace(/^-/,"").toLowerCase()},isElement:function(e,n){return n=n||t.document,!!(t.HTMLElement&&e instanceof t.HTMLElement)||(!!(n.defaultView&&n.defaultView.HTMLElement&&e instanceof n.defaultView.HTMLElement)||null!==e&&1===e.nodeType&&"string"==typeof e.nodeName)},createElement:function(e,n){var a=null,i=null;for(n=n||t.document,a=n.createDocumentFragment(),i=n.createElement("div"),i.innerHTML=e.trim();i.firstChild;)a.appendChild(i.firstChild);return a},removeWhitespace:function(t){for(var e;t&&"#text"===t.nodeName;)e=t,t=t.previousSibling,e.parentElement&&e.parentElement.removeChild(e)},isEqualArray:function(t,e){var n=t.length;if(n!==e.length)return!1;for(;n--;)if(t[n]!==e[n])return!1;return!0},deepEquals:function(t,e){var n;if("object"==typeof t&&t&&"object"==typeof e&&e){if(Object.keys(t).length!==Object.keys(e).length)return!1;for(n in t)if(!e.hasOwnProperty(n)||!this.deepEquals(t[n],e[n]))return!1}else if(t!==e)return!1;return!0},arrayShuffle:function(t){for(var e=t.slice(),n=e.length,a=n,i=-1,o=[];a--;)i=~~(Math.random()*n),o=e[a],e[a]=e[i],e[i]=o;return e},arrayFromList:function(t){var e,n;try{return Array.prototype.slice.call(t)}catch(a){for(e=[],n=0;n "+n),o&&e.removeAttribute("id")),i},clean:function(t){var e=[],n=-1;for(n=0;ni)return!0}return!0},Deferred:function(){this.promise=null,this.resolve=null,this.reject=null,this.id=n.randomHex()},isEmptyObject:function(t){var e="";if("function"==typeof Object.keys)return 0===Object.keys(t).length;for(e in t)if(t.hasOwnProperty(e))return!1;return!0},getClassname:function(t,e,n){var a="";return a+=t.block,a.length&&(a+=t.delineatorElement),a+=t["element"+this.pascalCase(e)],n?(a.length&&(a+=t.delineatorModifier),a+=n):a},getProperty:function(t,e){var n=e.split("."),a=null,i="",o=0;if(!e)return t;for(a=function(t){return t?t[i]:null};o-1,e.callFilters("afterIsBound",n,arguments)},addBinding:function(t){var e=this;this.callActions("beforeAddBinding",arguments),e.isBound()||e.bound.push(t),this.callActions("afterAddBinding",arguments)},removeBinding:function(t){var n=this,a=-1;this.callActions("beforeRemoveBinding",arguments),(a=n.bound.indexOf(t))>-1&&n.bound.splice(a,1),n.bound.length<1&&(n.unbindClick(),a=e.controls.indexOf(n),e.controls.splice(a,1),"active"===n.status&&n.renderStatus(n.el,"inactive")),this.callActions("afterRemoveBinding",arguments)},bindClick:function(){var t=this;this.callActions("beforeBindClick",arguments),t.handler=function(e){t.handleClick(e)},n.on(t.el,"click",t.handler),this.callActions("afterBindClick",arguments)},unbindClick:function(){var t=this;this.callActions("beforeUnbindClick",arguments),n.off(t.el,"click",t.handler),t.handler=null,this.callActions("afterUnbindClick",arguments)},handleClick:function(t){var a=this,i=null,o=null,r=!1,s=void 0,l={},c=null,u=[],f=-1;if(this.callActions("beforeHandleClick",arguments),this.pending=0,o=a.bound[0],i=a.selector?n.closestParent(t.target,o.config.selectors.control+a.selector,!0,o.dom.document):a.el,!i)return void a.callActions("afterHandleClick",arguments);switch(a.type){case"filter":l.filter=a.filter||i.getAttribute("data-filter");break;case"sort":l.sort=a.sort||i.getAttribute("data-sort");break;case"multimix":l.filter=a.filter||i.getAttribute("data-filter"),l.sort=a.sort||i.getAttribute("data-sort");break;case"toggle":l.filter=a.filter||i.getAttribute("data-toggle"),r="live"===a.status?n.hasClass(i,a.classNames.active):"active"===a.status}for(f=0;f0||("live"===a.status?a.updateLive(t,n):(i.sort=a.sort,i.filter=a.filter,a.callFilters("actionsUpdate",i,arguments),a.parseStatusChange(a.el,t,i,n)),a.callActions("afterUpdate",arguments))},updateLive:function(t,n){var a=this,i=null,o=null,r=null,s=-1;if(a.callActions("beforeUpdateLive",arguments),a.el){for(i=a.el.querySelectorAll(a.selector),s=0;r=i[s];s++){switch(o=new e.CommandMultimix,a.type){case"filter":o.filter=r.getAttribute("data-filter");break;case"sort":o.sort=r.getAttribute("data-sort");break;case"multimix":o.filter=r.getAttribute("data-filter"),o.sort=r.getAttribute("data-sort");break;case"toggle":o.filter=r.getAttribute("data-toggle")}o=a.callFilters("actionsUpdateLive",o,arguments),a.parseStatusChange(r,t,o,n)}a.callActions("afterUpdateLive",arguments)}},parseStatusChange:function(t,e,n,a){var i=this,o="",r="",s=-1;switch(i.callActions("beforeParseStatusChange",arguments),i.type){case"filter":e.filter===n.filter?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"multimix":e.sort===n.sort&&e.filter===n.filter?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"sort":e.sort.match(/:asc/g)&&(o=e.sort.replace(/:asc/g,"")),e.sort===n.sort||o===n.sort?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"toggle":for(a.length<1&&i.renderStatus(t,"inactive"),e.filter===n.filter&&i.renderStatus(t,"active"),s=0;s-1)throw new Error(e.messages.errorInsertPreexistingElement());c.style.display="none",s.appendChild(c),s.appendChild(i.dom.document.createTextNode(" ")),n.isElement(c,i.dom.document)&&c.matches(i.config.selectors.target)&&(l=new e.Target,l.init(c,i),l.isInDom=!0,i.targets.splice(r,0,l),r++)}i.dom.parent.insertBefore(s,o)}a.startOrder=i.origOrder=i.targets,i.callActions("afterInsertTargets",arguments)},getNextSibling:function(t,e,n){var a=this,i=null;return t=Math.max(t,0),e&&"before"===n?i=e:e&&"after"===n?i=e.nextElementSibling||null:a.targets.length>0&&"undefined"!=typeof t?i=t0&&(a.config.layout.siblingAfter?i=a.config.layout.siblingAfter:a.config.layout.siblingBefore?i=a.config.layout.siblingBefore.nextElementSibling:a.dom.parent.children[0]),a.callFilters("elementGetNextSibling",i,arguments)},filterOperation:function(t){var e=this,n=!1,a=-1,i="",o=null,r=-1;for(e.callActions("beforeFilterOperation",arguments),i=t.newFilter.action,r=0;o=t.newOrder[r];r++)n=t.newFilter.collection?t.newFilter.collection.indexOf(o.dom.el)>-1:""!==t.newFilter.selector&&o.dom.el.matches(t.newFilter.selector),e.evaluateHideShow(n,o,i,t);if(t.toRemove.length)for(r=0;o=t.show[r];r++)t.toRemove.indexOf(o)>-1&&(t.show.splice(r,1),(a=t.toShow.indexOf(o))>-1&&t.toShow.splice(a,1),t.toHide.push(o),t.hide.push(o),r--);t.matching=t.show.slice(),0===t.show.length&&""!==t.newFilter.selector&&0!==e.targets.length&&(t.hasFailed=!0),e.callActions("afterFilterOperation",arguments)},evaluateHideShow:function(t,e,n,a){var i=this;i.callActions("beforeEvaluateHideShow",arguments),t===!0&&"show"===n||t===!1&&"hide"===n?(a.show.push(e),!e.isShown&&a.toShow.push(e)):(a.hide.push(e),e.isShown&&a.toHide.push(e)),i.callActions("afterEvaluateHideShow",arguments)},sortOperation:function(t){var e=this;e.callActions("beforeSortOperation",arguments),t.startOrder=e.targets,t.newSort.collection?t.newOrder=t.newSort.collection:"random"===t.newSort.order?t.newOrder=n.arrayShuffle(t.startOrder):""===t.newSort.attribute?(t.newOrder=e.origOrder.slice(),"desc"===t.newSort.order&&t.newOrder.reverse()):(t.newOrder=t.startOrder.slice(),t.newOrder.sort(function(n,a){return e.compare(n,a,t.newSort)})),n.isEqualArray(t.newOrder,t.startOrder)&&(t.willSort=!1),e.callActions("afterSortOperation",arguments)},compare:function(t,e,n){var a=this,i=n.order,o=a.getAttributeValue(t,n.attribute),r=a.getAttributeValue(e,n.attribute);return isNaN(1*o)||isNaN(1*r)?(o=o.toLowerCase(),r=r.toLowerCase()):(o=1*o,r=1*r),or?"asc"===i?1:-1:o===r&&n.next?a.compare(t,e,n.next):0},getAttributeValue:function(t,n){var a=this,i="";return i=t.dom.el.getAttribute("data-"+n),null===i&&a.config.debug.showWarnings&&console.warn(e.messages.warningInconsistentSortingAttributes({attribute:"data-"+n})),a.callFilters("valueGetAttributeValue",i||0,arguments)},printSort:function(e,a){var i=this,o=e?a.newOrder:a.startOrder,r=e?a.startOrder:a.newOrder,s=o.length?o[o.length-1].dom.el.nextElementSibling:null,l=t.document.createDocumentFragment(),c=null,u=null,f=null,h=-1;for(i.callActions("beforePrintSort",arguments),h=0;u=o[h];h++)f=u.dom.el,"absolute"!==f.style.position&&(n.removeWhitespace(f.previousSibling),f.parentElement.removeChild(f));for(c=s?s.previousSibling:i.dom.parent.lastChild,c&&"#text"===c.nodeName&&n.removeWhitespace(c),h=0;u=r[h];h++)f=u.dom.el,n.isElement(l.lastChild)&&l.appendChild(t.document.createTextNode(" ")),l.appendChild(f);i.dom.parent.firstChild&&i.dom.parent.firstChild!==s&&l.insertBefore(t.document.createTextNode(" "),l.childNodes[0]),s?(l.appendChild(t.document.createTextNode(" ")),i.dom.parent.insertBefore(l,s)):i.dom.parent.appendChild(l),i.callActions("afterPrintSort",arguments)},parseSortString:function(t,a){var i=this,o=t.split(" "),r=a,s=[],l=-1;for(l=0;l-1&&(c=n.substring(l),u=s.exec(c),f=u[1]),t){case"fade":a.opacity=f?parseFloat(f):0;break;case"stagger":r.staggerDuration=f?parseFloat(f):100;break;default:if(o&&r.config.animation.reverseOut&&"scale"!==t?a[t].value=(f?parseFloat(f):e.transformDefaults[t].value)*-1:a[t].value=f?parseFloat(f):e.transformDefaults[t].value,f){for(m=0;d=h[m];m++)if(f.indexOf(d)>-1){a[t].unit=d;break}}else a[t].unit=e.transformDefaults[t].unit;i.push(t+"("+a[t].value+a[t].unit+")")}r.callActions("afterParseEffect",arguments)},buildState:function(t){var n=this,a=new e.State,i=null,o=-1;for(n.callActions("beforeBuildState",arguments),o=0;i=n.targets[o];o++)(!t.toRemove.length||t.toRemove.indexOf(i)<0)&&a.targets.push(i.dom.el);for(o=0;i=t.matching[o];o++)a.matching.push(i.dom.el);for(o=0;i=t.show[o];o++)a.show.push(i.dom.el);for(o=0;i=t.hide[o];o++)(!t.toRemove.length||t.toRemove.indexOf(i)<0)&&a.hide.push(i.dom.el);return a.id=n.id,a.container=n.dom.container,a.activeFilter=t.newFilter,a.activeSort=t.newSort,a.activeDataset=t.newDataset,a.activeContainerClassName=t.newContainerClassName,a.hasFailed=t.hasFailed,a.totalTargets=n.targets.length,a.totalShow=t.show.length,a.totalHide=t.hide.length,a.totalMatching=t.matching.length,a.triggerElement=t.triggerElement,n.callFilters("stateBuildState",a,arguments)},goMix:function(a,i){var o=this,r=null;return o.callActions("beforeGoMix",arguments),o.config.animation.duration&&o.config.animation.effects&&n.isVisible(o.dom.container)||(a=!1),i.toShow.length||i.toHide.length||i.willSort||i.willChangeLayout||(a=!1),i.startState.show.length||i.show.length||(a=!1),e.events.fire("mixStart",o.dom.container,{state:i.startState,futureState:i.newState,instance:o},o.dom.document),"function"==typeof o.config.callbacks.onMixStart&&o.config.callbacks.onMixStart.call(o.dom.container,i.startState,i.newState,o),n.removeClass(o.dom.container,n.getClassname(o.config.classNames,"container",o.config.classNames.modifierFailed)),r=o.userDeferred?o.userDeferred:o.userDeferred=n.defer(e.libraries),o.isBusy=!0,a&&e.features.has.transitions?(t.pageYOffset!==i.docState.scrollTop&&t.scrollTo(i.docState.scrollLeft,i.docState.scrollTop),o.config.animation.applyPerspective&&(o.dom.parent.style[e.features.perspectiveProp]=o.config.animation.perspectiveDistance,o.dom.parent.style[e.features.perspectiveOriginProp]=o.config.animation.perspectiveOrigin),o.config.animation.animateResizeContainer&&i.startHeight!==i.newHeight&&i.viewportDeltaY!==i.startHeight-i.newHeight&&(o.dom.parent.style.height=i.startHeight+"px"),o.config.animation.animateResizeContainer&&i.startWidth!==i.newWidth&&i.viewportDeltaX!==i.startWidth-i.newWidth&&(o.dom.parent.style.width=i.startWidth+"px"),i.startHeight===i.newHeight&&(o.dom.parent.style.height=i.startHeight+"px"),i.startWidth===i.newWidth&&(o.dom.parent.style.width=i.startWidth+"px"),i.startHeight===i.newHeight&&i.startWidth===i.newWidth&&(o.dom.parent.style.overflow="hidden"),requestAnimationFrame(function(){o.moveTargets(i)}),o.callFilters("promiseGoMix",r.promise,arguments)):(o.config.debug.fauxAsync?setTimeout(function(){o.cleanUp(i)},o.config.animation.duration):o.cleanUp(i),o.callFilters("promiseGoMix",r.promise,arguments))},getStartMixData:function(n){var a=this,i=t.getComputedStyle(a.dom.parent),o=a.dom.parent.getBoundingClientRect(),r=null,s={},l=-1,c=i[e.features.boxSizingProp];for(a.incPadding="border-box"===c,a.callActions("beforeGetStartMixData",arguments),l=0;r=n.show[l];l++)s=r.getPosData(),n.showPosData[l]={startPosData:s};for(l=0;r=n.toHide[l];l++)s=r.getPosData(),n.toHidePosData[l]={startPosData:s};n.startX=o.left,n.startY=o.top,n.startHeight=a.incPadding?o.height:o.height-parseFloat(i.paddingTop)-parseFloat(i.paddingBottom)-parseFloat(i.borderTop)-parseFloat(i.borderBottom),n.startWidth=a.incPadding?o.width:o.width-parseFloat(i.paddingLeft)-parseFloat(i.paddingRight)-parseFloat(i.borderLeft)-parseFloat(i.borderRight),a.callActions("afterGetStartMixData",arguments)},setInter:function(t){var e=this,a=null,i=-1;for(e.callActions("beforeSetInter",arguments),e.config.animation.clampHeight&&(e.dom.parent.style.height=t.startHeight+"px",e.dom.parent.style.overflow="hidden"),e.config.animation.clampWidth&&(e.dom.parent.style.width=t.startWidth+"px",e.dom.parent.style.overflow="hidden"),i=0;a=t.toShow[i];i++)a.show();t.willChangeLayout&&(n.removeClass(e.dom.container,t.startContainerClassName),n.addClass(e.dom.container,t.newContainerClassName)),e.callActions("afterSetInter",arguments)},getInterMixData:function(t){var e=this,n=null,a=-1;for(e.callActions("beforeGetInterMixData",arguments),a=0;n=t.show[a];a++)t.showPosData[a].interPosData=n.getPosData();for(a=0;n=t.toHide[a];a++)t.toHidePosData[a].interPosData=n.getPosData();e.callActions("afterGetInterMixData",arguments)},setFinal:function(t){var e=this,n=null,a=-1;for(e.callActions("beforeSetFinal",arguments),t.willSort&&e.printSort(!1,t),a=0;n=t.toHide[a];a++)n.hide();e.callActions("afterSetFinal",arguments)},getFinalMixData:function(e){var a=this,i=null,o=null,r=null,s=-1;for(a.callActions("beforeGetFinalMixData",arguments),s=0;r=e.show[s];s++)e.showPosData[s].finalPosData=r.getPosData();for(s=0;r=e.toHide[s];s++)e.toHidePosData[s].finalPosData=r.getPosData();for((a.config.animation.clampHeight||a.config.animation.clampWidth)&&(a.dom.parent.style.height=a.dom.parent.style.width=a.dom.parent.style.overflow=""),a.incPadding||(i=t.getComputedStyle(a.dom.parent)),o=a.dom.parent.getBoundingClientRect(),e.newX=o.left,e.newY=o.top,e.newHeight=a.incPadding?o.height:o.height-parseFloat(i.paddingTop)-parseFloat(i.paddingBottom)-parseFloat(i.borderTop)-parseFloat(i.borderBottom),e.newWidth=a.incPadding?o.width:o.width-parseFloat(i.paddingLeft)-parseFloat(i.paddingRight)-parseFloat(i.borderLeft)-parseFloat(i.borderRight),e.viewportDeltaX=e.docState.viewportWidth-this.dom.document.documentElement.clientWidth,e.viewportDeltaY=e.docState.viewportHeight-this.dom.document.documentElement.clientHeight,e.willSort&&a.printSort(!0,e),s=0;r=e.toShow[s];s++)r.hide();for(s=0;r=e.toHide[s];s++)r.show();e.willChangeLayout&&(n.removeClass(a.dom.container,e.newContainerClassName),n.addClass(a.dom.container,a.config.layout.containerClassName)),a.callActions("afterGetFinalMixData",arguments)},getTweenData:function(t){var n=this,a=null,i=null,o=Object.getOwnPropertyNames(n.effectsIn),r="",s=null,l=-1,c=-1,u=-1,f=-1;for(n.callActions("beforeGetTweenData",arguments),u=0;a=t.show[u];u++)for(i=t.showPosData[u],i.posIn=new e.StyleData,i.posOut=new e.StyleData,i.tweenData=new e.StyleData,a.isShown?(i.posIn.x=i.startPosData.x-i.interPosData.x,i.posIn.y=i.startPosData.y-i.interPosData.y):i.posIn.x=i.posIn.y=0,i.posOut.x=i.finalPosData.x-i.interPosData.x,i.posOut.y=i.finalPosData.y-i.interPosData.y,i.posIn.opacity=a.isShown?1:n.effectsIn.opacity,i.posOut.opacity=1,i.tweenData.opacity=i.posOut.opacity-i.posIn.opacity,a.isShown||n.config.animation.nudge||(i.posIn.x=i.posOut.x,i.posIn.y=i.posOut.y),i.tweenData.x=i.posOut.x-i.posIn.x,i.tweenData.y=i.posOut.y-i.posIn.y,n.config.animation.animateResizeTargets&&(i.posIn.width=i.startPosData.width,i.posIn.height=i.startPosData.height,l=(i.startPosData.width||i.finalPosData.width)-i.interPosData.width,i.posIn.marginRight=i.startPosData.marginRight-l,c=(i.startPosData.height||i.finalPosData.height)-i.interPosData.height,i.posIn.marginBottom=i.startPosData.marginBottom-c,i.posOut.width=i.finalPosData.width,i.posOut.height=i.finalPosData.height,l=(i.finalPosData.width||i.startPosData.width)-i.interPosData.width,i.posOut.marginRight=i.finalPosData.marginRight-l,c=(i.finalPosData.height||i.startPosData.height)-i.interPosData.height,i.posOut.marginBottom=i.finalPosData.marginBottom-c,i.tweenData.width=i.posOut.width-i.posIn.width,i.tweenData.height=i.posOut.height-i.posIn.height,i.tweenData.marginRight=i.posOut.marginRight-i.posIn.marginRight,i.tweenData.marginBottom=i.posOut.marginBottom-i.posIn.marginBottom),f=0;r=o[f];f++)s=n.effectsIn[r],s instanceof e.TransformData&&s.value&&(i.posIn[r].value=s.value,i.posOut[r].value=0,i.tweenData[r].value=i.posOut[r].value-i.posIn[r].value,i.posIn[r].unit=i.posOut[r].unit=i.tweenData[r].unit=s.unit);for(u=0;a=t.toHide[u];u++)for(i=t.toHidePosData[u],i.posIn=new e.StyleData,i.posOut=new e.StyleData,i.tweenData=new e.StyleData,i.posIn.x=a.isShown?i.startPosData.x-i.interPosData.x:0,i.posIn.y=a.isShown?i.startPosData.y-i.interPosData.y:0,i.posOut.x=n.config.animation.nudge?0:i.posIn.x,i.posOut.y=n.config.animation.nudge?0:i.posIn.y,i.tweenData.x=i.posOut.x-i.posIn.x,i.tweenData.y=i.posOut.y-i.posIn.y,n.config.animation.animateResizeTargets&&(i.posIn.width=i.startPosData.width,i.posIn.height=i.startPosData.height,l=i.startPosData.width-i.interPosData.width,i.posIn.marginRight=i.startPosData.marginRight-l,c=i.startPosData.height-i.interPosData.height,i.posIn.marginBottom=i.startPosData.marginBottom-c),i.posIn.opacity=1,i.posOut.opacity=n.effectsOut.opacity,i.tweenData.opacity=i.posOut.opacity-i.posIn.opacity,f=0;r=o[f];f++)s=n.effectsOut[r],s instanceof e.TransformData&&s.value&&(i.posIn[r].value=0,i.posOut[r].value=s.value,i.tweenData[r].value=i.posOut[r].value-i.posIn[r].value,i.posIn[r].unit=i.posOut[r].unit=i.tweenData[r].unit=s.unit);n.callActions("afterGetTweenData",arguments)},moveTargets:function(t){var a=this,i=null,o=null,r=null,s="",l=!1,c=-1,u=-1,f=a.checkProgress.bind(a);for(a.callActions("beforeMoveTargets",arguments),u=0;i=t.show[u];u++)o=new e.IMoveData,r=t.showPosData[u],s=i.isShown?"none":"show",l=a.willTransition(s,t.hasEffect,r.posIn,r.posOut),l&&c++,i.show(),o.posIn=r.posIn,o.posOut=r.posOut,o.statusChange=s,o.staggerIndex=c,o.operation=t,o.callback=l?f:null,i.move(o);for(u=0;i=t.toHide[u];u++)r=t.toHidePosData[u],o=new e.IMoveData,s="hide",l=a.willTransition(s,r.posIn,r.posOut),o.posIn=r.posIn,o.posOut=r.posOut,o.statusChange=s,o.staggerIndex=u,o.operation=t,o.callback=l?f:null,i.move(o);a.config.animation.animateResizeContainer&&(a.dom.parent.style[e.features.transitionProp]="height "+a.config.animation.duration+"ms ease, width "+a.config.animation.duration+"ms ease ",requestAnimationFrame(function(){t.startHeight!==t.newHeight&&t.viewportDeltaY!==t.startHeight-t.newHeight&&(a.dom.parent.style.height=t.newHeight+"px"),t.startWidth!==t.newWidth&&t.viewportDeltaX!==t.startWidth-t.newWidth&&(a.dom.parent.style.width=t.newWidth+"px")})),t.willChangeLayout&&(n.removeClass(a.dom.container,a.config.layout.ContainerClassName),n.addClass(a.dom.container,t.newContainerClassName)),a.callActions("afterMoveTargets",arguments)},hasEffect:function(){var t=this,e=["scale","translateX","translateY","translateZ","rotateX","rotateY","rotateZ"],n="",a=null,i=!1,o=-1,r=-1;if(1!==t.effectsIn.opacity)return t.callFilters("resultHasEffect",!0,arguments);for(r=0;n=e[r];r++)if(a=t.effectsIn[n],o="undefined"!==a.value?a.value:a,0!==o){i=!0;break}return t.callFilters("resultHasEffect",i,arguments)},willTransition:function(t,e,a,i){var o=this,r=!1;return r=!!n.isVisible(o.dom.container)&&(!!("none"!==t&&e||a.x!==i.x||a.y!==i.y)||!!o.config.animation.animateResizeTargets&&(a.width!==i.width||a.height!==i.height||a.marginRight!==i.marginRight||a.marginTop!==i.marginTop)),o.callFilters("resultWillTransition",r,arguments)},checkProgress:function(t){var e=this;e.targetsDone++,e.targetsBound===e.targetsDone&&e.cleanUp(t)},cleanUp:function(t){var a=this,i=null,o=null,r=null,s=null,l=-1;for(a.callActions("beforeCleanUp",arguments),a.targetsMoved=a.targetsImmovable=a.targetsBound=a.targetsDone=0,l=0;i=t.show[l];l++)i.cleanUp(),i.show();for(l=0;i=t.toHide[l];l++)i.cleanUp(),i.hide();if(t.willSort&&a.printSort(!1,t),a.dom.parent.style[e.features.transitionProp]=a.dom.parent.style.height=a.dom.parent.style.width=a.dom.parent.style.overflow=a.dom.parent.style[e.features.perspectiveProp]=a.dom.parent.style[e.features.perspectiveOriginProp]="",t.willChangeLayout&&(n.removeClass(a.dom.container,t.startContainerClassName),n.addClass(a.dom.container,t.newContainerClassName)),t.toRemove.length){for(l=0;i=a.targets[l];l++)t.toRemove.indexOf(i)>-1&&((o=i.dom.el.previousSibling)&&"#text"===o.nodeName&&(r=i.dom.el.nextSibling)&&"#text"===r.nodeName&&n.removeWhitespace(o),t.willSort||a.dom.parent.removeChild(i.dom.el),a.targets.splice(l,1),i.isInDom=!1,l--);a.origOrder=a.targets}t.willSort&&(a.targets=t.newOrder),a.state=t.newState,a.lastOperation=t,a.dom.targets=a.state.targets,e.events.fire("mixEnd",a.dom.container,{state:a.state,instance:a},a.dom.document),"function"==typeof a.config.callbacks.onMixEnd&&a.config.callbacks.onMixEnd.call(a.dom.container,a.state,a),t.hasFailed&&(e.events.fire("mixFail",a.dom.container,{state:a.state,instance:a},a.dom.document),"function"==typeof a.config.callbacks.onMixFail&&a.config.callbacks.onMixFail.call(a.dom.container,a.state,a),n.addClass(a.dom.container,n.getClassname(a.config.classNames,"container",a.config.classNames.modifierFailed))),"function"==typeof a.userCallback&&a.userCallback.call(a.dom.container,a.state,a),"function"==typeof a.userDeferred.resolve&&a.userDeferred.resolve(a.state),a.userCallback=null,a.userDeferred=null,a.lastClicked=null,a.isToggling=!1,a.isBusy=!1,a.queue.length&&(a.callActions("beforeReadQueueCleanUp",arguments),s=a.queue.shift(),a.userDeferred=s.deferred,a.isToggling=s.isToggling,a.lastClicked=s.triggerElement,s.instruction.command instanceof e.CommandMultimix?a.multimix.apply(a,s.args):a.dataset.apply(a,s.args)),a.callActions("afterCleanUp",arguments)},parseMultimixArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandMultimix,r=0;r-1?i.command.position=o:"string"==typeof o?i.command.collection=n.arrayFromList(n.createElement(o).childNodes):"object"==typeof o&&n.isElement(o,a.dom.document)?i.command.collection.length?i.command.sibling=o:i.command.collection=[o]:"object"==typeof o&&o.length?i.command.collection.length?i.command.sibling=o[0]:i.command.collection=o:"object"==typeof o&&o.childNodes&&o.childNodes.length?i.command.collection.length?i.command.sibling=o.childNodes[0]:i.command.collection=n.arrayFromList(o.childNodes):"object"==typeof o?n.extend(i.command,o):"boolean"==typeof o?i.animate=o:"function"==typeof o&&(i.callback=o));if(i.command.index&&i.command.sibling)throw new Error(e.messages.errorInsertInvalidArguments());return!i.command.collection.length&&a.config.debug.showWarnings&&console.warn(e.messages.warningInsertNoElements()),i=a.callFilters("instructionParseInsertArgs",i,arguments),n.freeze(i),i},parseRemoveArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=null,s=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandRemove,s=0;s-1&&i.command.targets.push(o);return!i.command.targets.length&&a.config.debug.showWarnings&&console.warn(e.messages.warningRemoveNoElements()),n.freeze(i),i},parseDatasetArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandDataset,r=0;r-1&&t.toggleArray.splice(a,1),i=t.getToggleSelector(),t.multimix({filter:i},e.animate,e.callback)},sort:function(){var t=this,e=t.parseSortArgs(arguments);return t.multimix({sort:e.command},e.animate,e.callback)},changeLayout:function(){var t=this,e=t.parseChangeLayoutArgs(arguments);return t.multimix({changeLayout:e.command},e.animate,e.callback)},dataset:function(){var t=this,n=t.parseDatasetArgs(arguments),a=null,i=null,o=!1;return t.callActions("beforeDataset",arguments),t.isBusy?(i=new e.QueueItem,i.args=arguments,i.instruction=n,t.queueMix(i)):(n.callback&&(t.userCallback=n.callback),o=n.animate^t.config.animation.enable?n.animate:t.config.animation.enable,a=t.getDataOperation(n.command.dataset),t.goMix(o,a))},multimix:function(){var t=this,n=null,a=!1,i=null,o=t.parseMultimixArgs(arguments);return t.callActions("beforeMultimix",arguments),t.isBusy?(i=new e.QueueItem,i.args=arguments,i.instruction=o,i.triggerElement=t.lastClicked,i.isToggling=t.isToggling,t.queueMix(i)):(n=t.getOperation(o.command),t.config.controls.enable&&(o.command.filter&&!t.isToggling&&(t.toggleArray.length=0,t.buildToggleArray(n.command)),t.queue.length<1&&t.updateControls(n.command)),o.callback&&(t.userCallback=o.callback),a=o.animate^t.config.animation.enable?o.animate:t.config.animation.enable,t.callFilters("operationMultimix",n,arguments),t.goMix(a,n))},getOperation:function(t){var a=this,i=t.sort,o=t.filter,r=t.changeLayout,s=t.remove,l=t.insert,c=new e.Operation;return c=a.callFilters("operationUnmappedGetOperation",c,arguments),c.id=n.randomHex(),c.command=t,c.startState=a.state,c.triggerElement=a.lastClicked,a.isBusy?(a.config.debug.showWarnings&&console.warn(e.messages.warningGetOperationInstanceBusy()),null):(l&&a.insertTargets(l,c),s&&(c.toRemove=s.targets),c.startSort=c.newSort=c.startState.activeSort,c.startOrder=c.newOrder=a.targets,i&&(c.startSort=c.startState.activeSort,c.newSort=i,c.willSort=a.willSort(i,c.startState.activeSort),c.willSort&&a.sortOperation(c)),c.startFilter=c.startState.activeFilter,o?c.newFilter=o:c.newFilter=n.extend(new e.CommandFilter,c.startFilter),"all"===c.newFilter.selector?c.newFilter.selector=a.config.selectors.target:"none"===c.newFilter.selector&&(c.newFilter.selector=""),a.filterOperation(c),c.startContainerClassName=c.startState.activeContainerClassName,r?(c.newContainerClassName=r.containerClassName,c.newContainerClassName!==c.startContainerClassName&&(c.willChangeLayout=!0)):c.newContainerClassName=c.startContainerClassName,a.config.animation.enable&&(a.getStartMixData(c),a.setInter(c),c.docState=n.getDocumentState(a.dom.document),a.getInterMixData(c),a.setFinal(c),a.getFinalMixData(c),a.parseEffects(),c.hasEffect=a.hasEffect(),a.getTweenData(c)),c.willSort&&(a.targets=c.newOrder),c.newState=a.buildState(c),a.callFilters("operationMappedGetOperation",c,arguments))},tween:function(t,e){var n=null,a=null,i=-1,o=-1;for(e=Math.min(e,1),e=Math.max(e,0),o=0;n=t.show[o];o++)a=t.showPosData[o],n.applyTween(a,e);for(o=0;n=t.hide[o];o++)n.isShown&&n.hide(),(i=t.toHide.indexOf(n))>-1&&(a=t.toHidePosData[i],n.isShown||n.show(),n.applyTween(a,e))},insert:function(){var t=this,e=t.parseInsertArgs(arguments);return t.multimix({insert:e.command},e.animate,e.callback)},insertBefore:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(e.command.collection,"before",e.command.sibling,e.animate,e.callback)},insertAfter:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(e.command.collection,"after",e.command.sibling,e.animate,e.callback)},prepend:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(0,e.command.collection,e.animate,e.callback)},append:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(t.state.totalTargets,e.command.collection,e.animate,e.callback)},remove:function(){var t=this,e=t.parseRemoveArgs(arguments);return t.multimix({remove:e.command},e.animate,e.callback)},getConfig:function(t){var e=this,a=null;return a=t?n.getProperty(e.config,t):e.config,e.callFilters("valueGetConfig",a,arguments)},configure:function(t){var e=this;e.callActions("beforeConfigure",arguments),n.extend(e.config,t,!0,!0),e.callActions("afterConfigure",arguments)},getState:function(){var t=this,a=null;return a=new e.State,n.extend(a,t.state),n.freeze(a),t.callFilters("stateGetState",a,arguments)},forceRefresh:function(){var t=this;t.indexTargets()},forceRender:function(){var t=this,e=null,n=null,a="";for(a in t.cache)e=t.cache[a],n=e.render(e.data),n!==e.dom.el&&(e.isInDom&&(e.unbindEvents(),t.dom.parent.replaceChild(n,e.dom.el)),e.isShown||(n.style.display="none"),e.dom.el=n,e.isInDom&&e.bindEvents());t.state=t.buildState(t.lastOperation)},destroy:function(t){var n=this,a=null,i=null,o=0;for(n.callActions("beforeDestroy",arguments),o=0;a=n.controls[o];o++)a.removeBinding(n);for(o=0;i=n.targets[o];o++)t&&i.show(),i.unbindEvents();n.dom.container.id.match(/^MixItUp/)&&n.dom.container.removeAttribute("id"),delete e.instances[n.id],n.callActions("afterDestroy",arguments)}}),e.IMoveData=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.posIn=null,this.posOut=null,this.operation=null,this.callback=null,this.statusChange="",this.duration=-1,this.staggerIndex=-1,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.IMoveData),e.IMoveData.prototype=Object.create(e.Base.prototype),e.IMoveData.prototype.constructor=e.IMoveData,e.TargetDom=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.el=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.TargetDom),e.TargetDom.prototype=Object.create(e.Base.prototype),e.TargetDom.prototype.constructor=e.TargetDom,e.Target=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.sortString="",this.mixer=null,this.callback=null,this.isShown=!1,this.isBound=!1,this.isExcluded=!1,this.isInDom=!1,this.handler=null,this.operation=null,this.data=null,this.dom=new e.TargetDom,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.Target),e.Target.prototype=Object.create(e.Base.prototype),n.extend(e.Target.prototype,{constructor:e.Target,init:function(t,n,a){var i=this,o="";if(i.callActions("beforeInit",arguments),i.mixer=n,t||(t=i.render(a)),i.cacheDom(t),i.bindEvents(),"none"!==i.dom.el.style.display&&(i.isShown=!0),a&&n.config.data.uidKey){if("undefined"==typeof(o=a[n.config.data.uidKey])||o.toString().length<1)throw new TypeError(e.messages.errorDatasetInvalidUidKey({uidKey:n.config.data.uidKey}));i.id=o,i.data=a,n.cache[o]=i}i.callActions("afterInit",arguments)},render:function(t){var a=this,i=null,o=null,r=null,s="";if(a.callActions("beforeRender",arguments),i=a.callFilters("renderRender",a.mixer.config.render.target,arguments),"function"!=typeof i)throw new TypeError(e.messages.errorDatasetRendererNotSet());return s=i(t),s&&"object"==typeof s&&n.isElement(s)?o=s:"string"==typeof s&&(r=document.createElement("div"),r.innerHTML=s,o=r.firstElementChild),a.callFilters("elRender",o,arguments)},cacheDom:function(t){var e=this;e.callActions("beforeCacheDom",arguments),e.dom.el=t,e.callActions("afterCacheDom",arguments)},getSortString:function(t){var e=this,n=e.dom.el.getAttribute("data-"+t)||"";e.callActions("beforeGetSortString",arguments),n=isNaN(1*n)?n.toLowerCase():1*n,e.sortString=n,e.callActions("afterGetSortString",arguments)},show:function(){var t=this;t.callActions("beforeShow",arguments),t.isShown||(t.dom.el.style.display="",t.isShown=!0),t.callActions("afterShow",arguments)},hide:function(){var t=this;t.callActions("beforeHide",arguments),t.isShown&&(t.dom.el.style.display="none",t.isShown=!1),t.callActions("afterHide",arguments)},move:function(t){var e=this;e.callActions("beforeMove",arguments),e.isExcluded||e.mixer.targetsMoved++,e.applyStylesIn(t),requestAnimationFrame(function(){e.applyStylesOut(t)}),e.callActions("afterMove",arguments)},applyTween:function(t,n){var a=this,i="",o=null,r=t.posIn,s=[],l=new e.StyleData,c=-1;for(a.callActions("beforeApplyTween",arguments),l.x=r.x,l.y=r.y,0===n?a.hide():a.isShown||a.show(),c=0;i=e.features.TWEENABLE[c];c++)if(o=t.tweenData[i],"x"===i){if(!o)continue;l.x=r.x+o*n}else if("y"===i){if(!o)continue;l.y=r.y+o*n}else if(o instanceof e.TransformData){if(!o.value)continue;l[i].value=r[i].value+o.value*n,l[i].unit=o.unit,s.push(i+"("+l[i].value+o.unit+")")}else{if(!o)continue;l[i]=r[i]+o*n,a.dom.el.style[i]=l[i]}(l.x||l.y)&&s.unshift("translate("+l.x+"px, "+l.y+"px)"),s.length&&(a.dom.el.style[e.features.transformProp]=s.join(" ")),a.callActions("afterApplyTween",arguments)},applyStylesIn:function(t){var n=this,a=t.posIn,i=1!==n.mixer.effectsIn.opacity,o=[];n.callActions("beforeApplyStylesIn",arguments),o.push("translate("+a.x+"px, "+a.y+"px)"),n.mixer.config.animation.animateResizeTargets&&("show"!==t.statusChange&&(n.dom.el.style.width=a.width+"px",n.dom.el.style.height=a.height+"px"),n.dom.el.style.marginRight=a.marginRight+"px",n.dom.el.style.marginBottom=a.marginBottom+"px"),i&&(n.dom.el.style.opacity=a.opacity),"show"===t.statusChange&&(o=o.concat(n.mixer.transformIn)),n.dom.el.style[e.features.transformProp]=o.join(" "),n.callActions("afterApplyStylesIn",arguments)},applyStylesOut:function(t){var n=this,a=[],i=[],o=n.mixer.config.animation.animateResizeTargets,r="undefined"!=typeof n.mixer.effectsIn.opacity;if(n.callActions("beforeApplyStylesOut",arguments),a.push(n.writeTransitionRule(e.features.transformRule,t.staggerIndex)),"none"!==t.statusChange&&a.push(n.writeTransitionRule("opacity",t.staggerIndex,t.duration)),o&&(a.push(n.writeTransitionRule("width",t.staggerIndex,t.duration)),a.push(n.writeTransitionRule("height",t.staggerIndex,t.duration)),a.push(n.writeTransitionRule("margin",t.staggerIndex,t.duration))),!t.callback)return n.mixer.targetsImmovable++,void(n.mixer.targetsMoved===n.mixer.targetsImmovable&&n.mixer.cleanUp(t.operation));switch(n.operation=t.operation,n.callback=t.callback,!n.isExcluded&&n.mixer.targetsBound++,n.isBound=!0,n.applyTransition(a),o&&t.posOut.width>0&&t.posOut.height>0&&(n.dom.el.style.width=t.posOut.width+"px",n.dom.el.style.height=t.posOut.height+"px",n.dom.el.style.marginRight=t.posOut.marginRight+"px",n.dom.el.style.marginBottom=t.posOut.marginBottom+"px"),n.mixer.config.animation.nudge||"hide"!==t.statusChange||i.push("translate("+t.posOut.x+"px, "+t.posOut.y+"px)"),t.statusChange){case"hide":r&&(n.dom.el.style.opacity=n.mixer.effectsOut.opacity),i=i.concat(n.mixer.transformOut);break;case"show":r&&(n.dom.el.style.opacity=1)}(n.mixer.config.animation.nudge||!n.mixer.config.animation.nudge&&"hide"!==t.statusChange)&&i.push("translate("+t.posOut.x+"px, "+t.posOut.y+"px)"),n.dom.el.style[e.features.transformProp]=i.join(" "),n.callActions("afterApplyStylesOut",arguments)},writeTransitionRule:function(t,e,n){var a=this,i=a.getDelay(e),o="";return o=t+" "+(n>0?n:a.mixer.config.animation.duration)+"ms "+i+"ms "+("opacity"===t?"linear":a.mixer.config.animation.easing),a.callFilters("ruleWriteTransitionRule",o,arguments)},getDelay:function(t){var e=this,n=-1;return"function"==typeof e.mixer.config.animation.staggerSequence&&(t=e.mixer.config.animation.staggerSequence.call(e,t,e.state)),n=e.mixer.staggerDuration?t*e.mixer.staggerDuration:0,e.callFilters("delayGetDelay",n,arguments)},applyTransition:function(t){var n=this,a=t.join(", ");n.callActions("beforeApplyTransition",arguments),n.dom.el.style[e.features.transitionProp]=a,n.callActions("afterApplyTransition",arguments)},handleTransitionEnd:function(t){var e=this,n=t.propertyName,a=e.mixer.config.animation.animateResizeTargets;e.callActions("beforeHandleTransitionEnd",arguments),e.isBound&&t.target.matches(e.mixer.config.selectors.target)&&(n.indexOf("transform")>-1||n.indexOf("opacity")>-1||a&&n.indexOf("height")>-1||a&&n.indexOf("width")>-1||a&&n.indexOf("margin")>-1)&&(e.callback.call(e,e.operation),e.isBound=!1,e.callback=null,e.operation=null),e.callActions("afterHandleTransitionEnd",arguments)},eventBus:function(t){var e=this;switch(e.callActions("beforeEventBus",arguments),t.type){case"webkitTransitionEnd":case"transitionend":e.handleTransitionEnd(t)}e.callActions("afterEventBus",arguments)},unbindEvents:function(){var t=this;t.callActions("beforeUnbindEvents",arguments),n.off(t.dom.el,"webkitTransitionEnd",t.handler),n.off(t.dom.el,"transitionend",t.handler),t.callActions("afterUnbindEvents",arguments)},bindEvents:function(){var t=this,a="";t.callActions("beforeBindEvents",arguments),a="webkit"===e.features.transitionPrefix?"webkitTransitionEnd":"transitionend",t.handler=function(e){return t.eventBus(e)},n.on(t.dom.el,a,t.handler),t.callActions("afterBindEvents",arguments)},getPosData:function(n){var a=this,i={},o=null,r=new e.StyleData;return a.callActions("beforeGetPosData",arguments),r.x=a.dom.el.offsetLeft,r.y=a.dom.el.offsetTop,(a.mixer.config.animation.animateResizeTargets||n)&&(o=a.dom.el.getBoundingClientRect(),r.top=o.top,r.right=o.right,r.bottom=o.bottom,r.left=o.left,r.width=o.width,r.height=o.height),a.mixer.config.animation.animateResizeTargets&&(i=t.getComputedStyle(a.dom.el),r.marginBottom=parseFloat(i.marginBottom),r.marginRight=parseFloat(i.marginRight)),a.callFilters("posDataGetPosData",r,arguments)},cleanUp:function(){var t=this;t.callActions("beforeCleanUp",arguments),t.dom.el.style[e.features.transformProp]="",t.dom.el.style[e.features.transitionProp]="",t.dom.el.style.opacity="",t.mixer.config.animation.animateResizeTargets&&(t.dom.el.style.width="",t.dom.el.style.height="",t.dom.el.style.marginRight="",t.dom.el.style.marginBottom=""),t.callActions("afterCleanUp",arguments)}}),e.Collection=function(t){var e=null,a=-1;for(this.callActions("beforeConstruct"),a=0;e=t[a];a++)this[a]=e;this.length=t.length,this.callActions("afterConstruct"),n.freeze(this)},e.BaseStatic.call(e.Collection),e.Collection.prototype=Object.create(e.Base.prototype),n.extend(e.Collection.prototype,{constructor:e.Collection,mixitup:function(t){var a=this,i=null,o=Array.prototype.slice.call(arguments),r=[],s=-1;for(this.callActions("beforeMixitup"),o.shift(),s=0;i=a[s];s++)r.push(i[t].apply(i,o));return a.callFilters("promiseMixitup",n.all(r,e.libraries),arguments)}}),e.Operation=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.args=[],this.command=null,this.showPosData=[],this.toHidePosData=[],this.startState=null,this.newState=null,this.docState=null,this.willSort=!1,this.willChangeLayout=!1,this.hasEffect=!1,this.hasFailed=!1,this.triggerElement=null,this.show=[],this.hide=[],this.matching=[],this.toShow=[],this.toHide=[],this.toMove=[],this.toRemove=[],this.startOrder=[],this.newOrder=[],this.startSort=null,this.newSort=null,this.startFilter=null,this.newFilter=null,this.startDataset=null,this.newDataset=null,this.viewportDeltaX=0,this.viewportDeltaY=0,this.startX=0,this.startY=0,this.startHeight=0,this.startWidth=0,this.newX=0,this.newY=0,this.newHeight=0,this.newWidth=0,this.startContainerClassName="",this.startDisplay="",this.newContainerClassName="",this.newDisplay="",this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.Operation),e.Operation.prototype=Object.create(e.Base.prototype),e.Operation.prototype.constructor=e.Operation,e.State=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.activeFilter=null,this.activeSort=null,this.activeContainerClassName="",this.container=null,this.targets=[],this.hide=[],this.show=[],this.matching=[],this.totalTargets=-1,this.totalShow=-1,this.totalHide=-1,this.totalMatching=-1,this.hasFailed=!1,this.triggerElement=null,this.activeDataset=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.State),e.State.prototype=Object.create(e.Base.prototype),e.State.prototype.constructor=e.State,e.UserInstruction=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.command={},this.animate=!1,this.callback=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.UserInstruction),e.UserInstruction.prototype=Object.create(e.Base.prototype),e.UserInstruction.prototype.constructor=e.UserInstruction,e.Messages=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.ERROR_FACTORY_INVALID_CONTAINER="[MixItUp] An invalid selector or element reference was passed to the mixitup factory function",this.ERROR_FACTORY_CONTAINER_NOT_FOUND="[MixItUp] The provided selector yielded no container element",this.ERROR_CONFIG_INVALID_ANIMATION_EFFECTS="[MixItUp] Invalid value for `animation.effects`",this.ERROR_CONFIG_INVALID_CONTROLS_SCOPE="[MixItUp] Invalid value for `controls.scope`",this.ERROR_CONFIG_INVALID_PROPERTY='[MixitUp] Invalid configuration object property "${erroneous}"${suggestion}',this.ERROR_CONFIG_INVALID_PROPERTY_SUGGESTION='. Did you mean "${probableMatch}"?',this.ERROR_CONFIG_DATA_UID_KEY_NOT_SET="[MixItUp] To use the dataset API, a UID key must be specified using `data.uidKey`",this.ERROR_DATASET_INVALID_UID_KEY='[MixItUp] The specified UID key "${uidKey}" is not present on one or more dataset items',this.ERROR_DATASET_DUPLICATE_UID='[MixItUp] The UID "${uid}" was found on two or more dataset items. UIDs must be unique.',this.ERROR_INSERT_INVALID_ARGUMENTS="[MixItUp] Please provider either an index or a sibling and position to insert, not both",this.ERROR_INSERT_PREEXISTING_ELEMENT="[MixItUp] An element to be inserted already exists in the container",this.ERROR_FILTER_INVALID_ARGUMENTS="[MixItUp] Please provide either a selector or collection `.filter()`, not both",this.ERROR_DATASET_NOT_SET="[MixItUp] To use the dataset API with pre-rendered targets, a starting dataset must be set using `load.dataset`",this.ERROR_DATASET_PRERENDERED_MISMATCH="[MixItUp] `load.dataset` does not match pre-rendered targets",this.ERROR_DATASET_RENDERER_NOT_SET="[MixItUp] To insert an element via the dataset API, a target renderer function must be provided to `render.target`",this.WARNING_FACTORY_PREEXISTING_INSTANCE="[MixItUp] WARNING: This element already has an active MixItUp instance. The provided configuration object will be ignored. If you wish to perform additional methods on this instance, please create a reference.",this.WARNING_INSERT_NO_ELEMENTS="[MixItUp] WARNING: No valid elements were passed to `.insert()`",this.WARNING_REMOVE_NO_ELEMENTS="[MixItUp] WARNING: No valid elements were passed to `.remove()`",this.WARNING_MULTIMIX_INSTANCE_QUEUE_FULL="[MixItUp] WARNING: An operation was requested but the MixItUp instance was busy. The operation was rejected because the queue is full or queuing is disabled.",this.WARNING_GET_OPERATION_INSTANCE_BUSY="[MixItUp] WARNING: Operations can be be created while the MixItUp instance is busy.",this.WARNING_NO_PROMISE_IMPLEMENTATION="[MixItUp] WARNING: No Promise implementations could be found. If you wish to use promises with MixItUp please install an ES6 Promise polyfill.",this.WARNING_INCONSISTENT_SORTING_ATTRIBUTES='[MixItUp] WARNING: The requested sorting data attribute "${attribute}" was not present on one or more target elements which may product unexpected sort output',this.callActions("afterConstruct"),this.compileTemplates(),n.seal(this)},e.BaseStatic.call(e.Messages),e.Messages.prototype=Object.create(e.Base.prototype),e.Messages.prototype.constructor=e.Messages,e.Messages.prototype.compileTemplates=function(){var t="",e="";for(t in this)"string"==typeof(e=this[t])&&(this[n.camelCase(t)]=n.template(e))},e.messages=new e.Messages,e.Facade=function(t){e.Base.call(this),this.callActions("beforeConstruct",arguments),this.configure=t.configure.bind(t),this.show=t.show.bind(t),this.hide=t.hide.bind(t),this.filter=t.filter.bind(t),this.toggleOn=t.toggleOn.bind(t),this.toggleOff=t.toggleOff.bind(t),this.sort=t.sort.bind(t),this.changeLayout=t.changeLayout.bind(t),this.multimix=t.multimix.bind(t),this.dataset=t.dataset.bind(t),this.tween=t.tween.bind(t),this.insert=t.insert.bind(t),this.insertBefore=t.insertBefore.bind(t),this.insertAfter=t.insertAfter.bind(t),this.prepend=t.prepend.bind(t),this.append=t.append.bind(t),this.remove=t.remove.bind(t),this.destroy=t.destroy.bind(t),this.forceRefresh=t.forceRefresh.bind(t),this.forceRender=t.forceRender.bind(t),this.isMixing=t.isMixing.bind(t),this.getOperation=t.getOperation.bind(t),this.getConfig=t.getConfig.bind(t),this.getState=t.getState.bind(t),this.callActions("afterConstruct",arguments),n.freeze(this),n.seal(this)},e.BaseStatic.call(e.Facade),e.Facade.prototype=Object.create(e.Base.prototype),e.Facade.prototype.constructor=e.Facade,"object"==typeof exports&&"object"==typeof module?module.exports=e:"function"==typeof define&&define.amd?define(function(){return e}):"undefined"!=typeof t.mixitup&&"function"==typeof t.mixitup||(t.mixitup=e),e.BaseStatic.call(e.constructor),e.NAME="mixitup",e.CORE_VERSION="3.2.2"}(window); \ No newline at end of file diff --git a/demos/programmatic-filtering-by-url-with-pagination/index.html b/demos/programmatic-filtering-by-url-with-pagination/index.html new file mode 100644 index 0000000..e2ffa3f --- /dev/null +++ b/demos/programmatic-filtering-by-url-with-pagination/index.html @@ -0,0 +1,307 @@ + + + + + + + + + MixItUp MultiFilter Demo - Programmatic Filtering by URL with Pagination + + + + +
+ + + + +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/demos/programmatic-filtering-by-url-with-pagination/style.css b/demos/programmatic-filtering-by-url-with-pagination/style.css new file mode 100644 index 0000000..b7c0a03 --- /dev/null +++ b/demos/programmatic-filtering-by-url-with-pagination/style.css @@ -0,0 +1,378 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-color:after, +.control-shape:after, +.control-size:after { + content: ''; + position: absolute; +} + +.control-color:after { + width: 10px; + height: 10px; + top: calc(50% - 6px); + left: calc(50% - 6px); + border: 2px solid currentColor; + border-radius: 2px; + background: currentColor; + transition: background-color 150ms, border-color 150ms; +} + +.control-color[data-toggle=".green"] { + color: #91e6c7; +} + +.control-color[data-toggle=".blue"] { + color: #5ecdde; +} + +.control-color[data-toggle=".pink"] { + color: #d595aa; +} + +.control-shape[data-toggle=".square"]:after, +.control-shape[data-toggle=".circle"]:after { + width: 10px; + height: 10px; + top: calc(50% - 5px); + left: calc(50% - 5px); + background: white; + border-radius: 2px; +} + +.control-shape[data-toggle=".circle"]:after { + border-radius: 10px; +} + +.control-shape[data-toggle=".triangle"]:after { + border: 6px solid transparent; + border-bottom: 9px solid white; + top: calc(50% - 10px); + left: calc(50% - 6px); +} + +.control-size:after { + width: 8px; + height: 8px; + top: calc(50% - 4px); + left: calc(50% - 4px); + border: 2px solid white; + border-radius: 2px; +} + +.control-size[data-toggle=".medium"]:after { + width: 12px; + height: 12px; + top: calc(50% - 6px); + left: calc(50% - 6px); +} + +.control-size[data-toggle=".large"]:after { + width: 16px; + height: 16px; + top: calc(50% - 8px); + left: calc(50% - 8px); +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +/* Pagination Controls +---------------------------------------------------------------------- */ + +.controls-pagination { + padding: 1rem; + font-size: 0.1px; + text-align: justify; +} + +.controls-pagination:after { + content: ''; + display: inline-block; + width: 100%; +} + +.mixitup-page-list, +.mixitup-page-stats { + display: inline-block; + vertical-align: middle; +} + +.mixitup-page-list { + text-align: left; +} + +.mixitup-page-stats { + font-size: .9rem; + color: #333; + font-weight: bold; + font-family: 'helvetica', arial, sans-serif; +} + +.mixitup-control { + position: relative; + display: inline-block; + text-align: center; + width: 2.7rem; + height: 2.7rem; + background: #fff; + border-top: 3px solid transparent; + border-bottom: 3px solid transparent; + margin-right: 1px; + cursor: pointer; + font-size: .9rem; + color: #333; + font-weight: bold; + font-family: 'helvetica', arial, sans-serif; + transition: color 150ms, border-color 150ms; + vertical-align: middle; +} + +.mixitup-control:first-child { + border-radius: 3px 0 0 3px; +} + +.mixitup-control:last-child { + border-radius: 0 3px 3px 0; +} + +.mixitup-control:not(.mixitup-control-active):hover { + color: #91e6c7; +} + +.mixitup-control-active { + border-bottom-color: #91e6c7; + cursor: default; +} + +.mixitup-control:disabled { + background: #eaeaea; + color: #aaa; + cursor: default; +} + +.mixitup-control-truncation-marker { + background: transparent; + pointer-events: none; + line-height: 2.2em; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} \ No newline at end of file diff --git a/demos/programmatic-filtering-by-url/index.html b/demos/programmatic-filtering-by-url/index.html new file mode 100644 index 0000000..c359a1b --- /dev/null +++ b/demos/programmatic-filtering-by-url/index.html @@ -0,0 +1,257 @@ + + + + + + + + + MixItUp MultiFilter Demo - Programmatic Filtering by URL + + + + +
+ + + + +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/programmatic-filtering-by-url/style.css b/demos/programmatic-filtering-by-url/style.css new file mode 100644 index 0000000..cee6878 --- /dev/null +++ b/demos/programmatic-filtering-by-url/style.css @@ -0,0 +1,300 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-color:after, +.control-shape:after, +.control-size:after { + content: ''; + position: absolute; +} + +.control-color:after { + width: 10px; + height: 10px; + top: calc(50% - 6px); + left: calc(50% - 6px); + border: 2px solid currentColor; + border-radius: 2px; + background: currentColor; + transition: background-color 150ms, border-color 150ms; +} + +.control-color[data-toggle=".green"] { + color: #91e6c7; +} + +.control-color[data-toggle=".blue"] { + color: #5ecdde; +} + +.control-color[data-toggle=".pink"] { + color: #d595aa; +} + +.control-shape[data-toggle=".square"]:after, +.control-shape[data-toggle=".circle"]:after { + width: 10px; + height: 10px; + top: calc(50% - 5px); + left: calc(50% - 5px); + background: white; + border-radius: 2px; +} + +.control-shape[data-toggle=".circle"]:after { + border-radius: 10px; +} + +.control-shape[data-toggle=".triangle"]:after { + border: 6px solid transparent; + border-bottom: 9px solid white; + top: calc(50% - 10px); + left: calc(50% - 6px); +} + +.control-size:after { + width: 8px; + height: 8px; + top: calc(50% - 4px); + left: calc(50% - 4px); + border: 2px solid white; + border-radius: 2px; +} + +.control-size[data-toggle=".medium"]:after { + width: 12px; + height: 12px; + top: calc(50% - 6px); + left: calc(50% - 6px); +} + +.control-size[data-toggle=".large"]:after { + width: 16px; + height: 16px; + top: calc(50% - 8px); + left: calc(50% - 8px); +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/demos/programmatic-filtering-on-click/index.html b/demos/programmatic-filtering-on-click/index.html new file mode 100644 index 0000000..6dd45d1 --- /dev/null +++ b/demos/programmatic-filtering-on-click/index.html @@ -0,0 +1,126 @@ + + + + + + + + + MixItUp MultiFilter Demo - Programmatic Filtering on Click + + + + +
+ + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/programmatic-filtering-on-click/style.css b/demos/programmatic-filtering-on-click/style.css new file mode 100644 index 0000000..755114f --- /dev/null +++ b/demos/programmatic-filtering-on-click/style.css @@ -0,0 +1,234 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.select-wrapper { + display: inline-block; + padding: .5rem; + background: #2a2a2a; + margin-left: .75rem; + vertical-align: top; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/demos/radios/index.html b/demos/radios/index.html new file mode 100644 index 0000000..9772e61 --- /dev/null +++ b/demos/radios/index.html @@ -0,0 +1,116 @@ + + + + + + + + + MixItUp MultiFilter Demo - Radios + + +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/radios/style.css b/demos/radios/style.css new file mode 100644 index 0000000..ed9eff3 --- /dev/null +++ b/demos/radios/style.css @@ -0,0 +1,266 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.radio-group { + display: inline-block; + padding: .5rem; + background: #2a2a2a; + margin-left: .75rem; + vertical-align: top; +} + +.radio { + text-align: justify; +} + +.radio:after { + content: ''; + display: inline-block; + width: 100%; +} + +.radio-input, +.radio-label { + display: inline-block; +} + +.radio-group-label, +.radio-label { + color: white; + font-family: 'helvetica-neue', arial, sans-serif; + font-size: .9rem; +} + +.radio-group-label { + font-weight: bold; + display: block; + margin-bottom: .5rem; +} + +.radio-label { + margin-right: .5rem; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/demos/reset.css b/demos/reset.css new file mode 100644 index 0000000..cd114cc --- /dev/null +++ b/demos/reset.css @@ -0,0 +1,72 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +body { + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: antialiased; +} + +blockquote, q { + quotes: none; +} + +a { + text-decoration: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +button { + background: transparent; + border-radius: 0; + border: 0; + padding: 0; + + -webkit-appearance: none; + -webkit-border-radius: 0; + + user-select: none; +} + +button:focus { + outline: 0 none; +} + +button::-moz-focus-inner { + padding: 0; + border: 0; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/demos/selects/index.html b/demos/selects/index.html new file mode 100644 index 0000000..0f45361 --- /dev/null +++ b/demos/selects/index.html @@ -0,0 +1,86 @@ + + + + + + + + + MixItUp MultiFilter Demo - Selects + + +
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/selects/style.css b/demos/selects/style.css new file mode 100644 index 0000000..755114f --- /dev/null +++ b/demos/selects/style.css @@ -0,0 +1,234 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.select-wrapper { + display: inline-block; + padding: .5rem; + background: #2a2a2a; + margin-left: .75rem; + vertical-align: top; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/demos/text-inputs/index.html b/demos/text-inputs/index.html new file mode 100644 index 0000000..dac9500 --- /dev/null +++ b/demos/text-inputs/index.html @@ -0,0 +1,71 @@ + + + + + + + + + MixItUp MultiFilter Demo - Text Inputs + + +
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/text-inputs/style.css b/demos/text-inputs/style.css new file mode 100644 index 0000000..8a4830d --- /dev/null +++ b/demos/text-inputs/style.css @@ -0,0 +1,242 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.text-input-wrapper { + display: inline-block; + padding: .5rem; + background: #2a2a2a; + margin-left: .75rem; + vertical-align: top; +} + +.text-input-wrapper input { + font-family: 'helvetica-neue', arial, sans-serif; + font-size: .9rem; + padding: .5em; + border-radius: 2px; + border: 0; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/demos/toggles/index.html b/demos/toggles/index.html new file mode 100644 index 0000000..9b722f8 --- /dev/null +++ b/demos/toggles/index.html @@ -0,0 +1,83 @@ + + + + + + + + + MixItUp MultiFilter Demo - Toggle Controls + + +
+ + +
+ + + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ +
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/toggles/style.css b/demos/toggles/style.css new file mode 100644 index 0000000..cee6878 --- /dev/null +++ b/demos/toggles/style.css @@ -0,0 +1,300 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.control-group { + display: inline-block; + margin-left: .75rem; + vertical-align: top; +} + +.control { + position: relative; + display: inline-block; + width: 2.7rem; + height: 2.7rem; + background: #444; + cursor: pointer; + font-size: 0.1px; + color: white; + transition: background 150ms; +} + +.control-text { + width: auto; + font-size: .9rem; + padding: 0 1rem; + font-family: 'helvetica-neue', arial, sans-serif; + font-weight: 700; +} + +.control:not(.mixitup-control-active):hover { + background: #3f3f3f; +} + +.control-color:after, +.control-shape:after, +.control-size:after { + content: ''; + position: absolute; +} + +.control-color:after { + width: 10px; + height: 10px; + top: calc(50% - 6px); + left: calc(50% - 6px); + border: 2px solid currentColor; + border-radius: 2px; + background: currentColor; + transition: background-color 150ms, border-color 150ms; +} + +.control-color[data-toggle=".green"] { + color: #91e6c7; +} + +.control-color[data-toggle=".blue"] { + color: #5ecdde; +} + +.control-color[data-toggle=".pink"] { + color: #d595aa; +} + +.control-shape[data-toggle=".square"]:after, +.control-shape[data-toggle=".circle"]:after { + width: 10px; + height: 10px; + top: calc(50% - 5px); + left: calc(50% - 5px); + background: white; + border-radius: 2px; +} + +.control-shape[data-toggle=".circle"]:after { + border-radius: 10px; +} + +.control-shape[data-toggle=".triangle"]:after { + border: 6px solid transparent; + border-bottom: 9px solid white; + top: calc(50% - 10px); + left: calc(50% - 6px); +} + +.control-size:after { + width: 8px; + height: 8px; + top: calc(50% - 4px); + left: calc(50% - 4px); + border: 2px solid white; + border-radius: 2px; +} + +.control-size[data-toggle=".medium"]:after { + width: 12px; + height: 12px; + top: calc(50% - 6px); + left: calc(50% - 6px); +} + +.control-size[data-toggle=".large"]:after { + width: 16px; + height: 16px; + top: calc(50% - 8px); + left: calc(50% - 8px); +} + +.control-sort:after { + content: ''; + position: absolute; + width: 10px; + height: 10px; + border-top: 2px solid; + border-left: 2px solid; + top: calc(50% - 6px); + left: calc(50% - 6px); + transform: translateY(2px) rotate(45deg); +} + +.control-sort[data-sort*=":desc"]:after { + transform: translateY(-3px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +/* Container +---------------------------------------------------------------------- */ + +.container { + padding: 1rem; + text-align: justify; + font-size: 0.1px; +} + +.container:after { + content: ''; + display: inline-block; + width: 100%; +} + +/* Target Elements +---------------------------------------------------------------------- */ + +.mix, +.gap { + display: inline-block; + vertical-align: top; +} + +.mix { + background: #fff; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 100%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +.mix:after { + content: ''; + position: absolute; +} + +.mix.square:after, +.mix.circle:after { + width: 40%; + height: 40%; + top: 30%; + left: 30%; + background: currentColor; + border-radius: 3px; +} + +.mix.circle:after { + border-radius: 100%; +} + +.mix.triangle:after { + font-size: 3vw; + border: 3em solid transparent; + border-bottom: 5em solid currentColor; + border-top: 0; + left: calc(50% - 3em); + top: calc(50% - 3em); +} + +.mix.medium:after { + transform: scale(.75); +} + +.mix.small:after { + transform: scale(.5); +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .mix.triangle:after { + font-size: 2vw; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .mix.triangle:after { + font-size: 1.5vw; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .mix.triangle:after { + font-size: 1.25vw; + } +} + +/* 6 Columns */ + +@media screen and (min-width: 1401px) { + .mix, + .gap { + width: calc(100%/6 - (((6 - 1) * 1rem) / 6)); + } + + .mix.triangle:after { + font-size: 1vw; + } +} + + diff --git a/dist/mixitup-multifilter.js b/dist/mixitup-multifilter.js new file mode 100644 index 0000000..9d32b0b --- /dev/null +++ b/dist/mixitup-multifilter.js @@ -0,0 +1,1256 @@ +/**! + * MixItUp MultiFilter v3.3.6 + * A UI-builder for powerful multidimensional filtering + * Build 293e0dda-087e-4a76-aadf-e3e8b311b81f + * + * Requires mixitup.js >= v^3.1.2 + * + * @copyright Copyright 2014-2020 KunkaLabs Limited. + * @author KunkaLabs Limited. + * @link https://www.kunkalabs.com/mixitup-multifilter/ + * + * @license Commercial use requires a commercial license. + * https://www.kunkalabs.com/mixitup-multifilter/licenses/ + * + * Non-commercial use permitted under same terms as license. + * http://creativecommons.org/licenses/by-nc/3.0/ + */ +(function(window) { + 'use strict'; + + var mixitupMultifilter = function(mixitup) { + var h = mixitup.h; + var diacriticsMap; + + diacriticsMap = [ + ['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], + ['AA', /[\uA732]/g], + ['AE', /[\u00C6\u01FC\u01E2]/g], + ['AO', /[\uA734]/g], + ['AU', /[\uA736]/g], + ['AV', /[\uA738\uA73A]/g], + ['AY', /[\uA73C]/g], + ['B', /[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g], + ['C', /[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g], + ['D', /[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g], + ['DZ', /[\u01F1\u01C4]/g], + ['Dz', /[\u01F2\u01C5]/g], + ['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], + ['F', /[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g], + ['G', /[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g], + ['H', /[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g], + ['I', /[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g], + ['J', /[\u004A\u24BF\uFF2A\u0134\u0248]/g], + ['K', /[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g], + ['L', /[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g], + ['LJ', /[\u01C7]/g], + ['Lj', /[\u01C8]/g], + ['M', /[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g], + ['N', /[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g], + ['NJ', /[\u01CA]/g], + ['Nj', /[\u01CB]/g], + ['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], + ['OI', /[\u01A2]/g], + ['OO', /[\uA74E]/g], + ['OU', /[\u0222]/g], + ['P', /[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g], + ['Q', /[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g], + ['R', /[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g], + ['S', /[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g], + ['T', /[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g], + ['TZ', /[\uA728]/g], + ['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], + ['V', /[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g], + ['VY', /[\uA760]/g], + ['W', /[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g], + ['X', /[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g], + ['Y', /[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g], + ['Z', /[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g], + ['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], + ['aa', /[\uA733]/g], + ['ae', /[\u00E6\u01FD\u01E3]/g], + ['ao', /[\uA735]/g], + ['au', /[\uA737]/g], + ['av', /[\uA739\uA73B]/g], + ['ay', /[\uA73D]/g], + ['b', /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g], + ['c', /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g], + ['d', /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g], + ['dz', /[\u01F3\u01C6]/g], + ['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], + ['f', /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g], + ['g', /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g], + ['h', /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g], + ['hv', /[\u0195]/g], + ['i', /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g], + ['j', /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g], + ['k', /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g], + ['l', /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g], + ['lj', /[\u01C9]/g], + ['m', /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g], + ['n', /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g], + ['nj', /[\u01CC]/g], + ['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], + ['oi', /[\u01A3]/g], + ['ou', /[\u0223]/g], + ['oo', /[\uA74F]/g], + ['p', /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g], + ['q', /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g], + ['r', /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g], + ['s', /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g], + ['t', /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g], + ['tz', /[\uA729]/g], + ['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], + ['v', /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g], + ['vy', /[\uA761]/g], + ['w', /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g], + ['x', /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g], + ['y', /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g], + ['z', /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g] + ]; + + if ( + !mixitup.CORE_VERSION || + !h.compareVersions(mixitupMultifilter.REQUIRE_CORE_VERSION, mixitup.CORE_VERSION) + ) { + throw new Error( + '[MixItUp Multifilter] MixItUp Multifilter v' + + mixitupMultifilter.EXTENSION_VERSION + + ' requires at least MixItUp v' + + mixitupMultifilter.REQUIRE_CORE_VERSION + ); + } + + /** + * A group of optional callback functions to be invoked at various + * points within the lifecycle of a mixer operation. + * + * @constructor + * @memberof mixitup.Config + * @name callbacks + * @namespace + * @public + * @since 3.0.0 + */ + + mixitup.ConfigCallbacks.registerAction('afterConstruct', 'multifilter', function() { + /** + * A callback function invoked whenever MultiFilter filter groups + * are parsed. This occurs whenever the user interacts with filter + * group UI, or when the `parseFilterGroups()` API method is called, + * but before the resulting filter operation has been triggered. + * + * By default, this generates the appropriate compound selector and + * filters the mixer using a `multimix()` API call internally. This + * callback can be used to transform the multimix command object sent + * to this API call. + * + * This is particularly useful when additional behavior such as sorting + * or pagination must be taken into account when using the MultiFilter API. + * + * The callback receives the generated multimix command object, and must + * also return a valid multimix command object. + * + * @example Example: Overriding the default filtering behavior with `onParseFilterGroups` + * var mixer = mixitup(containerEl, { + * callbacks: { + * onParseFilterGroups: function(command) { + * command.paginate = 3; + * command.sort = 'default:desc'; + * + * return command; + * } + * } + * }); + * + * @name onParseFilterGroups + * @memberof mixitup.Config.callbacks + * @instance + * @type {function} + * @default null + */ + + this.onParseFilterGroups = null; + }); + + /** + * A group of properties defining the behavior of your multifilter UI. + * + * @constructor + * @memberof mixitup.Config + * @name multifilter + * @namespace + * @public + * @since 3.0.0 + */ + + mixitup.ConfigMultifilter = function() { + + /** + * A boolean dictating whether or not to enable multifilter functionality. + * + * If `true`, MixItUp will query the DOM for any elements with a + * `data-filter-group` attribute present on instantiation. + * + * @name enable + * @memberof mixitup.Config.multifilter + * @instance + * @type {boolean} + * @default false + */ + + this.enable = false; + + /** + * A string dictating the logic to use when concatenating selectors within + * individual filter groups. + * + * If set to `'or'` (default), targets will be shown if they match any + * active filter in the group. + * + * If set to `'and'`, targets will be shown only if they match + * all active filters in the group. + * + * @name logicWithinGroup + * @memberof mixitup.Config.multifilter + * @instance + * @type {string} + * @default 'or' + */ + + this.logicWithinGroup = 'or'; + + /** + * A string dictating the logic to use when concatenating each group's + * selectors into one single selector. + * + * If set to `'and'` (default), targets will be shown only if they match + * the combined active selectors of all groups. + * + * If set to `'or'`, targets will be shown if they match the active selectors + * of any individual group. + * + * @name logicBetweenGroups + * @memberof mixitup.Config.multifilter + * @instance + * @type {string} + * @default 'and' + */ + + this.logicBetweenGroups = 'and'; + + /** + * An integer dictating the minimum number of characters at which the value + * of a text input will be included as a multifilter. This prevents short or + * incomplete words with many potential matches from triggering + * filter operations. + * + * @name minSearchLength + * @memberof mixitup.Config.multifilter + * @instance + * @type {number} + * @default 3 + */ + + this.minSearchLength = 3; + + /** + * A string dictating when the parsing of filter groups should occur. + * + * If set to `'change'` (default), the mixer will be filtered whenever the + * filtering UI is interacted with. The mode provides real-time filtering with + * instant feedback. + * + * If set to `'submit'`, the mixer will only be filtered when a submit button is + * clicked (if using a `
` element as a parent). This enables the user to firstly + * make their selection, and then trigger filtering once they have + * finished making their selection. + * + * Alternatively, the `mixer.parseFilterGroups()` method can be called via the API at any + * time to trigger the parsing of filter groups and filter the mixer. + * + * @name parseOn + * @memberof mixitup.Config.multifilter + * @instance + * @type {string} + * @default 'change' + */ + + this.parseOn = 'change'; + + /** + * An integer dictating the duration in ms that must elapse between keyup + * events in order to trigger a change. + * + * Setting a comfortable delay of ~350ms prevents the mixer from being + * thrashed while typing occurs. + * + * @name keyupThrottleDuration + * @memberof mixitup.Config.multifilter + * @instance + * @type {number} + * @default 350 + */ + + this.keyupThrottleDuration = 350; + + h.seal(this); + }; + + /** + * The MixItUp configuration object is extended with properties relating to + * the MultiFilter extension. + * + * For the full list of configuration options, please refer to the MixItUp + * core documentation. + * + * @constructor + * @memberof mixitup + * @name Config + * @namespace + * @public + * @since 2.0.0 + */ + + mixitup.Config.registerAction('beforeConstruct', 'multifilter', function() { + this.multifilter = new mixitup.ConfigMultifilter(); + }); + + mixitup.MultifilterFormEventTracker = function() { + this.form = null; + this.totalBound = 0; + this.totalHandled = 0; + + h.seal(this); + }; + + mixitup.FilterGroupDom = function() { + this.el = null; + this.form = null; + + h.seal(this); + }; + + mixitup.FilterGroup = function() { + this.name = ''; + this.dom = new mixitup.FilterGroupDom(); + this.activeSelectors = []; + this.activeFilters = []; + this.activeToggles = []; + this.handler = null; + this.mixer = null; + this.logic = 'or'; + this.parseOn = 'change'; + this.keyupTimeout = -1; + + h.seal(this); + }; + + h.extend(mixitup.FilterGroup.prototype, { + + /** + * @private + * @param {HTMLELement} el + * @param {mixitup.Mixer} mixer + * @return {void} + */ + + init: function(el, mixer) { + var self = this, + logic = el.getAttribute('data-logic'); + + self.dom.el = el; + + this.name = self.dom.el.getAttribute('data-filter-group') || ''; + + self.cacheDom(); + + if (self.dom.form) { + self.enableButtons(); + } + + self.mixer = mixer; + + if ((logic && logic.toLowerCase() === 'and') || mixer.config.multifilter.logicWithinGroup === 'and') { + // override default group logic + + self.logic = 'and'; + + } + + self.bindEvents(); + }, + + /** + * @private + * @return {void} + */ + + cacheDom: function() { + var self = this; + + self.dom.form = h.closestParent(self.dom.el, 'form', true); + }, + + enableButtons: function() { + var self = this, + buttons = self.dom.form.querySelectorAll('button[type="submit"]:disabled'), + button = null, + i = -1; + + for (i = 0; button = buttons[i]; i++) { + if (button.disabled) { + button.disabled = false; + } + } + }, + + /** + * @private + * @return {void} + */ + + bindEvents: function() { + var self = this; + + self.handler = function(e) { + switch (e.type) { + case 'reset': + case 'submit': + self.handleFormEvent(e); + + break; + default: + self['handle' + h.pascalCase(e.type)](e); + } + }; + + h.on(self.dom.el, 'click', self.handler); + h.on(self.dom.el, 'change', self.handler); + h.on(self.dom.el, 'keyup', self.handler); + + if (self.dom.form) { + h.on(self.dom.form, 'reset', self.handler); + h.on(self.dom.form, 'submit', self.handler); + } + }, + + /** + * @private + * @return {void} + */ + + unbindEvents: function() { + var self = this; + + h.off(self.dom.el, 'click', self.handler); + h.off(self.dom.el, 'change', self.handler); + h.off(self.dom.el, 'keyup', self.handler); + + if (self.dom.form) { + h.off(self.dom.form, 'reset', self.handler); + h.off(self.dom.form, 'submit', self.handler); + } + + self.handler = null; + }, + + /** + * @private + * @param {MouseEvent} e + * @return {void} + */ + + handleClick: function(e) { + var self = this, + mixer = self.mixer, + returnValue = null, + controlEl = h.closestParent(e.target, '[data-filter], [data-toggle]', true), + controlSelector = '', + index = -1, + selector = ''; + + if (!controlEl) return; + + if ((controlSelector = self.mixer.config.selectors.control) && !controlEl.matches(controlSelector)) { + return; + } + + e.stopPropagation(); + + if (!mixer.lastClicked) { + mixer.lastClicked = controlEl; + } + + if (typeof mixer.config.callbacks.onMixClick === 'function') { + returnValue = mixer.config.callbacks.onMixClick.call(mixer.lastClicked, mixer.state, e, self); + + if (returnValue === false) { + // User has returned `false` from the callback, so do not handle click + + return; + } + } + + if (controlEl.matches('[data-filter]')) { + selector = controlEl.getAttribute('data-filter'); + + self.activeToggles = []; + self.activeSelectors = self.activeFilters = [selector]; + } else if (controlEl.matches('[data-toggle]')) { + selector = controlEl.getAttribute('data-toggle'); + + self.activeFilters = []; + + if ((index = self.activeToggles.indexOf(selector)) > -1) { + self.activeToggles.splice(index, 1); + } else { + self.activeToggles.push(selector); + } + + if (self.logic === 'and') { + // Compress into single node + + self.activeSelectors = [self.activeToggles]; + } else { + self.activeSelectors = self.activeToggles; + } + } + + self.updateControls(); + + if (self.mixer.config.multifilter.parseOn === 'change') { + self.mixer.parseFilterGroups(); + } + }, + + /** + * @private + * @param {Event} e + * @return {void} + */ + + handleChange: function(e) { + var self = this, + input = e.target; + + e.stopPropagation(); + + switch(input.type) { + case 'text': + case 'search': + case 'email': + case 'select-one': + case 'radio': + self.getSingleValue(input); + + break; + case 'checkbox': + case 'select-multiple': + self.getMultipleValues(input); + + break; + } + + if (self.mixer.config.multifilter.parseOn === 'change') { + self.mixer.parseFilterGroups(); + } + }, + + handleKeyup: function(e) { + var self = this, + input = e.target; + + // NB: Selects can fire keyup events (e.g. multiselect, textual search) + + if (['text', 'search', 'email'].indexOf(input.type) < 0) return; + + if (self.mixer.config.multifilter.parseOn !== 'change') { + self.mixer.getSingleValue(input); + + return; + } + + clearTimeout(self.keyupTimeout); + + self.keyupTimeout = setTimeout(function() { + self.getSingleValue(input); + self.mixer.parseFilterGroups(); + }, self.mixer.config.multifilter.keyupThrottleDuration); + }, + + /** + * @private + * @param {Event} e + * @return {void} + */ + + handleFormEvent: function(e) { + var self = this, + tracker = null, + group = null, + i = -1; + + if (e.type === 'submit') { + e.preventDefault(); + } + + if (e.type === 'reset') { + self.activeFilters = + self.activeToggles = + self.activeSelectors = []; + + self.updateControls(); + } + + if (!self.mixer.multifilterFormEventTracker) { + tracker = self.mixer.multifilterFormEventTracker = new mixitup.MultifilterFormEventTracker(); + + tracker.form = e.target; + + for (i = 0; group = self.mixer.filterGroups[i]; i++) { + if (group.dom.form !== e.target) continue; + + tracker.totalBound++; + } + } else { + tracker = self.mixer.multifilterFormEventTracker; + } + + if (e.target === tracker.form) { + tracker.totalHandled++; + + if (tracker.totalHandled === tracker.totalBound) { + self.mixer.multifilterFormEventTracker = null; + + if (e.type === 'submit' || self.mixer.config.multifilter.parseOn === 'change') { + self.mixer.parseFilterGroups(); + } + } + } + }, + + /** + * @private + * @param {HTMLELement} input + * @return {void} + */ + + getSingleValue: function(input) { + var self = this, + diacriticMap = null, + attributeName = '', + selector = '', + value = '', + i = -1; + + if (input.type.match(/text|search|email/g)) { + attributeName = input.getAttribute('data-search-attribute'); + + if (!attributeName) { + throw new Error('[MixItUp MultiFilter] A valid `data-search-attribute` must be present on text inputs'); + } + + if (input.value.length < self.mixer.config.multifilter.minSearchLength) { + self.activeSelectors = self.activeFilters = self.activeToggles = ['']; + + return; + } + + // Lowercase and trim + + value = input.value.toLowerCase().trim(); + + // Replace diacritics + + for (i = 0; (diacriticMap = diacriticsMap[i]); i++) { + value = value.replace(diacriticMap[1], diacriticMap[0]); + } + + // Strip non-word characters + + value = value.replace(/\W+/g, ' '); + + selector = '[' + attributeName + '*="' + value + '"]'; + } else { + selector = input.value; + } + + if (typeof input.value === 'string') { + self.activeSelectors = + self.activeToggles = + self.activeFilters = + selector ? [selector] : []; + } + }, + + /** + * @private + * @param {HTMLELement} input + * @return {void} + */ + + getMultipleValues: function(input) { + var self = this, + activeToggles = [], + query = '', + item = null, + items = null, + i = -1; + + switch (input.type) { + case 'checkbox': + query = 'input[type="checkbox"]'; + + break; + case 'select-multiple': + query = 'option'; + } + + items = self.dom.el.querySelectorAll(query); + + for (i = 0; item = items[i]; i++) { + if ((item.checked || item.selected) && item.value) { + activeToggles.push(item.value); + } + } + + self.activeFilters = []; + self.activeToggles = activeToggles; + + if (self.logic === 'and') { + // Compress into single node + + self.activeSelectors = [activeToggles]; + } else { + self.activeSelectors = activeToggles; + } + }, + + /** + * @private + * @param {Array.} [controlEls] + * @return {void} + */ + + updateControls: function(controlEls) { + var self = this, + controlEl = null, + controlSelector = '', + controlsSelector = '', + type = '', + i = -1; + + controlSelector = self.mixer.config.selectors.control.trim(); + + controlsSelector = [ + '[data-filter]' + controlSelector, + '[data-toggle]' + controlSelector + ].join(', '); + + controlEls = controlEls || self.dom.el.querySelectorAll(controlsSelector); + + for (i = 0; controlEl = controlEls[i]; i++) { + type = Boolean(controlEl.getAttribute('data-toggle')) ? 'toggle' : 'filter'; + + self.updateControl(controlEl, type); + } + }, + + /** + * @private + * @param {HTMLELement} controlEl + * @param {string} type + * @return {void} + */ + + updateControl: function(controlEl, type) { + var self = this, + selector = controlEl.getAttribute('data-' + type), + activeControls = self.activeToggles.concat(self.activeFilters), + activeClassName = ''; + + activeClassName = h.getClassname(self.mixer.config.classNames, type, self.mixer.config.classNames.modifierActive); + + if (activeControls.indexOf(selector) > -1) { + h.addClass(controlEl, activeClassName); + } else { + h.removeClass(controlEl, activeClassName); + } + }, + + /** + * @private + */ + + updateUi: function() { + var self = this, + controlEls = self.dom.el.querySelectorAll('[data-filter], [data-toggle]'), + inputEls = self.dom.el.querySelectorAll('input[type="radio"], input[type="checkbox"], option'), + activeControls = self.activeToggles.concat(self.activeFilters), + isActive = false, + inputEl = null, + i = -1; + + if (controlEls.length) { + self.updateControls(controlEls, true); + } + + for (i = 0; inputEl = inputEls[i]; i++) { + isActive = activeControls.indexOf(inputEl.value) > -1; + + switch (inputEl.tagName.toLowerCase()) { + case 'option': + inputEl.selected = isActive; + + break; + case 'input': + inputEl.checked = isActive; + + break; + } + } + } + }); + + mixitup.MixerDom.registerAction('afterConstruct', 'multifilter', function() { + this.filterGroups = []; + }); + + /** + * The `mixitup.Mixer` class is extended with API methods relating to + * the MultiFilter extension. + * + * For the full list of API methods, please refer to the MixItUp + * core documentation. + * + * @constructor + * @namespace + * @name Mixer + * @memberof mixitup + * @public + * @since 3.0.0 + */ + + mixitup.Mixer.registerAction('afterConstruct', 'multifilter', function() { + this.filterGroups = []; + this.filterGroupsHash = {}; + this.multifilterFormEventTracker = null; + }); + + mixitup.Mixer.registerAction('afterCacheDom', 'multifilter', function() { + var self = this, + parent = null; + + if (!self.config.multifilter.enable) return; + + switch (self.config.controls.scope) { + case 'local': + parent = self.dom.container; + + break; + case 'global': + parent = self.dom.document; + + break; + default: + throw new Error(mixitup.messages.ERROR_CONFIG_INVALID_CONTROLS_SCOPE); + } + + self.dom.filterGroups = parent.querySelectorAll('[data-filter-group]'); + }); + + mixitup.Mixer.registerAction('beforeInitControls', 'multifilter', function() { + var self = this; + + if (!self.config.multifilter.enable) return; + + self.config.controls.live = true; // force live controls if multifilter is enabled + }); + + mixitup.Mixer.registerAction('afterSanitizeConfig', 'multifilter', function() { + var self = this; + + self.config.multifilter.logicBetweenGroups = self.config.multifilter.logicBetweenGroups.toLowerCase().trim(); + self.config.multifilter.logicWithinGroup = self.config.multifilter.logicWithinGroup.toLowerCase().trim(); + }); + + mixitup.Mixer.registerAction('afterAttach', 'multifilter', function() { + var self = this; + + if (self.dom.filterGroups.length) { + self.indexFilterGroups(); + } + }); + + mixitup.Mixer.registerAction('afterUpdateControls', 'multifilter', function() { + var self = this, + group = null, + i = -1; + + for (i = 0; group = self.filterGroups[i]; i++) { + group.updateControls(); + } + }); + + mixitup.Mixer.registerAction('beforeDestroy', 'multifilter', function() { + var self = this, + group = null, + i = -1; + + for (i = 0; group = self.filterGroups[i]; i++) { + group.unbindEvents(); + } + }); + + mixitup.Mixer.extend( + /** @lends mixitup.Mixer */ + { + /** + * @private + * @return {void} + */ + + indexFilterGroups: function() { + var self = this, + filterGroup = null, + el = null, + i = -1; + + for (i = 0; el = self.dom.filterGroups[i]; i++) { + filterGroup = new mixitup.FilterGroup(); + + filterGroup.init(el, self); + + self.filterGroups.push(filterGroup); + + if (filterGroup.name) { + // If present, also index by name + + if (typeof self.filterGroupsHash[filterGroup.name] !== 'undefined') { + throw new Error('[MixItUp MultiFilter] A filter group with name "' + filterGroup.name + '" already exists'); + } + + self.filterGroupsHash[filterGroup.name] = filterGroup; + } + } + }, + + /** + * @private + * @instance + * @since 2.0.0 + * @param {Array<*>} args + * @return {mixitup.UserInstruction} + */ + + parseParseFilterGroupsArgs: function(args) { + var self = this, + instruction = new mixitup.UserInstruction(), + arg = null, + i = -1; + + instruction.animate = self.config.animation.enable; + instruction.command = new mixitup.CommandFilter(); + + for (i = 0; i < args.length; i++) { + arg = args[i]; + + if (typeof arg === 'boolean') { + instruction.animate = arg; + } else if (typeof arg === 'function') { + instruction.callback = arg; + } + } + + h.freeze(instruction); + + return instruction; + }, + + /** + * Recursively builds up paths between all possible permutations + * of filter group nodes according to the defined logic. + * + * @private + * @return {Array.>} + */ + + getFilterGroupPaths: function() { + var self = this, + buildPath = null, + crawl = null, + nodes = null, + matrix = [], + paths = [], + trackers = [], + i = -1; + + for (i = 0; i < self.filterGroups.length; i++) { + // Filter out groups without any active filters + + if ((nodes = self.filterGroups[i].activeSelectors).length) { + matrix.push(nodes); + + // Initialise tracker for each group + + trackers.push(0); + } + } + + buildPath = function() { + var node = null, + path = [], + i = -1; + + for (i = 0; i < matrix.length; i++) { + node = matrix[i][trackers[i]]; + + if (Array.isArray(node)) { + // AND logic within group + + node = node.join(''); + } + + path.push(node); + } + + path = h.clean(path); + + paths.push(path); + }; + + crawl = function(index) { + index = index || 0; + + var nodes = matrix[index]; + + while (trackers[index] < nodes.length) { + if (index < matrix.length - 1) { + // If not last, recurse + + crawl(index + 1); + } else { + // Last, build selector + + buildPath(); + } + + trackers[index]++; + } + + trackers[index] = 0; + }; + + if (!matrix.length) return ''; + + crawl(); + + return paths; + }, + + /** + * Builds up a selector string from a provided paths array. + * + * @private + * @param {Array.>} paths + * @return {string} + */ + + buildSelectorFromPaths: function(paths) { + var self = this, + path = null, + output = [], + pathSelector = '', + nodeDelineator = '', + i = -1; + + if (!paths.length) { + return ''; + } + + if (self.config.multifilter.logicBetweenGroups === 'or') { + nodeDelineator = ', '; + } + + if (paths.length > 1) { + for (i = 0; i < paths.length; i++) { + path = paths[i]; + + pathSelector = path.join(nodeDelineator); + + if (output.indexOf(pathSelector) < 0) { + output.push(pathSelector); + } + } + + return output.join(', '); + } else { + return paths[0].join(nodeDelineator); + } + }, + + /** + * Traverses the currently active filters in all groups, building up a + * compound selector string as per the defined logic. A filter operation + * is then called on the mixer using the resulting selector. + * + * This method can be used to programmatically trigger the parsing of + * filter groups after manipulations to a group's active selector(s) by + * the `.setFilterGroupSelectors()` API method. + * + * @example + * + * .parseFilterGroups([animate] [, callback]) + * + * @example Example: Triggering parsing after programmatically changing the values of a filter group + * + * mixer.setFilterGroupSelectors('color', ['.green', '.blue']); + * + * mixer.parseFilterGroups(); + * + * @public + * @since 3.0.0 + * @param {boolean} [animate=true] + * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. + * @param {function} [callback=null] + * An optional callback function to be invoked after the operation has completed. + * @return {Promise.} + * A promise resolving with the current state object. + */ + + parseFilterGroups: function() { + var self = this, + instruction = self.parseFilterArgs(arguments), + paths = self.getFilterGroupPaths(), + selector = self.buildSelectorFromPaths(paths), + callback = null, + command = {}; + + if (selector === '') { + selector = self.config.controls.toggleDefault; + } + + instruction.command.selector = selector; + + command.filter = instruction.command; + + if (typeof (callback = self.config.callbacks.onParseFilterGroups) === 'function') { + command = callback(command); + } + + return self.multimix(command, instruction.animate, instruction.callback); + }, + + /** + * Programmatically sets one or more active selectors for a specific filter + * group and updates the group's UI. + * + * Because MixItUp has no way of knowing how to break down a provided + * compound selector into its component groups, we can not use the + * standard `.filter()` or `toggleOn()/toggleOff()` API methods when using + * the MultiFilter extension. Instead, this method allows us to perform + * multi-dimensional filtering via the API by setting the active selectors of + * individual groups and then triggering the `.parseFilterGroups()` method. + * + * If setting multiple active selectors, do not pass a compound selector. + * Instead, pass an array with each item containing a single selector + * string as in example 2. + * + * @example + * + * .setFilterGroupSelectors(groupName, selectors) + * + * @example Example 1: Setting a single active selector for a "color" group + * + * mixer.setFilterGroupSelectors('color', '.green'); + * + * mixer.parseFilterGroups(); + * + * @example Example 2: Setting multiple active selectors for a "size" group + * + * mixer.setFilterGroupSelectors('size', ['.small', '.large']); + * + * mixer.parseFilterGroups(); + * + * @public + * @since 3.2.0 + * @param {string} groupName The name of the filter group as defined in the markup via the `data-filter-group` attribute. + * @param {(string|Array.)} selectors A single selector string, or multiple selector strings as an array. + * @return {void} + */ + + setFilterGroupSelectors: function(groupName, selectors) { + var self = this, + filterGroup = null; + + selectors = Array.isArray(selectors) ? selectors : [selectors]; + + if (typeof (filterGroup = self.filterGroupsHash[groupName]) === 'undefined') { + throw new Error('[MixItUp MultiFilter] No filter group could be found with the name "' + groupName + '"'); + } + + filterGroup.activeToggles = selectors.slice(); + + if (filterGroup.logic === 'and') { + // Compress into single node + + filterGroup.activeSelectors = [filterGroup.activeToggles]; + } else { + filterGroup.activeSelectors = filterGroup.activeToggles; + } + + filterGroup.updateUi(filterGroup.activeToggles); + }, + + /** + * Returns an array of active selectors for a specific filter group. + * + * @example + * + * .getFilterGroupSelectors(groupName) + * + * @example Example: Retrieving the active selectors for a "size" group + * + * mixer.getFilterGroupSelectors('size'); // ['.small', '.large'] + * + * @public + * @since 3.2.0 + * @param {string} groupName The name of the filter group as defined in the markup via the `data-filter-group` attribute. + * @return {void} + */ + + getFilterGroupSelectors: function(groupName) { + var self = this, + filterGroup = null; + + if (typeof (filterGroup = self.filterGroupsHash[groupName]) === 'undefined') { + throw new Error('[MixItUp MultiFilter] No filter group could be found with the name "' + groupName + '"'); + } + + return filterGroup.activeSelectors.slice(); + } + }); + + mixitup.Facade.registerAction('afterConstruct', 'multifilter', function(mixer) { + this.parseFilterGroups = mixer.parseFilterGroups.bind(mixer); + this.setFilterGroupSelectors = mixer.setFilterGroupSelectors.bind(mixer); + this.getFilterGroupSelectors = mixer.getFilterGroupSelectors.bind(mixer); + }); }; + + mixitupMultifilter.TYPE = 'mixitup-extension'; + mixitupMultifilter.NAME = 'mixitup-multifilter'; + mixitupMultifilter.EXTENSION_VERSION = '3.3.6'; + mixitupMultifilter.REQUIRE_CORE_VERSION = '^3.1.2'; + + if (typeof exports === 'object' && typeof module === 'object') { + module.exports = mixitupMultifilter; + } else if (typeof define === 'function' && define.amd) { + define(function() { + return mixitupMultifilter; + }); + } else if (window.mixitup && typeof window.mixitup === 'function') { + mixitupMultifilter(window.mixitup); + } else { + throw new Error('[MixItUp MultiFilter] MixItUp core not found'); + }})(window); \ No newline at end of file diff --git a/dist/mixitup-multifilter.min.js b/dist/mixitup-multifilter.min.js new file mode 100644 index 0000000..a216317 --- /dev/null +++ b/dist/mixitup-multifilter.min.js @@ -0,0 +1,18 @@ +/**! + * MixItUp MultiFilter v3.3.6 + * A UI-builder for powerful multidimensional filtering + * Build 293e0dda-087e-4a76-aadf-e3e8b311b81f + * + * Requires mixitup.js >= v^3.1.2 + * + * @copyright Copyright 2014-2020 KunkaLabs Limited. + * @author KunkaLabs Limited. + * @link https://www.kunkalabs.com/mixitup-multifilter/ + * + * @license Commercial use requires a commercial license. + * https://www.kunkalabs.com/mixitup-multifilter/licenses/ + * + * Non-commercial use permitted under same terms as license. + * http://creativecommons.org/licenses/by-nc/3.0/ + */ +!function(u){"use strict";var e=function(u){var t,i=u.h;if(t=[["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],["AA",/[\uA732]/g],["AE",/[\u00C6\u01FC\u01E2]/g],["AO",/[\uA734]/g],["AU",/[\uA736]/g],["AV",/[\uA738\uA73A]/g],["AY",/[\uA73C]/g],["B",/[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g],["C",/[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g],["D",/[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g],["DZ",/[\u01F1\u01C4]/g],["Dz",/[\u01F2\u01C5]/g],["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],["F",/[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g],["G",/[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g],["H",/[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g],["I",/[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g],["J",/[\u004A\u24BF\uFF2A\u0134\u0248]/g],["K",/[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g],["L",/[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g],["LJ",/[\u01C7]/g],["Lj",/[\u01C8]/g],["M",/[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g],["N",/[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g],["NJ",/[\u01CA]/g],["Nj",/[\u01CB]/g],["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],["OI",/[\u01A2]/g],["OO",/[\uA74E]/g],["OU",/[\u0222]/g],["P",/[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g],["Q",/[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g],["R",/[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g],["S",/[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g],["T",/[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g],["TZ",/[\uA728]/g],["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],["V",/[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g],["VY",/[\uA760]/g],["W",/[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g],["X",/[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g],["Y",/[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g],["Z",/[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g],["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],["aa",/[\uA733]/g],["ae",/[\u00E6\u01FD\u01E3]/g],["ao",/[\uA735]/g],["au",/[\uA737]/g],["av",/[\uA739\uA73B]/g],["ay",/[\uA73D]/g],["b",/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g],["c",/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g],["d",/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g],["dz",/[\u01F3\u01C6]/g],["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],["f",/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g],["g",/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g],["h",/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g],["hv",/[\u0195]/g],["i",/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g],["j",/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g],["k",/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g],["l",/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g],["lj",/[\u01C9]/g],["m",/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g],["n",/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g],["nj",/[\u01CC]/g],["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],["oi",/[\u01A3]/g],["ou",/[\u0223]/g],["oo",/[\uA74F]/g],["p",/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g],["q",/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g],["r",/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g],["s",/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g],["t",/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g],["tz",/[\uA729]/g],["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],["v",/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g],["vy",/[\uA761]/g],["w",/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g],["x",/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g],["y",/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g],["z",/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g]],!u.CORE_VERSION||!i.compareVersions(e.REQUIRE_CORE_VERSION,u.CORE_VERSION))throw new Error("[MixItUp Multifilter] MixItUp Multifilter v"+e.EXTENSION_VERSION+" requires at least MixItUp v"+e.REQUIRE_CORE_VERSION);u.ConfigCallbacks.registerAction("afterConstruct","multifilter",function(){this.onParseFilterGroups=null}),u.ConfigMultifilter=function(){this.enable=!1,this.logicWithinGroup="or",this.logicBetweenGroups="and",this.minSearchLength=3,this.parseOn="change",this.keyupThrottleDuration=350,i.seal(this)},u.Config.registerAction("beforeConstruct","multifilter",function(){this.multifilter=new u.ConfigMultifilter}),u.MultifilterFormEventTracker=function(){this.form=null,this.totalBound=0,this.totalHandled=0,i.seal(this)},u.FilterGroupDom=function(){this.el=null,this.form=null,i.seal(this)},u.FilterGroup=function(){this.name="",this.dom=new u.FilterGroupDom,this.activeSelectors=[],this.activeFilters=[],this.activeToggles=[],this.handler=null,this.mixer=null,this.logic="or",this.parseOn="change",this.keyupTimeout=-1,i.seal(this)},i.extend(u.FilterGroup.prototype,{init:function(u,e){var t=this,i=u.getAttribute("data-logic");t.dom.el=u,this.name=t.dom.el.getAttribute("data-filter-group")||"",t.cacheDom(),t.dom.form&&t.enableButtons(),t.mixer=e,(i&&"and"===i.toLowerCase()||"and"===e.config.multifilter.logicWithinGroup)&&(t.logic="and"),t.bindEvents()},cacheDom:function(){var u=this;u.dom.form=i.closestParent(u.dom.el,"form",!0)},enableButtons:function(){var u=this,e=u.dom.form.querySelectorAll('button[type="submit"]:disabled'),t=null,i=-1;for(i=0;t=e[i];i++)t.disabled&&(t.disabled=!1)},bindEvents:function(){var u=this;u.handler=function(e){switch(e.type){case"reset":case"submit":u.handleFormEvent(e);break;default:u["handle"+i.pascalCase(e.type)](e)}},i.on(u.dom.el,"click",u.handler),i.on(u.dom.el,"change",u.handler),i.on(u.dom.el,"keyup",u.handler),u.dom.form&&(i.on(u.dom.form,"reset",u.handler),i.on(u.dom.form,"submit",u.handler))},unbindEvents:function(){var u=this;i.off(u.dom.el,"click",u.handler),i.off(u.dom.el,"change",u.handler),i.off(u.dom.el,"keyup",u.handler),u.dom.form&&(i.off(u.dom.form,"reset",u.handler),i.off(u.dom.form,"submit",u.handler)),u.handler=null},handleClick:function(u){var e=this,t=e.mixer,r=null,l=i.closestParent(u.target,"[data-filter], [data-toggle]",!0),o="",n=-1,E="";l&&((o=e.mixer.config.selectors.control)&&!l.matches(o)||(u.stopPropagation(),t.lastClicked||(t.lastClicked=l),"function"==typeof t.config.callbacks.onMixClick&&(r=t.config.callbacks.onMixClick.call(t.lastClicked,t.state,u,e),r===!1)||(l.matches("[data-filter]")?(E=l.getAttribute("data-filter"),e.activeToggles=[],e.activeSelectors=e.activeFilters=[E]):l.matches("[data-toggle]")&&(E=l.getAttribute("data-toggle"),e.activeFilters=[],(n=e.activeToggles.indexOf(E))>-1?e.activeToggles.splice(n,1):e.activeToggles.push(E),"and"===e.logic?e.activeSelectors=[e.activeToggles]:e.activeSelectors=e.activeToggles),e.updateControls(),"change"===e.mixer.config.multifilter.parseOn&&e.mixer.parseFilterGroups())))},handleChange:function(u){var e=this,t=u.target;switch(u.stopPropagation(),t.type){case"text":case"search":case"email":case"select-one":case"radio":e.getSingleValue(t);break;case"checkbox":case"select-multiple":e.getMultipleValues(t)}"change"===e.mixer.config.multifilter.parseOn&&e.mixer.parseFilterGroups()},handleKeyup:function(u){var e=this,t=u.target;if(!(["text","search","email"].indexOf(t.type)<0)){if("change"!==e.mixer.config.multifilter.parseOn)return void e.mixer.getSingleValue(t);clearTimeout(e.keyupTimeout),e.keyupTimeout=setTimeout(function(){e.getSingleValue(t),e.mixer.parseFilterGroups()},e.mixer.config.multifilter.keyupThrottleDuration)}},handleFormEvent:function(e){var t=this,i=null,r=null,l=-1;if("submit"===e.type&&e.preventDefault(),"reset"===e.type&&(t.activeFilters=t.activeToggles=t.activeSelectors=[],t.updateControls()),t.mixer.multifilterFormEventTracker)i=t.mixer.multifilterFormEventTracker;else for(i=t.mixer.multifilterFormEventTracker=new u.MultifilterFormEventTracker,i.form=e.target,l=0;r=t.mixer.filterGroups[l];l++)r.dom.form===e.target&&i.totalBound++;e.target===i.form&&(i.totalHandled++,i.totalHandled===i.totalBound&&(t.mixer.multifilterFormEventTracker=null,"submit"!==e.type&&"change"!==t.mixer.config.multifilter.parseOn||t.mixer.parseFilterGroups()))},getSingleValue:function(u){var e=this,i=null,r="",l="",o="",n=-1;if(u.type.match(/text|search|email/g)){if(r=u.getAttribute("data-search-attribute"),!r)throw new Error("[MixItUp MultiFilter] A valid `data-search-attribute` must be present on text inputs");if(u.value.length-1?i.addClass(u,o):i.removeClass(u,o)},updateUi:function(){var u=this,e=u.dom.el.querySelectorAll("[data-filter], [data-toggle]"),t=u.dom.el.querySelectorAll('input[type="radio"], input[type="checkbox"], option'),i=u.activeToggles.concat(u.activeFilters),r=!1,l=null,o=-1;for(e.length&&u.updateControls(e,!0),o=0;l=t[o];o++)switch(r=i.indexOf(l.value)>-1,l.tagName.toLowerCase()){case"option":l.selected=r;break;case"input":l.checked=r}}}),u.MixerDom.registerAction("afterConstruct","multifilter",function(){this.filterGroups=[]}),u.Mixer.registerAction("afterConstruct","multifilter",function(){this.filterGroups=[],this.filterGroupsHash={},this.multifilterFormEventTracker=null}),u.Mixer.registerAction("afterCacheDom","multifilter",function(){var e=this,t=null;if(e.config.multifilter.enable){switch(e.config.controls.scope){case"local":t=e.dom.container;break;case"global":t=e.dom.document;break;default:throw new Error(u.messages.ERROR_CONFIG_INVALID_CONTROLS_SCOPE)}e.dom.filterGroups=t.querySelectorAll("[data-filter-group]")}}),u.Mixer.registerAction("beforeInitControls","multifilter",function(){var u=this;u.config.multifilter.enable&&(u.config.controls.live=!0)}),u.Mixer.registerAction("afterSanitizeConfig","multifilter",function(){var u=this;u.config.multifilter.logicBetweenGroups=u.config.multifilter.logicBetweenGroups.toLowerCase().trim(),u.config.multifilter.logicWithinGroup=u.config.multifilter.logicWithinGroup.toLowerCase().trim()}),u.Mixer.registerAction("afterAttach","multifilter",function(){var u=this;u.dom.filterGroups.length&&u.indexFilterGroups()}),u.Mixer.registerAction("afterUpdateControls","multifilter",function(){var u=this,e=null,t=-1;for(t=0;e=u.filterGroups[t];t++)e.updateControls()}),u.Mixer.registerAction("beforeDestroy","multifilter",function(){var u=this,e=null,t=-1;for(t=0;e=u.filterGroups[t];t++)e.unbindEvents()}),u.Mixer.extend({indexFilterGroups:function(){var e=this,t=null,i=null,r=-1;for(r=0;i=e.dom.filterGroups[r];r++)if(t=new u.FilterGroup,t.init(i,e),e.filterGroups.push(t),t.name){if("undefined"!=typeof e.filterGroupsHash[t.name])throw new Error('[MixItUp MultiFilter] A filter group with name "'+t.name+'" already exists');e.filterGroupsHash[t.name]=t}},parseParseFilterGroupsArgs:function(e){var t=this,r=new u.UserInstruction,l=null,o=-1;for(r.animate=t.config.animation.enable,r.command=new u.CommandFilter,o=0;o1){for(o=0;ocallbacks + +A group of optional callback functions to be invoked at various +points within the lifecycle of a mixer operation. + +### onParseFilterGroups + + + + +A callback function invoked whenever MultiFilter filter groups +are parsed. This occurs whenever the user interacts with filter +group UI, or when the `parseFilterGroups()` API method is called, +but before the resulting filter operation has been triggered. + +By default, this generates the appropriate compound selector and +filters the mixer using a `multimix()` API call internally. This +callback can be used to transform the multimix command object sent +to this API call. + +This is particularly useful when additional behavior such as sorting +or pagination must be taken into account when using the MultiFilter API. + +The callback receives the generated multimix command object, and must +also return a valid multimix command object. + + +|Type | Default +|--- | --- +|`function`| `null` + +###### Example: Overriding the default filtering behavior with `onParseFilterGroups` + +```js +var mixer = mixitup(containerEl, { + callbacks: { + onParseFilterGroups: function(command) { + command.paginate = 3; + command.sort = 'default:desc'; + + return command; + } + } +}); +``` + +

multifilter

+ +A group of properties defining the behavior of your multifilter UI. + +### enable + + + + +A boolean dictating whether or not to enable multifilter functionality. + +If `true`, MixItUp will query the DOM for any elements with a +`data-filter-group` attribute present on instantiation. + + +|Type | Default +|--- | --- +|`boolean`| `false` + +### logicWithinGroup + + + + +A string dictating the logic to use when concatenating selectors within +individual filter groups. + +If set to `'or'` (default), targets will be shown if they match any +active filter in the group. + +If set to `'and'`, targets will be shown only if they match +all active filters in the group. + + +|Type | Default +|--- | --- +|`string`| `'or'` + +### logicBetweenGroups + + + + +A string dictating the logic to use when concatenating each group's +selectors into one single selector. + +If set to `'and'` (default), targets will be shown only if they match +the combined active selectors of all groups. + +If set to `'or'`, targets will be shown if they match the active selectors +of any individual group. + + +|Type | Default +|--- | --- +|`string`| `'and'` + +### minSearchLength + + + + +An integer dictating the minimum number of characters at which the value +of a text input will be included as a multifilter. This prevents short or +incomplete words with many potential matches from triggering +filter operations. + + +|Type | Default +|--- | --- +|`number`| `3` + +### parseOn + + + + +A string dictating when the parsing of filter groups should occur. + +If set to `'change'` (default), the mixer will be filtered whenever the +filtering UI is interacted with. The mode provides real-time filtering with +instant feedback. + +If set to `'submit'`, the mixer will only be filtered when a submit button is +clicked (if using a `` element as a parent). This enables the user to firstly +make their selection, and then trigger filtering once they have +finished making their selection. + +Alternatively, the `mixer.parseFilterGroups()` method can be called via the API at any +time to trigger the parsing of filter groups and filter the mixer. + + +|Type | Default +|--- | --- +|`string`| `'change'` + +### keyupThrottleDuration + + + + +An integer dictating the duration in ms that must elapse between keyup +events in order to trigger a change. + +Setting a comfortable delay of ~350ms prevents the mixer from being +thrashed while typing occurs. + + +|Type | Default +|--- | --- +|`number`| `350` + + diff --git a/docs/mixitup.Mixer.md b/docs/mixitup.Mixer.md new file mode 100644 index 0000000..befce49 --- /dev/null +++ b/docs/mixitup.Mixer.md @@ -0,0 +1,112 @@ +# mixitup.Mixer + +## Overview + +The `mixitup.Mixer` class is extended with API methods relating to +the MultiFilter extension. + +For the full list of API methods, please refer to the MixItUp +core documentation. + +### Contents + +- [parseFilterGroups()](#parseFilterGroups) +- [setFilterGroupSelectors()](#setFilterGroupSelectors) +- [getFilterGroupSelectors()](#getFilterGroupSelectors) + + +

parseFilterGroups()

+ +*Version added: 3.0.0* + +`.parseFilterGroups([animate] [, callback])` + +Traverses the currently active filters in all groups, building up a +compound selector string as per the defined logic. A filter operation +is then called on the mixer using the resulting selector. + +This method can be used to programmatically trigger the parsing of +filter groups after manipulations to a group's active selector(s) by +the `.setFilterGroupSelectors()` API method. + +| |Type | Name | Description +|---|--- | --- | --- +|Param |`boolean` | `[animate]` | An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. +|Param |`function` | `[callback]` | An optional callback function to be invoked after the operation has completed. +|Returns |`Promise.` | A promise resolving with the current state object. + + +###### Example: Triggering parsing after programmatically changing the values of a filter group + +```js + +mixer.setFilterGroupSelectors('color', ['.green', '.blue']); + +mixer.parseFilterGroups(); +``` + +

setFilterGroupSelectors()

+ +*Version added: 3.2.0* + +`.setFilterGroupSelectors(groupName, selectors)` + +Programmatically sets one or more active selectors for a specific filter +group and updates the group's UI. + +Because MixItUp has no way of knowing how to break down a provided +compound selector into its component groups, we can not use the +standard `.filter()` or `toggleOn()/toggleOff()` API methods when using +the MultiFilter extension. Instead, this method allows us to perform +multi-dimensional filtering via the API by setting the active selectors of +individual groups and then triggering the `.parseFilterGroups()` method. + +If setting multiple active selectors, do not pass a compound selector. +Instead, pass an array with each item containing a single selector +string as in example 2. + +| |Type | Name | Description +|---|--- | --- | --- +|Param |`string` | `groupName` | The name of the filter group as defined in the markup via the `data-filter-group` attribute. +|Param |`string, Array.` | `selectors` | A single selector string, or multiple selector strings as an array. +|Returns |`void` | + + +###### Example 1: Setting a single active selector for a "color" group + +```js + +mixer.setFilterGroupSelectors('color', '.green'); + +mixer.parseFilterGroups(); +``` +###### Example 2: Setting multiple active selectors for a "size" group + +```js + +mixer.setFilterGroupSelectors('size', ['.small', '.large']); + +mixer.parseFilterGroups(); +``` + +

getFilterGroupSelectors()

+ +*Version added: 3.2.0* + +`.getFilterGroupSelectors(groupName)` + +Returns an array of active selectors for a specific filter group. + +| |Type | Name | Description +|---|--- | --- | --- +|Param |`string` | `groupName` | The name of the filter group as defined in the markup via the `data-filter-group` attribute. +|Returns |`void` | + + +###### Example: Retrieving the active selectors for a "size" group + +```js + +mixer.getFilterGroupSelectors('size'); // ['.small', '.large'] +``` + diff --git a/docs/mixitup.md b/docs/mixitup.md new file mode 100644 index 0000000..09409a1 --- /dev/null +++ b/docs/mixitup.md @@ -0,0 +1,11 @@ +#() + + + + + + +| |Type | Name | Description +|---|--- | --- | --- +|Returns | + diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..db53732 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,95 @@ +var gulp = require('gulp'); +var jshint = require('gulp-jshint'); +var stylish = require('jshint-stylish'); +var rename = require('gulp-rename'); +var jscs = require('gulp-jscs'); +var uglify = require('gulp-uglify'); +var livereload = require('gulp-livereload'); +var sourcemaps = require('gulp-sourcemaps'); +var exec = require('child_process').exec; + +gulp.task('default', ['watch']); + +gulp.task('watch', function() { + livereload.listen(35730); + + gulp.watch([ + './src/*.js', + './src/*.hbs' + ], ['reload-js']) + .on('change', function(e) { + console.log( + '[gulp-watch] file ' + + e.path + + ' was ' + + e.type + + ', building' + ); + }); +}); + +gulp.task('reload-js', ['build-dist'], function() { + return livereload.changed(); +}); + +gulp.task('prod', ['uglify']); + +gulp.task('uglify', ['build'], function() { + return gulp.src([ + './dist/mixitup-multifilter.js' + ]) + .pipe(uglify({ + preserveComments: 'license' + })) + .pipe(rename('mixitup-multifilter.min.js')) + .on('error', function(e) { + console.error('[uglify] ' + e.message); + }) + .pipe(sourcemaps.write('./')) + .pipe(gulp.dest('./dist/')) + .pipe(gulp.dest('./demos/')); +}); + +gulp.task('build', ['build-dist'], function(done) { + exec('node node_modules/mixitup-build/docs.js -s mixitup-multifilter.js', function(e, out) { + if (out) { + console.log(out); + } + + done(e); + }); +}); + + +gulp.task('build-dist', ['lint', 'code-style'], function(done) { + exec('node node_modules/mixitup-build/dist.js -o mixitup-multifilter.js', function(e, out) { + if (out) { + console.log(out); + } + + done(e); + }); +}); + +gulp.task('lint', function() { + return gulp.src([ + './src/*.js' + ], + { + base: '/' + }) + .pipe(jshint('./.jshintrc')) + .pipe(jshint.reporter(stylish)) + .pipe(jshint.reporter('fail')); +}); + +gulp.task('code-style', function() { + return gulp.src([ + './src/*.js' + ], + { + base: '/' + }) + .pipe(jscs()) + .pipe(jscs.reporter()); +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..341e21c --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "mixitup-multifilter", + "title": "MixItUp MultiFilter", + "version": "3.3.6", + "description": "A UI-builder for powerful multidimensional filtering", + "author": "KunkaLabs Limited", + "homepage": "https://www.kunkalabs.com/mixitup-multifilter/", + "license" : "SEE LICENSE IN license.md", + "private": true, + "main": "./dist/mixitup-multifilter.js", + "dependencies": { + "mixitup": "^3.1.2" + }, + "devDependencies": { + "gulp": "^3.8.8", + "gulp-jscs": "^4.0.0", + "gulp-jshint": "^1.8.5", + "gulp-livereload": "~2.1.1", + "gulp-rename": "^1.2.2", + "gulp-sourcemaps": "^1.5.2", + "gulp-uglify": "2.0.1", + "jshint-stylish": "~1.0.0", + "merge-stream": "^0.1.7", + "vinyl-buffer": "^1.0.0", + "vinyl-source-stream": "^1.1.0", + "mocha": "^3.0.2", + "jsdom-global": "2.0.0", + "chai": "^3.5.0", + "chai-as-promised": "^5.3.0", + "chai-shallow-deep-equal": "^1.4.0", + "mixitup-build": "git://github.com/patrickkunka/mixitup-build.git" + } +} \ No newline at end of file diff --git a/src/banner.js b/src/banner.js new file mode 100644 index 0000000..75f1e64 --- /dev/null +++ b/src/banner.js @@ -0,0 +1,17 @@ +/**! + * {{title}} v{{version}} + * {{description}} + * Build {{buildId}} + * + * Requires mixitup.js >= v{{coreVersion}} + * + * @copyright Copyright {{beginCopyrightYear}}-{{currentYear}} {{author}}. + * @author {{author}}. + * @link {{websiteUrl}} + * + * @license Commercial use requires a commercial license. + * {{commercialLicenseUrl}} + * + * Non-commercial use permitted under same terms as {{nonComercialLicenseTitle}} license. + * {{nonCommercialLicenseUrl}} + */ \ No newline at end of file diff --git a/src/config-callbacks.js b/src/config-callbacks.js new file mode 100644 index 0000000..05491fe --- /dev/null +++ b/src/config-callbacks.js @@ -0,0 +1,53 @@ +/* global mixitup */ + +/** + * A group of optional callback functions to be invoked at various + * points within the lifecycle of a mixer operation. + * + * @constructor + * @memberof mixitup.Config + * @name callbacks + * @namespace + * @public + * @since 3.0.0 + */ + +mixitup.ConfigCallbacks.registerAction('afterConstruct', 'multifilter', function() { + /** + * A callback function invoked whenever MultiFilter filter groups + * are parsed. This occurs whenever the user interacts with filter + * group UI, or when the `parseFilterGroups()` API method is called, + * but before the resulting filter operation has been triggered. + * + * By default, this generates the appropriate compound selector and + * filters the mixer using a `multimix()` API call internally. This + * callback can be used to transform the multimix command object sent + * to this API call. + * + * This is particularly useful when additional behavior such as sorting + * or pagination must be taken into account when using the MultiFilter API. + * + * The callback receives the generated multimix command object, and must + * also return a valid multimix command object. + * + * @example Example: Overriding the default filtering behavior with `onParseFilterGroups` + * var mixer = mixitup(containerEl, { + * callbacks: { + * onParseFilterGroups: function(command) { + * command.paginate = 3; + * command.sort = 'default:desc'; + * + * return command; + * } + * } + * }); + * + * @name onParseFilterGroups + * @memberof mixitup.Config.callbacks + * @instance + * @type {function} + * @default null + */ + + this.onParseFilterGroups = null; +}); \ No newline at end of file diff --git a/src/config-multifilter.js b/src/config-multifilter.js new file mode 100644 index 0000000..2f1766b --- /dev/null +++ b/src/config-multifilter.js @@ -0,0 +1,125 @@ +/* global mixitup, h */ + +/** + * A group of properties defining the behavior of your multifilter UI. + * + * @constructor + * @memberof mixitup.Config + * @name multifilter + * @namespace + * @public + * @since 3.0.0 + */ + +mixitup.ConfigMultifilter = function() { + + /** + * A boolean dictating whether or not to enable multifilter functionality. + * + * If `true`, MixItUp will query the DOM for any elements with a + * `data-filter-group` attribute present on instantiation. + * + * @name enable + * @memberof mixitup.Config.multifilter + * @instance + * @type {boolean} + * @default false + */ + + this.enable = false; + + /** + * A string dictating the logic to use when concatenating selectors within + * individual filter groups. + * + * If set to `'or'` (default), targets will be shown if they match any + * active filter in the group. + * + * If set to `'and'`, targets will be shown only if they match + * all active filters in the group. + * + * @name logicWithinGroup + * @memberof mixitup.Config.multifilter + * @instance + * @type {string} + * @default 'or' + */ + + this.logicWithinGroup = 'or'; + + /** + * A string dictating the logic to use when concatenating each group's + * selectors into one single selector. + * + * If set to `'and'` (default), targets will be shown only if they match + * the combined active selectors of all groups. + * + * If set to `'or'`, targets will be shown if they match the active selectors + * of any individual group. + * + * @name logicBetweenGroups + * @memberof mixitup.Config.multifilter + * @instance + * @type {string} + * @default 'and' + */ + + this.logicBetweenGroups = 'and'; + + /** + * An integer dictating the minimum number of characters at which the value + * of a text input will be included as a multifilter. This prevents short or + * incomplete words with many potential matches from triggering + * filter operations. + * + * @name minSearchLength + * @memberof mixitup.Config.multifilter + * @instance + * @type {number} + * @default 3 + */ + + this.minSearchLength = 3; + + /** + * A string dictating when the parsing of filter groups should occur. + * + * If set to `'change'` (default), the mixer will be filtered whenever the + * filtering UI is interacted with. The mode provides real-time filtering with + * instant feedback. + * + * If set to `'submit'`, the mixer will only be filtered when a submit button is + * clicked (if using a `` element as a parent). This enables the user to firstly + * make their selection, and then trigger filtering once they have + * finished making their selection. + * + * Alternatively, the `mixer.parseFilterGroups()` method can be called via the API at any + * time to trigger the parsing of filter groups and filter the mixer. + * + * @name parseOn + * @memberof mixitup.Config.multifilter + * @instance + * @type {string} + * @default 'change' + */ + + this.parseOn = 'change'; + + /** + * An integer dictating the duration in ms that must elapse between keyup + * events in order to trigger a change. + * + * Setting a comfortable delay of ~350ms prevents the mixer from being + * thrashed while typing occurs. + * + * @name keyupThrottleDuration + * @memberof mixitup.Config.multifilter + * @instance + * @type {number} + * @default 350 + */ + + this.keyupThrottleDuration = 350; + + h.seal(this); +}; \ No newline at end of file diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..7c24f9c --- /dev/null +++ b/src/config.js @@ -0,0 +1,20 @@ +/* global mixitup */ + +/** + * The MixItUp configuration object is extended with properties relating to + * the MultiFilter extension. + * + * For the full list of configuration options, please refer to the MixItUp + * core documentation. + * + * @constructor + * @memberof mixitup + * @name Config + * @namespace + * @public + * @since 2.0.0 + */ + +mixitup.Config.registerAction('beforeConstruct', 'multifilter', function() { + this.multifilter = new mixitup.ConfigMultifilter(); +}); \ No newline at end of file diff --git a/src/diacritics-map.js b/src/diacritics-map.js new file mode 100644 index 0000000..c5f5804 --- /dev/null +++ b/src/diacritics-map.js @@ -0,0 +1,88 @@ +/* global diacriticsMap:true */ + +diacriticsMap = [ + ['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], + ['AA', /[\uA732]/g], + ['AE', /[\u00C6\u01FC\u01E2]/g], + ['AO', /[\uA734]/g], + ['AU', /[\uA736]/g], + ['AV', /[\uA738\uA73A]/g], + ['AY', /[\uA73C]/g], + ['B', /[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g], + ['C', /[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g], + ['D', /[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g], + ['DZ', /[\u01F1\u01C4]/g], + ['Dz', /[\u01F2\u01C5]/g], + ['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], + ['F', /[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g], + ['G', /[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g], + ['H', /[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g], + ['I', /[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g], + ['J', /[\u004A\u24BF\uFF2A\u0134\u0248]/g], + ['K', /[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g], + ['L', /[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g], + ['LJ', /[\u01C7]/g], + ['Lj', /[\u01C8]/g], + ['M', /[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g], + ['N', /[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g], + ['NJ', /[\u01CA]/g], + ['Nj', /[\u01CB]/g], + ['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], + ['OI', /[\u01A2]/g], + ['OO', /[\uA74E]/g], + ['OU', /[\u0222]/g], + ['P', /[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g], + ['Q', /[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g], + ['R', /[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g], + ['S', /[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g], + ['T', /[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g], + ['TZ', /[\uA728]/g], + ['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], + ['V', /[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g], + ['VY', /[\uA760]/g], + ['W', /[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g], + ['X', /[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g], + ['Y', /[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g], + ['Z', /[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g], + ['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], + ['aa', /[\uA733]/g], + ['ae', /[\u00E6\u01FD\u01E3]/g], + ['ao', /[\uA735]/g], + ['au', /[\uA737]/g], + ['av', /[\uA739\uA73B]/g], + ['ay', /[\uA73D]/g], + ['b', /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g], + ['c', /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g], + ['d', /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g], + ['dz', /[\u01F3\u01C6]/g], + ['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], + ['f', /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g], + ['g', /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g], + ['h', /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g], + ['hv', /[\u0195]/g], + ['i', /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g], + ['j', /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g], + ['k', /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g], + ['l', /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g], + ['lj', /[\u01C9]/g], + ['m', /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g], + ['n', /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g], + ['nj', /[\u01CC]/g], + ['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], + ['oi', /[\u01A3]/g], + ['ou', /[\u0223]/g], + ['oo', /[\uA74F]/g], + ['p', /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g], + ['q', /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g], + ['r', /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g], + ['s', /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g], + ['t', /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g], + ['tz', /[\uA729]/g], + ['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], + ['v', /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g], + ['vy', /[\uA761]/g], + ['w', /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g], + ['x', /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g], + ['y', /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g], + ['z', /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g] +]; \ No newline at end of file diff --git a/src/facade.js b/src/facade.js new file mode 100644 index 0000000..258ed9f --- /dev/null +++ b/src/facade.js @@ -0,0 +1,7 @@ +/* global mixitup */ + +mixitup.Facade.registerAction('afterConstruct', 'multifilter', function(mixer) { + this.parseFilterGroups = mixer.parseFilterGroups.bind(mixer); + this.setFilterGroupSelectors = mixer.setFilterGroupSelectors.bind(mixer); + this.getFilterGroupSelectors = mixer.getFilterGroupSelectors.bind(mixer); +}); \ No newline at end of file diff --git a/src/filter-group-dom.js b/src/filter-group-dom.js new file mode 100644 index 0000000..74b1f64 --- /dev/null +++ b/src/filter-group-dom.js @@ -0,0 +1,8 @@ +/* global mixitup, h */ + +mixitup.FilterGroupDom = function() { + this.el = null; + this.form = null; + + h.seal(this); +}; \ No newline at end of file diff --git a/src/filter-group.js b/src/filter-group.js new file mode 100644 index 0000000..8d96c5f --- /dev/null +++ b/src/filter-group.js @@ -0,0 +1,483 @@ +/* global mixitup, h, diacriticsMap */ + +mixitup.FilterGroup = function() { + this.name = ''; + this.dom = new mixitup.FilterGroupDom(); + this.activeSelectors = []; + this.activeFilters = []; + this.activeToggles = []; + this.handler = null; + this.mixer = null; + this.logic = 'or'; + this.parseOn = 'change'; + this.keyupTimeout = -1; + + h.seal(this); +}; + +h.extend(mixitup.FilterGroup.prototype, { + + /** + * @private + * @param {HTMLELement} el + * @param {mixitup.Mixer} mixer + * @return {void} + */ + + init: function(el, mixer) { + var self = this, + logic = el.getAttribute('data-logic'); + + self.dom.el = el; + + this.name = self.dom.el.getAttribute('data-filter-group') || ''; + + self.cacheDom(); + + if (self.dom.form) { + self.enableButtons(); + } + + self.mixer = mixer; + + if ((logic && logic.toLowerCase() === 'and') || mixer.config.multifilter.logicWithinGroup === 'and') { + // override default group logic + + self.logic = 'and'; + + } + + self.bindEvents(); + }, + + /** + * @private + * @return {void} + */ + + cacheDom: function() { + var self = this; + + self.dom.form = h.closestParent(self.dom.el, 'form', true); + }, + + enableButtons: function() { + var self = this, + buttons = self.dom.form.querySelectorAll('button[type="submit"]:disabled'), + button = null, + i = -1; + + for (i = 0; button = buttons[i]; i++) { + if (button.disabled) { + button.disabled = false; + } + } + }, + + /** + * @private + * @return {void} + */ + + bindEvents: function() { + var self = this; + + self.handler = function(e) { + switch (e.type) { + case 'reset': + case 'submit': + self.handleFormEvent(e); + + break; + default: + self['handle' + h.pascalCase(e.type)](e); + } + }; + + h.on(self.dom.el, 'click', self.handler); + h.on(self.dom.el, 'change', self.handler); + h.on(self.dom.el, 'keyup', self.handler); + + if (self.dom.form) { + h.on(self.dom.form, 'reset', self.handler); + h.on(self.dom.form, 'submit', self.handler); + } + }, + + /** + * @private + * @return {void} + */ + + unbindEvents: function() { + var self = this; + + h.off(self.dom.el, 'click', self.handler); + h.off(self.dom.el, 'change', self.handler); + h.off(self.dom.el, 'keyup', self.handler); + + if (self.dom.form) { + h.off(self.dom.form, 'reset', self.handler); + h.off(self.dom.form, 'submit', self.handler); + } + + self.handler = null; + }, + + /** + * @private + * @param {MouseEvent} e + * @return {void} + */ + + handleClick: function(e) { + var self = this, + mixer = self.mixer, + returnValue = null, + controlEl = h.closestParent(e.target, '[data-filter], [data-toggle]', true), + controlSelector = '', + index = -1, + selector = ''; + + if (!controlEl) return; + + if ((controlSelector = self.mixer.config.selectors.control) && !controlEl.matches(controlSelector)) { + return; + } + + e.stopPropagation(); + + if (!mixer.lastClicked) { + mixer.lastClicked = controlEl; + } + + if (typeof mixer.config.callbacks.onMixClick === 'function') { + returnValue = mixer.config.callbacks.onMixClick.call(mixer.lastClicked, mixer.state, e, self); + + if (returnValue === false) { + // User has returned `false` from the callback, so do not handle click + + return; + } + } + + if (controlEl.matches('[data-filter]')) { + selector = controlEl.getAttribute('data-filter'); + + self.activeToggles = []; + self.activeSelectors = self.activeFilters = [selector]; + } else if (controlEl.matches('[data-toggle]')) { + selector = controlEl.getAttribute('data-toggle'); + + self.activeFilters = []; + + if ((index = self.activeToggles.indexOf(selector)) > -1) { + self.activeToggles.splice(index, 1); + } else { + self.activeToggles.push(selector); + } + + if (self.logic === 'and') { + // Compress into single node + + self.activeSelectors = [self.activeToggles]; + } else { + self.activeSelectors = self.activeToggles; + } + } + + self.updateControls(); + + if (self.mixer.config.multifilter.parseOn === 'change') { + self.mixer.parseFilterGroups(); + } + }, + + /** + * @private + * @param {Event} e + * @return {void} + */ + + handleChange: function(e) { + var self = this, + input = e.target; + + e.stopPropagation(); + + switch(input.type) { + case 'text': + case 'search': + case 'email': + case 'select-one': + case 'radio': + self.getSingleValue(input); + + break; + case 'checkbox': + case 'select-multiple': + self.getMultipleValues(input); + + break; + } + + if (self.mixer.config.multifilter.parseOn === 'change') { + self.mixer.parseFilterGroups(); + } + }, + + handleKeyup: function(e) { + var self = this, + input = e.target; + + // NB: Selects can fire keyup events (e.g. multiselect, textual search) + + if (['text', 'search', 'email'].indexOf(input.type) < 0) return; + + if (self.mixer.config.multifilter.parseOn !== 'change') { + self.mixer.getSingleValue(input); + + return; + } + + clearTimeout(self.keyupTimeout); + + self.keyupTimeout = setTimeout(function() { + self.getSingleValue(input); + self.mixer.parseFilterGroups(); + }, self.mixer.config.multifilter.keyupThrottleDuration); + }, + + /** + * @private + * @param {Event} e + * @return {void} + */ + + handleFormEvent: function(e) { + var self = this, + tracker = null, + group = null, + i = -1; + + if (e.type === 'submit') { + e.preventDefault(); + } + + if (e.type === 'reset') { + self.activeFilters = + self.activeToggles = + self.activeSelectors = []; + + self.updateControls(); + } + + if (!self.mixer.multifilterFormEventTracker) { + tracker = self.mixer.multifilterFormEventTracker = new mixitup.MultifilterFormEventTracker(); + + tracker.form = e.target; + + for (i = 0; group = self.mixer.filterGroups[i]; i++) { + if (group.dom.form !== e.target) continue; + + tracker.totalBound++; + } + } else { + tracker = self.mixer.multifilterFormEventTracker; + } + + if (e.target === tracker.form) { + tracker.totalHandled++; + + if (tracker.totalHandled === tracker.totalBound) { + self.mixer.multifilterFormEventTracker = null; + + if (e.type === 'submit' || self.mixer.config.multifilter.parseOn === 'change') { + self.mixer.parseFilterGroups(); + } + } + } + }, + + /** + * @private + * @param {HTMLELement} input + * @return {void} + */ + + getSingleValue: function(input) { + var self = this, + diacriticMap = null, + attributeName = '', + selector = '', + value = '', + i = -1; + + if (input.type.match(/text|search|email/g)) { + attributeName = input.getAttribute('data-search-attribute'); + + if (!attributeName) { + throw new Error('[MixItUp MultiFilter] A valid `data-search-attribute` must be present on text inputs'); + } + + if (input.value.length < self.mixer.config.multifilter.minSearchLength) { + self.activeSelectors = self.activeFilters = self.activeToggles = ['']; + + return; + } + + // Lowercase and trim + + value = input.value.toLowerCase().trim(); + + // Replace diacritics + + for (i = 0; (diacriticMap = diacriticsMap[i]); i++) { + value = value.replace(diacriticMap[1], diacriticMap[0]); + } + + // Strip non-word characters + + value = value.replace(/\W+/g, ' '); + + selector = '[' + attributeName + '*="' + value + '"]'; + } else { + selector = input.value; + } + + if (typeof input.value === 'string') { + self.activeSelectors = + self.activeToggles = + self.activeFilters = + selector ? [selector] : []; + } + }, + + /** + * @private + * @param {HTMLELement} input + * @return {void} + */ + + getMultipleValues: function(input) { + var self = this, + activeToggles = [], + query = '', + item = null, + items = null, + i = -1; + + switch (input.type) { + case 'checkbox': + query = 'input[type="checkbox"]'; + + break; + case 'select-multiple': + query = 'option'; + } + + items = self.dom.el.querySelectorAll(query); + + for (i = 0; item = items[i]; i++) { + if ((item.checked || item.selected) && item.value) { + activeToggles.push(item.value); + } + } + + self.activeFilters = []; + self.activeToggles = activeToggles; + + if (self.logic === 'and') { + // Compress into single node + + self.activeSelectors = [activeToggles]; + } else { + self.activeSelectors = activeToggles; + } + }, + + /** + * @private + * @param {Array.} [controlEls] + * @return {void} + */ + + updateControls: function(controlEls) { + var self = this, + controlEl = null, + controlSelector = '', + controlsSelector = '', + type = '', + i = -1; + + controlSelector = self.mixer.config.selectors.control.trim(); + + controlsSelector = [ + '[data-filter]' + controlSelector, + '[data-toggle]' + controlSelector + ].join(', '); + + controlEls = controlEls || self.dom.el.querySelectorAll(controlsSelector); + + for (i = 0; controlEl = controlEls[i]; i++) { + type = Boolean(controlEl.getAttribute('data-toggle')) ? 'toggle' : 'filter'; + + self.updateControl(controlEl, type); + } + }, + + /** + * @private + * @param {HTMLELement} controlEl + * @param {string} type + * @return {void} + */ + + updateControl: function(controlEl, type) { + var self = this, + selector = controlEl.getAttribute('data-' + type), + activeControls = self.activeToggles.concat(self.activeFilters), + activeClassName = ''; + + activeClassName = h.getClassname(self.mixer.config.classNames, type, self.mixer.config.classNames.modifierActive); + + if (activeControls.indexOf(selector) > -1) { + h.addClass(controlEl, activeClassName); + } else { + h.removeClass(controlEl, activeClassName); + } + }, + + /** + * @private + */ + + updateUi: function() { + var self = this, + controlEls = self.dom.el.querySelectorAll('[data-filter], [data-toggle]'), + inputEls = self.dom.el.querySelectorAll('input[type="radio"], input[type="checkbox"], option'), + activeControls = self.activeToggles.concat(self.activeFilters), + isActive = false, + inputEl = null, + i = -1; + + if (controlEls.length) { + self.updateControls(controlEls, true); + } + + for (i = 0; inputEl = inputEls[i]; i++) { + isActive = activeControls.indexOf(inputEl.value) > -1; + + switch (inputEl.tagName.toLowerCase()) { + case 'option': + inputEl.selected = isActive; + + break; + case 'input': + inputEl.checked = isActive; + + break; + } + } + } +}); \ No newline at end of file diff --git a/src/mixer-dom.js b/src/mixer-dom.js new file mode 100644 index 0000000..62f8dc1 --- /dev/null +++ b/src/mixer-dom.js @@ -0,0 +1,5 @@ +/* global mixitup */ + +mixitup.MixerDom.registerAction('afterConstruct', 'multifilter', function() { + this.filterGroups = []; +}); \ No newline at end of file diff --git a/src/mixer.js b/src/mixer.js new file mode 100644 index 0000000..d08ac6e --- /dev/null +++ b/src/mixer.js @@ -0,0 +1,416 @@ +/* global mixitup, h */ + +/** + * The `mixitup.Mixer` class is extended with API methods relating to + * the MultiFilter extension. + * + * For the full list of API methods, please refer to the MixItUp + * core documentation. + * + * @constructor + * @namespace + * @name Mixer + * @memberof mixitup + * @public + * @since 3.0.0 + */ + +mixitup.Mixer.registerAction('afterConstruct', 'multifilter', function() { + this.filterGroups = []; + this.filterGroupsHash = {}; + this.multifilterFormEventTracker = null; +}); + +mixitup.Mixer.registerAction('afterCacheDom', 'multifilter', function() { + var self = this, + parent = null; + + if (!self.config.multifilter.enable) return; + + switch (self.config.controls.scope) { + case 'local': + parent = self.dom.container; + + break; + case 'global': + parent = self.dom.document; + + break; + default: + throw new Error(mixitup.messages.ERROR_CONFIG_INVALID_CONTROLS_SCOPE); + } + + self.dom.filterGroups = parent.querySelectorAll('[data-filter-group]'); +}); + +mixitup.Mixer.registerAction('beforeInitControls', 'multifilter', function() { + var self = this; + + if (!self.config.multifilter.enable) return; + + self.config.controls.live = true; // force live controls if multifilter is enabled +}); + +mixitup.Mixer.registerAction('afterSanitizeConfig', 'multifilter', function() { + var self = this; + + self.config.multifilter.logicBetweenGroups = self.config.multifilter.logicBetweenGroups.toLowerCase().trim(); + self.config.multifilter.logicWithinGroup = self.config.multifilter.logicWithinGroup.toLowerCase().trim(); +}); + +mixitup.Mixer.registerAction('afterAttach', 'multifilter', function() { + var self = this; + + if (self.dom.filterGroups.length) { + self.indexFilterGroups(); + } +}); + +mixitup.Mixer.registerAction('afterUpdateControls', 'multifilter', function() { + var self = this, + group = null, + i = -1; + + for (i = 0; group = self.filterGroups[i]; i++) { + group.updateControls(); + } +}); + +mixitup.Mixer.registerAction('beforeDestroy', 'multifilter', function() { + var self = this, + group = null, + i = -1; + + for (i = 0; group = self.filterGroups[i]; i++) { + group.unbindEvents(); + } +}); + +mixitup.Mixer.extend( +/** @lends mixitup.Mixer */ +{ + /** + * @private + * @return {void} + */ + + indexFilterGroups: function() { + var self = this, + filterGroup = null, + el = null, + i = -1; + + for (i = 0; el = self.dom.filterGroups[i]; i++) { + filterGroup = new mixitup.FilterGroup(); + + filterGroup.init(el, self); + + self.filterGroups.push(filterGroup); + + if (filterGroup.name) { + // If present, also index by name + + if (typeof self.filterGroupsHash[filterGroup.name] !== 'undefined') { + throw new Error('[MixItUp MultiFilter] A filter group with name "' + filterGroup.name + '" already exists'); + } + + self.filterGroupsHash[filterGroup.name] = filterGroup; + } + } + }, + + /** + * @private + * @instance + * @since 2.0.0 + * @param {Array<*>} args + * @return {mixitup.UserInstruction} + */ + + parseParseFilterGroupsArgs: function(args) { + var self = this, + instruction = new mixitup.UserInstruction(), + arg = null, + i = -1; + + instruction.animate = self.config.animation.enable; + instruction.command = new mixitup.CommandFilter(); + + for (i = 0; i < args.length; i++) { + arg = args[i]; + + if (typeof arg === 'boolean') { + instruction.animate = arg; + } else if (typeof arg === 'function') { + instruction.callback = arg; + } + } + + h.freeze(instruction); + + return instruction; + }, + + /** + * Recursively builds up paths between all possible permutations + * of filter group nodes according to the defined logic. + * + * @private + * @return {Array.>} + */ + + getFilterGroupPaths: function() { + var self = this, + buildPath = null, + crawl = null, + nodes = null, + matrix = [], + paths = [], + trackers = [], + i = -1; + + for (i = 0; i < self.filterGroups.length; i++) { + // Filter out groups without any active filters + + if ((nodes = self.filterGroups[i].activeSelectors).length) { + matrix.push(nodes); + + // Initialise tracker for each group + + trackers.push(0); + } + } + + buildPath = function() { + var node = null, + path = [], + i = -1; + + for (i = 0; i < matrix.length; i++) { + node = matrix[i][trackers[i]]; + + if (Array.isArray(node)) { + // AND logic within group + + node = node.join(''); + } + + path.push(node); + } + + path = h.clean(path); + + paths.push(path); + }; + + crawl = function(index) { + index = index || 0; + + var nodes = matrix[index]; + + while (trackers[index] < nodes.length) { + if (index < matrix.length - 1) { + // If not last, recurse + + crawl(index + 1); + } else { + // Last, build selector + + buildPath(); + } + + trackers[index]++; + } + + trackers[index] = 0; + }; + + if (!matrix.length) return ''; + + crawl(); + + return paths; + }, + + /** + * Builds up a selector string from a provided paths array. + * + * @private + * @param {Array.>} paths + * @return {string} + */ + + buildSelectorFromPaths: function(paths) { + var self = this, + path = null, + output = [], + pathSelector = '', + nodeDelineator = '', + i = -1; + + if (!paths.length) { + return ''; + } + + if (self.config.multifilter.logicBetweenGroups === 'or') { + nodeDelineator = ', '; + } + + if (paths.length > 1) { + for (i = 0; i < paths.length; i++) { + path = paths[i]; + + pathSelector = path.join(nodeDelineator); + + if (output.indexOf(pathSelector) < 0) { + output.push(pathSelector); + } + } + + return output.join(', '); + } else { + return paths[0].join(nodeDelineator); + } + }, + + /** + * Traverses the currently active filters in all groups, building up a + * compound selector string as per the defined logic. A filter operation + * is then called on the mixer using the resulting selector. + * + * This method can be used to programmatically trigger the parsing of + * filter groups after manipulations to a group's active selector(s) by + * the `.setFilterGroupSelectors()` API method. + * + * @example + * + * .parseFilterGroups([animate] [, callback]) + * + * @example Example: Triggering parsing after programmatically changing the values of a filter group + * + * mixer.setFilterGroupSelectors('color', ['.green', '.blue']); + * + * mixer.parseFilterGroups(); + * + * @public + * @since 3.0.0 + * @param {boolean} [animate=true] + * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. + * @param {function} [callback=null] + * An optional callback function to be invoked after the operation has completed. + * @return {Promise.} + * A promise resolving with the current state object. + */ + + parseFilterGroups: function() { + var self = this, + instruction = self.parseFilterArgs(arguments), + paths = self.getFilterGroupPaths(), + selector = self.buildSelectorFromPaths(paths), + callback = null, + command = {}; + + if (selector === '') { + selector = self.config.controls.toggleDefault; + } + + instruction.command.selector = selector; + + command.filter = instruction.command; + + if (typeof (callback = self.config.callbacks.onParseFilterGroups) === 'function') { + command = callback(command); + } + + return self.multimix(command, instruction.animate, instruction.callback); + }, + + /** + * Programmatically sets one or more active selectors for a specific filter + * group and updates the group's UI. + * + * Because MixItUp has no way of knowing how to break down a provided + * compound selector into its component groups, we can not use the + * standard `.filter()` or `toggleOn()/toggleOff()` API methods when using + * the MultiFilter extension. Instead, this method allows us to perform + * multi-dimensional filtering via the API by setting the active selectors of + * individual groups and then triggering the `.parseFilterGroups()` method. + * + * If setting multiple active selectors, do not pass a compound selector. + * Instead, pass an array with each item containing a single selector + * string as in example 2. + * + * @example + * + * .setFilterGroupSelectors(groupName, selectors) + * + * @example Example 1: Setting a single active selector for a "color" group + * + * mixer.setFilterGroupSelectors('color', '.green'); + * + * mixer.parseFilterGroups(); + * + * @example Example 2: Setting multiple active selectors for a "size" group + * + * mixer.setFilterGroupSelectors('size', ['.small', '.large']); + * + * mixer.parseFilterGroups(); + * + * @public + * @since 3.2.0 + * @param {string} groupName The name of the filter group as defined in the markup via the `data-filter-group` attribute. + * @param {(string|Array.)} selectors A single selector string, or multiple selector strings as an array. + * @return {void} + */ + + setFilterGroupSelectors: function(groupName, selectors) { + var self = this, + filterGroup = null; + + selectors = Array.isArray(selectors) ? selectors : [selectors]; + + if (typeof (filterGroup = self.filterGroupsHash[groupName]) === 'undefined') { + throw new Error('[MixItUp MultiFilter] No filter group could be found with the name "' + groupName + '"'); + } + + filterGroup.activeToggles = selectors.slice(); + + if (filterGroup.logic === 'and') { + // Compress into single node + + filterGroup.activeSelectors = [filterGroup.activeToggles]; + } else { + filterGroup.activeSelectors = filterGroup.activeToggles; + } + + filterGroup.updateUi(filterGroup.activeToggles); + }, + + /** + * Returns an array of active selectors for a specific filter group. + * + * @example + * + * .getFilterGroupSelectors(groupName) + * + * @example Example: Retrieving the active selectors for a "size" group + * + * mixer.getFilterGroupSelectors('size'); // ['.small', '.large'] + * + * @public + * @since 3.2.0 + * @param {string} groupName The name of the filter group as defined in the markup via the `data-filter-group` attribute. + * @return {void} + */ + + getFilterGroupSelectors: function(groupName) { + var self = this, + filterGroup = null; + + if (typeof (filterGroup = self.filterGroupsHash[groupName]) === 'undefined') { + throw new Error('[MixItUp MultiFilter] No filter group could be found with the name "' + groupName + '"'); + } + + return filterGroup.activeSelectors.slice(); + } +}); \ No newline at end of file diff --git a/src/module-definitions.js b/src/module-definitions.js new file mode 100644 index 0000000..dc7c692 --- /dev/null +++ b/src/module-definitions.js @@ -0,0 +1,13 @@ +/* global mixitupMultifilter */ + +if (typeof exports === 'object' && typeof module === 'object') { + module.exports = mixitupMultifilter; +} else if (typeof define === 'function' && define.amd) { + define(function() { + return mixitupMultifilter; + }); +} else if (window.mixitup && typeof window.mixitup === 'function') { + mixitupMultifilter(window.mixitup); +} else { + throw new Error('[MixItUp MultiFilter] MixItUp core not found'); +} \ No newline at end of file diff --git a/src/multifilter-form-event-tracker.js b/src/multifilter-form-event-tracker.js new file mode 100644 index 0000000..dae7d89 --- /dev/null +++ b/src/multifilter-form-event-tracker.js @@ -0,0 +1,9 @@ +/* global mixitup, h */ + +mixitup.MultifilterFormEventTracker = function() { + this.form = null; + this.totalBound = 0; + this.totalHandled = 0; + + h.seal(this); +}; \ No newline at end of file diff --git a/src/version-check.js b/src/version-check.js new file mode 100644 index 0000000..574ea8e --- /dev/null +++ b/src/version-check.js @@ -0,0 +1,13 @@ +/* global mixitupMultifilter, mixitup, h */ + +if ( + !mixitup.CORE_VERSION || + !h.compareVersions(mixitupMultifilter.REQUIRE_CORE_VERSION, mixitup.CORE_VERSION) +) { + throw new Error( + '[MixItUp Multifilter] MixItUp Multifilter v' + + mixitupMultifilter.EXTENSION_VERSION + + ' requires at least MixItUp v' + + mixitupMultifilter.REQUIRE_CORE_VERSION + ); +} \ No newline at end of file diff --git a/src/wrapper.hbs b/src/wrapper.hbs new file mode 100644 index 0000000..9c220ab --- /dev/null +++ b/src/wrapper.hbs @@ -0,0 +1,39 @@ +{{>banner}} + +(function(window) { + 'use strict'; + + var mixitupMultifilter = function(mixitup) { + var h = mixitup.h; + var diacriticsMap; + + {{>diacritics-map}} + + {{>version-check}} + + {{>config-callbacks}} + + {{>config-multifilter}} + + {{>config}} + + {{>multifilter-form-event-tracker}} + + {{>filter-group-dom}} + + {{>filter-group}} + + {{>mixer-dom}} + + {{>mixer}} + + {{>facade}} + }; + + mixitupMultifilter.TYPE = 'mixitup-extension'; + mixitupMultifilter.NAME = '{{name}}'; + mixitupMultifilter.EXTENSION_VERSION = '{{version}}'; + mixitupMultifilter.REQUIRE_CORE_VERSION = '{{coreVersion}}'; + + {{>module-definitions}} +})(window); \ No newline at end of file