Initial v3.3.6

This commit is contained in:
2020-04-10 08:15:15 -04:00
commit acd5401790
50 changed files with 7832 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
node_modules
components
.tmp
.DS_Store
.log
coverage/

95
.jscsrc Normal file
View File

@@ -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
}
}

23
.jshintrc Normal file
View File

@@ -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"
]
}

83
CHANGELOG.md Normal file
View File

@@ -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 `<input>` 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 `<select>` element with no selected value yields an array with an empty string when querying its value via `.getFilterGroupSelectors()`.
- Fixes issue where `onMixClick` callback was not invoked for MultiFilter filter or toggle controls.
- Adds a new "Programmatic Filtering by URL" demo with Pagination compatibility.
- Updates previous versions of "Programmatic Filtering by URL" demo to use `history.replaceState()` rather than `history.pushState()`.
## 3.3.0
- Adds a new callback method `onParseFilterGroups()`, enabling transformation of the resulting multimix command.
- Fixes issue introduced in 3.2.1 regarding "keyup" events from legitimate text inputs not being handled.
## 3.2.1
- Fixes an issue where the "active" class name was not added to toggles if "and" logic was used within a group.
- Fixes an issue where "keyup" events from non-textual inputs (i.e. multiselect) were unintentionally handled and fired filter operations.
## 3.2.0
- Adds `.setFilterGroupSelectors()` and `.getFilterGroupSelectors()` methods to allow multi dimensional filtering
and programmatic control of the UI via the API.
## 3.1.2
- Bumps core dependency to 3.1.2, improves version comparison functionality.
## 3.1.1
- Fixes an issue where empty string values in `<select>` 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

346
README.md Normal file
View File

@@ -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
...
<script src="/path/to/mixitup.min.js"></script>
<script src="/path/to/mixitup-multifilter.min.js"></script>
</body>
</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 `<fieldset>` makes sense semantically, particularly when using inputs like checkboxes or radios within it.
You can think of your entire multifilter UI as a `<form>` with which to interact with your mixer.
```html
<form>
<fieldset data-filter-group>
<button type="button" data-filter=".jacket">Jackets</button>
<button type="button" data-filter=".sweater">Sweaters</button>
<button type="button" data-filter=".shirt">Shirts</button>
</fieldset>
<fieldset data-filter-group>
<button type="button" data-toggle=".red">Red</button>
<button type="button" data-toggle=".blue">Blue</button>
<button type="button" data-toggle=".green">Green</button>
</fieldset>
</form>
```
*Wrapping filter groups in a parent `<form>` 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
<fieldset data-filter-group>
<label>Red</label>
<input type="radio" name="color" value=".red"/>
<label>Blue</label>
<input type="radio" name="color" value=".blue"/>
<label>Green</label>
<input type="radio" name="color" value=".green"/>
</fieldset>
```
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
<fieldset data-filter-group>
<label>Red</label>
<input type="checkbox" value=".red"/>
<label>Blue</label>
<input type="checkbox" value=".blue"/>
<label>Green</label>
<input type="checkbox" value=".green"/>
</fieldset>
```
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
<fieldset data-filter-group>
<select>
<option value="">Select a color</option>
<option value=".red">Red</option>
<option value=".blue">Blue</option>
<option value=".green">Green</option>
</select>
</fieldset>
```
Selects can be used to select one value from many values with a very small footprint. If the multiple attribute is added (`<select multiple>`), selects can also be used to select multiple values.
##### Text
```html
<fieldset data-filter-group>
<input type="text" data-search-attribute="class" placeholder="Search by color"/>
</fieldset>
```
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
<fieldset data-filter-group data-logic="and">
```
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 `<form>` with which to interact with our mixer. Adding a parent a `<form>` 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
<form>
<fieldset data-filter-group>
...
</fieldset>
<fieldset data-filter-group>
...
</fieldset>
<button type="reset">Clear filters</button>
</form>
```
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
<div>
<form data-filter-group>
...
<button type="reset">Clear filters</button>
</form>
<form data-filter-group>
...
<button type="reset">Clear filters</button>
</form>
</div>
```
#### 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
<form>
<fieldset data-filter-group>
...
</fieldset>
<fieldset data-filter-group>
...
</fieldset>
<button type="reset">Clear filters</button>
<button type="submit">Search</button>
</form>
```
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
<button type="submit" disabled>Search</button>
```
#### 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
<form>
<fieldset data-filter-group="color">
...
</fieldset>
<fieldset data-filter-group="size">
...
</fieldset>
</form>
```
We can then target that group by its name when we call the API method:
```js
mixer.setFilterGroupSelectors('color', ['.green', '.blue']);
mixer.parseFilterGroups()
```

116
demos/checkboxes/index.html Normal file
View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Checkboxes</title>
</head>
<body>
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<fieldset data-filter-group class="checkbox-group">
<label class="checkbox-group-label">Color</label>
<div class="checkbox">
<label class="checkbox-label">Green</label>
<input type="checkbox" value=".green"/>
</div>
<div class="checkbox">
<label class="checkbox-label">Blue</label>
<input type="checkbox" value=".blue"/>
</div>
<div class="checkbox">
<label class="checkbox-label">Pink</label>
<input type="checkbox" value=".pink"/>
</div>
</fieldset>
<fieldset data-filter-group class="checkbox-group">
<label class="checkbox-group-label">Shape</label>
<div class="checkbox">
<label class="checkbox-label">Square</label>
<input type="checkbox" value=".square"/>
</div>
<div class="checkbox">
<label class="checkbox-label">Circle</label>
<input type="checkbox" value=".circle"/>
</div>
<div class="checkbox">
<label class="checkbox-label">Triangle</label>
<input type="checkbox" value=".triangle"/>
</div>
</fieldset>
<fieldset data-filter-group class="checkbox-group">
<label class="checkbox-group-label">Size</label>
<div class="checkbox">
<label class="checkbox-label">Small</label>
<input type="checkbox" value=".small"/>
</div>
<div class="checkbox">
<label class="checkbox-label">Medium</label>
<input type="checkbox" value=".medium"/>
</div>
<div class="checkbox">
<label class="checkbox-label">Large</label>
<input type="checkbox" value=".large"/>
</div>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square"></div>
<div class="mix green medium square"></div>
<div class="mix blue large triangle"></div>
<div class="mix pink large circle"></div>
<div class="mix green small circle"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink medium square"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink small circle"></div>
<div class="mix green large triangle"></div>
<div class="mix blue small square"></div>
<div class="mix pink small square"></div>
<div class="mix green large square"></div>
<div class="mix blue large circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
}
});
</script>
</body>
</html>

266
demos/checkboxes/style.css Normal file
View File

@@ -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;
}
}

View File

@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Filter and Toggle Controls</title>
</head>
<body>
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Color</label>
<button type="button" class="control control-text" data-filter=".mix">All</button>
<button type="button" class="control control-color" data-toggle=".green">Green</button>
<button type="button" class="control control-color" data-toggle=".blue">Blue</button>
<button type="button" class="control control-color" data-toggle=".pink">Pink</button>
</fieldset>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Shape</label>
<button type="button" class="control control-text" data-filter=".mix">All</button>
<button type="button" class="control control-shape" data-toggle=".square">Square</button>
<button type="button" class="control control-shape" data-toggle=".circle">Circle</button>
<button type="button" class="control control-shape" data-toggle=".triangle">Triangle</button>
</fieldset>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Size</label>
<button type="button" class="control control-text" data-filter=".mix">All</button>
<button type="button" class="control control-size" data-toggle=".small">Small</button>
<button type="button" class="control control-size" data-toggle=".medium">Medium</button>
<button type="button" class="control control-size" data-toggle=".large">Large</button>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square"></div>
<div class="mix green medium square"></div>
<div class="mix blue large triangle"></div>
<div class="mix pink large circle"></div>
<div class="mix green small circle"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink medium square"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink small circle"></div>
<div class="mix green large triangle"></div>
<div class="mix blue small square"></div>
<div class="mix pink small square"></div>
<div class="mix green large square"></div>
<div class="mix blue large circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
}
});
</script>
</body>
</html>

View File

@@ -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;
}
}

83
demos/filters/index.html Normal file
View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Filter Controls</title>
</head>
<body>
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Color</label>
<button type="button" class="control control-color" data-filter=".green">Green</button>
<button type="button" class="control control-color" data-filter=".blue">Blue</button>
<button type="button" class="control control-color" data-filter=".pink">Pink</button>
</fieldset>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Shape</label>
<button type="button" class="control control-shape" data-filter=".square">Square</button>
<button type="button" class="control control-shape" data-filter=".circle">Circle</button>
<button type="button" class="control control-shape" data-filter=".triangle">Triangle</button>
</fieldset>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Size</label>
<button type="button" class="control control-size" data-filter=".small">Small</button>
<button type="button" class="control control-size" data-filter=".medium">Medium</button>
<button type="button" class="control control-size" data-filter=".large">Large</button>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square"></div>
<div class="mix green medium square"></div>
<div class="mix blue large triangle"></div>
<div class="mix pink large circle"></div>
<div class="mix green small circle"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink medium square"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink small circle"></div>
<div class="mix green large triangle"></div>
<div class="mix blue small square"></div>
<div class="mix pink small square"></div>
<div class="mix green large square"></div>
<div class="mix blue large circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
}
});
</script>
</body>
</html>

300
demos/filters/style.css Normal file
View File

@@ -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;
}
}

23
demos/index.html Normal file
View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>MixItUp Multifilter Demos</title>
</head>
<body>
<h1>MixItUp Multifilter Demos</h1>
<ul>
<li><a href="./filters/">Filter Controls</a></li>
<li><a href="./toggles/">Toggle Controls</a></li>
<li><a href="./filters-and-toggles/">Filter and Toggle Controls</a></li>
<li><a href="./checkboxes/">Checkboxes</a></li>
<li><a href="./radios/">Radios</a></li>
<li><a href="./selects/">Selects</a></li>
<li><a href="./text-inputs/">Text Inputs</a></li>
<li><a href="./programmatic-filtering-on-click/">Programmatic Filtering on Click</a></li>
<li><a href="./programmatic-filtering-by-url/">Programmatic Filtering by URL</a></li>
<li><a href="./programmatic-filtering-by-url-with-pagination/">Programmatic Filtering by URL with Pagination (requires Pagination extension)</a></li>
</ul>
</body>
</html>

18
demos/mixitup-multifilter.min.js vendored Normal file

File diff suppressed because one or more lines are too long

18
demos/mixitup.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,307 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Programmatic Filtering by URL with Pagination</title>
</head>
<body>
<!--
This demo will show how we can programmatically set both our multifilter UI
and the mixer simultanesouly on page load.
Click on some filters to create your desired filtering then refresh the page.
This demo also includes functionality to include current page number in the URL,
for those users who wish to use the Pagination and MultiFilter extensions together.
-->
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<!-- NB: Each filter group has been named by providing a value to the `data-filter-group` attribute -->
<fieldset data-filter-group="color" class="control-group">
<label class="control-group-label">Color</label>
<button type="button" class="control control-color" data-toggle=".green">Green</button>
<button type="button" class="control control-color" data-toggle=".blue">Blue</button>
<button type="button" class="control control-color" data-toggle=".pink">Pink</button>
</fieldset>
<fieldset data-filter-group="shape" class="control-group">
<label class="control-group-label">Shape</label>
<button type="button" class="control control-shape" data-toggle=".square">Square</button>
<button type="button" class="control control-shape" data-toggle=".circle">Circle</button>
<button type="button" class="control control-shape" data-toggle=".triangle">Triangle</button>
</fieldset>
<fieldset data-filter-group="size" class="control-group">
<label class="control-group-label">Size</label>
<button type="button" class="control control-size" data-toggle=".small">Small</button>
<button type="button" class="control control-size" data-toggle=".medium">Medium</button>
<button type="button" class="control control-size" data-toggle=".large">Large</button>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square"></div>
<div class="mix green medium square"></div>
<div class="mix blue large triangle"></div>
<div class="mix pink large circle"></div>
<div class="mix green small circle"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink medium square"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink small circle"></div>
<div class="mix green large triangle"></div>
<div class="mix blue small square"></div>
<div class="mix pink small square"></div>
<div class="mix green large square"></div>
<div class="mix blue large circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<div class="controls-pagination">
<div class="mixitup-page-list"></div>
<div class="mixitup-page-stats"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<!--
This demo requires the Pagination premium extension. Update the following
script tag's `src` to a valid reference on your filesystem:
-->
<script src="../path/to/your/mixitup-pagination.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var targetSelector = '.mix';
var activeHash = '';
var activePage = -1;
/**
* Deserializes a hash segment (if present) into in an object.
*
* @return {object|null}
*/
function deserializeHash() {
var hash = window.location.hash.replace(/^#/g, '');
var obj = null;
var props = [];
if (!hash) return obj;
obj = {};
props = hash.split('&');
props.forEach(function(props) {
var pair = props.split('=');
var propName = pair[0];
obj[propName] = propName === 'page' ? parseInt(pair[1]) : pair[1].split(',');
});
return obj;
}
/**
* Serializes a uiState object into a string.
*
* @param {object} uiState
* @return {string}
*/
function serializeUiState(uiState) {
var output = '';
for (var key in uiState) {
var values = uiState[key];
if (Array.isArray(values) && !values.length) continue;
output += key + '=';
output += Array.isArray(values) ? values.join(',') : values;
output += '&';
};
output = output.replace(/&$/g, '');
return output;
}
/**
* Constructs a `uiState` object using the
* `getFilterGroupSelectors()` API method.
*
* @return {object}
*/
function getUiState() {
var page = mixer.getState().activePagination.page;
// NB: You will need to rename the following keys to match the names of
// your project's filter groups  these should match those defined
// in your HTML.
var uiState = {
color: mixer.getFilterGroupSelectors('color').map(getValueFromSelector),
shape: mixer.getFilterGroupSelectors('shape').map(getValueFromSelector),
size: mixer.getFilterGroupSelectors('size').map(getValueFromSelector)
};
if (page > 1) {
uiState.page = page;
}
return uiState;
}
/**
* Updates the URL hash whenever the current UI state changes.
*
* @param {mixitup.State} state
* @return {void}
*/
function setHash(state) {
var selector = state.activeFilter.selector;
// Construct an object representing the current state of each
// filter group
var uiState = getUiState();
var page = uiState.page || 1;
// Create a URL hash string by serializing the uiState object
var newHash = '#' + serializeUiState(uiState);
if (selector === targetSelector && window.location.href.indexOf('#') > -1 && page === 1) {
// Equivalent to filter "all", and a hash exists, remove the hash
activeHash = '';
history.replaceState(null, document.title, window.location.pathname);
} else if (newHash !== window.location.hash && selector !== targetSelector || page > 1) {
// Change the hash
activeHash = newHash;
history.replaceState(null, document.title, window.location.pathname + newHash);
}
}
/**
* Updates the mixer to a previous UI state.
*
* @param {object|null} uiState
* @param {boolean} [animate]
* @return {Promise}
*/
function syncMixerWithPreviousUiState(uiState, animate) {
var color = (uiState && uiState.color) ? uiState.color : [];
var size = (uiState && uiState.size) ? uiState.size : [];
var shape = (uiState && uiState.shape) ? uiState.shape : [];
activePage = (uiState && uiState.page) ? uiState.page : 1;
mixer.setFilterGroupSelectors('color', color.map(getSelectorFromValue));
mixer.setFilterGroupSelectors('size', size.map(getSelectorFromValue));
mixer.setFilterGroupSelectors('shape', shape.map(getSelectorFromValue));
// Parse the filter groups (passing `false` will perform no animation)
return mixer.parseFilterGroups(animate ? true : false);
}
/**
* Converts a selector (e.g. '.green') into a simple value (e.g. 'green').
*
* @param {string} selector
* @return {string}
*/
function getValueFromSelector(selector) {
return selector.replace(/^./, '');
}
/**
* Converts a simple value (e.g. 'green') into a selector (e.g. '.green').
*
* @param {string} selector
* @return {string}
*/
function getSelectorFromValue(value) {
return '.' + value;
}
/**
* A function for filtering the values of the mixitup command object
* generated by calling the `parseFilterGroups()` method.
*
* @param {object} command
* @return {object}
*/
function handleParseFilterGroups(command) {
if (activePage > 1) {
// If an activePage greater than 1 has been parsed
// from the URL, update the command with a pagination
// instruction
command.paginate = activePage;
}
return command;
}
var uiState = deserializeHash();
// Instantiate MixItUp
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
pagination: {
limit: 4,
maintainActivePage: false
},
animation: {
effects: 'fade translateZ(-100px)'
},
callbacks: {
onParseFilterGroups: handleParseFilterGroups,
onMixEnd: setHash // Call the setHash() method at the end of each operation
}
});
if (uiState) {
// If a valid uiState object is present on page load, filter the mixer
syncMixerWithPreviousUiState(uiState);
}
</script>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -0,0 +1,257 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Programmatic Filtering by URL</title>
</head>
<body>
<!--
This demo will show how we can programmatically set both our multifilter UI
and the mixer simultanesouly on page load.
Click on some filters to create your desired filtering then refresh the page.
-->
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<!-- NB: Each filter group has been named by providing a value to the `data-filter-group` attribute -->
<fieldset data-filter-group="color" class="control-group">
<label class="control-group-label">Color</label>
<button type="button" class="control control-color" data-toggle=".green">Green</button>
<button type="button" class="control control-color" data-toggle=".blue">Blue</button>
<button type="button" class="control control-color" data-toggle=".pink">Pink</button>
</fieldset>
<fieldset data-filter-group="shape" class="control-group">
<label class="control-group-label">Shape</label>
<button type="button" class="control control-shape" data-toggle=".square">Square</button>
<button type="button" class="control control-shape" data-toggle=".circle">Circle</button>
<button type="button" class="control control-shape" data-toggle=".triangle">Triangle</button>
</fieldset>
<fieldset data-filter-group="size" class="control-group">
<label class="control-group-label">Size</label>
<button type="button" class="control control-size" data-toggle=".small">Small</button>
<button type="button" class="control control-size" data-toggle=".medium">Medium</button>
<button type="button" class="control control-size" data-toggle=".large">Large</button>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square"></div>
<div class="mix green medium square"></div>
<div class="mix blue large triangle"></div>
<div class="mix pink large circle"></div>
<div class="mix green small circle"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink medium square"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink small circle"></div>
<div class="mix green large triangle"></div>
<div class="mix blue small square"></div>
<div class="mix pink small square"></div>
<div class="mix green large square"></div>
<div class="mix blue large circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var targetSelector = '.mix';
var activeHash = '';
/**
* Deserializes a hash segment (if present) into in an object.
*
* @return {object|null}
*/
function deserializeHash() {
var hash = window.location.hash.replace(/^#/g, '');
var obj = null;
var groups = [];
if (!hash) return obj;
obj = {};
groups = hash.split('&');
groups.forEach(function(group) {
var pair = group.split('=');
var groupName = pair[0];
obj[groupName] = pair[1].split(',');
});
return obj;
}
/**
* Serializes a uiState object into a string.
*
* @param {object} uiState
* @return {string}
*/
function serializeUiState(uiState) {
var output = '';
for (var key in uiState) {
var values = uiState[key];
if (!values.length) continue;
output += key + '=';
output += values.join(',');
output += '&';
};
output = output.replace(/&$/g, '');
return output;
}
/**
* Constructs a `uiState` object using the
* `getFilterGroupSelectors()` API method.
*
* @return {object}
*/
function getUiState() {
// NB: You will need to rename the object keys to match the names of
// your project's filter groups  these should match those defined
// in your HTML.
var uiState = {
color: mixer.getFilterGroupSelectors('color').map(getValueFromSelector),
shape: mixer.getFilterGroupSelectors('shape').map(getValueFromSelector),
size: mixer.getFilterGroupSelectors('size').map(getValueFromSelector)
};
return uiState;
}
/**
* Updates the URL hash whenever the current filter changes.
*
* @param {mixitup.State} state
* @return {void}
*/
function setHash(state) {
var selector = state.activeFilter.selector;
// Construct an object representing the current state of each
// filter group
var uiState = getUiState();
// Create a URL hash string by serializing the uiState object
var newHash = '#' + serializeUiState(uiState);
if (selector === targetSelector && window.location.href.indexOf('#') > -1) {
// Equivalent to filter "all", and a hash exists, remove the hash
activeHash = '';
history.replaceState(null, document.title, window.location.pathname);
} else if (newHash !== window.location.hash && selector !== targetSelector) {
// Change the hash
activeHash = newHash;
history.replaceState(null, document.title, window.location.pathname + newHash);
}
}
/**
* Updates the mixer to a previous UI state.
*
* @param {object|null} uiState
* @param {boolean} [animate]
* @return {Promise}
*/
function syncMixerWithPreviousUiState(uiState, animate) {
var color = (uiState && uiState.color) ? uiState.color : [];
var size = (uiState && uiState.size) ? uiState.size : [];
var shape = (uiState && uiState.shape) ? uiState.shape : [];
mixer.setFilterGroupSelectors('color', color.map(getSelectorFromValue));
mixer.setFilterGroupSelectors('size', size.map(getSelectorFromValue));
mixer.setFilterGroupSelectors('shape', shape.map(getSelectorFromValue));
// Parse the filter groups (passing `false` will perform no animation)
return mixer.parseFilterGroups(animate ? true : false);
}
/**
* Converts a selector (e.g. '.green') into a simple value (e.g. 'green').
*
* @param {string} selector
* @return {string}
*/
function getValueFromSelector(selector) {
return selector.replace(/^./, '');
}
/**
* Converts a simple value (e.g. 'green') into a selector (e.g. '.green').
*
* @param {string} selector
* @return {string}
*/
function getSelectorFromValue(value) {
return '.' + value;
}
var uiState = deserializeHash();
// Instantiate MixItUp
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
},
callbacks: {
onMixEnd: setHash // Call the setHash() method at the end of each operation
}
});
if (uiState) {
// If a valid uiState object is present on page load, filter the mixer
syncMixerWithPreviousUiState(uiState);
}
</script>
</body>
</html>

View File

@@ -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;
}
}

View File

@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Programmatic Filtering on Click</title>
</head>
<body>
<!--
This demo will show how we can programmatically update both our multifilter UI
and the mixer simultanesouly, using the `setFilterGroupSelectors()` and
`parseSelectorGroups()` API methods.
Click on a target in the container, and the container will be filtered according
to that target's attributes.
-->
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<!-- NB: Each filter group has been named by providing a value to the `data-filter-group` attribute -->
<fieldset data-filter-group="color" class="select-wrapper">
<select>
<option value="">Color</option>
<option value=".green">Green</option>
<option value=".blue">Blue</option>
<option value=".pink">Pink</option>
</select>
</fieldset>
<fieldset data-filter-group="shape" class="select-wrapper">
<select>
<option value="">Shape</option>
<option value=".square">Square</option>
<option value=".circle">Circle</option>
<option value=".triangle">Triangle</option>
</select>
</fieldset>
<fieldset data-filter-group="size" class="select-wrapper">
<select>
<option value="">Size</option>
<option value=".small">Small</option>
<option value=".medium">Medium</option>
<option value=".large">Large</option>
</select>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square" data-color="green" data-size="small" data-shape="square"></div>
<div class="mix green medium square" data-color="green" data-size="medium" data-shape="square"></div>
<div class="mix blue large triangle" data-color="blue" data-size="large" data-shape="triangle"></div>
<div class="mix pink large circle" data-color="pink" data-size="large" data-shape="circle"></div>
<div class="mix green small circle" data-color="green" data-size="small" data-shape="circle"></div>
<div class="mix blue medium triangle" data-color="blue" data-size="medium" data-shape="triangle"></div>
<div class="mix pink medium square" data-color="pink" data-size="medium" data-shape="square"></div>
<div class="mix blue medium triangle" data-color="blue" data-size="medium" data-shape="triangle"></div>
<div class="mix pink small circle" data-color="pink" data-size="small" data-shape="circle"></div>
<div class="mix green large triangle" data-color="green" data-size="large" data-shape="triangle"></div>
<div class="mix blue small square" data-color="blue" data-size="small" data-shape="square"></div>
<div class="mix pink small square" data-color="pink" data-size="small" data-shape="square"></div>
<div class="mix green large square" data-color="green" data-size="large" data-shape="square"></div>
<div class="mix blue large circle" data-color="blue" data-size="large" data-shape="circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
}
});
// Add a delegated click event handler to the container
containerEl.addEventListener('click', handleContainerClick);
function handleContainerClick(e) {
var color, size, shape;
var target = e.target;
// If the clicked element is not a target, do not handle
if (!target.matches('.mix')) return;
// Build up a selector for each group using the data attributes present on the target
color = '.' + target.getAttribute('data-color');
size = '.' + target.getAttribute('data-size');
shape = '.' + target.getAttribute('data-shape');
// Set the active filter
mixer.setFilterGroupSelectors('color', color);
mixer.setFilterGroupSelectors('size', size);
mixer.setFilterGroupSelectors('shape', shape);
// Now that each group has been udpated, parse the filter groups and filter the container
mixer.parseFilterGroups();
}
</script>
</body>
</html>

View File

@@ -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;
}
}

116
demos/radios/index.html Normal file
View File

@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Radios</title>
</head>
<body>
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<fieldset data-filter-group class="radio-group">
<label class="radio-group-label">Color</label>
<div class="radio">
<label class="radio-label">Green</label>
<input type="radio" name="color" value=".green"/>
</div>
<div class="radio">
<label class="radio-label">Blue</label>
<input type="radio" name="color" value=".blue"/>
</div>
<div class="radio">
<label class="radio-label">Pink</label>
<input type="radio" name="color" value=".pink"/>
</div>
</fieldset>
<fieldset data-filter-group class="radio-group">
<label class="radio-group-label">Shape</label>
<div class="radio">
<label class="radio-label">Square</label>
<input type="radio" name="shape" value=".square"/>
</div>
<div class="radio">
<label class="radio-label">Circle</label>
<input type="radio" name="shape" value=".circle"/>
</div>
<div class="radio">
<label class="radio-label">Triangle</label>
<input type="radio" name="shape" value=".triangle"/>
</div>
</fieldset>
<fieldset data-filter-group class="radio-group">
<label class="radio-group-label">Size</label>
<div class="radio">
<label class="radio-label">Small</label>
<input type="radio" name="size" value=".small"/>
</div>
<div class="radio">
<label class="radio-label">Medium</label>
<input type="radio" name="size" value=".medium"/>
</div>
<div class="radio">
<label class="radio-label">Large</label>
<input type="radio" name="size" value=".large"/>
</div>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square"></div>
<div class="mix green medium square"></div>
<div class="mix blue large triangle"></div>
<div class="mix pink large circle"></div>
<div class="mix green small circle"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink medium square"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink small circle"></div>
<div class="mix green large triangle"></div>
<div class="mix blue small square"></div>
<div class="mix pink small square"></div>
<div class="mix green large square"></div>
<div class="mix blue large circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
}
});
</script>
</body>
</html>

266
demos/radios/style.css Normal file
View File

@@ -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;
}
}

72
demos/reset.css Normal file
View File

@@ -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;
}

86
demos/selects/index.html Normal file
View File

@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Selects</title>
</head>
<body>
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<fieldset data-filter-group class="select-wrapper">
<select>
<option value="">Color</option>
<option value=".green">Green</option>
<option value=".blue">Blue</option>
<option value=".pink">Pink</option>
</select>
</fieldset>
<fieldset data-filter-group class="select-wrapper">
<select>
<option value="">Shape</option>
<option value=".square">Square</option>
<option value=".circle">Circle</option>
<option value=".triangle">Triangle</option>
</select>
</fieldset>
<fieldset data-filter-group class="select-wrapper">
<select>
<option value="">Size</option>
<option value=".small">Small</option>
<option value=".medium">Medium</option>
<option value=".large">Large</option>
</select>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square"></div>
<div class="mix green medium square"></div>
<div class="mix blue large triangle"></div>
<div class="mix pink large circle"></div>
<div class="mix green small circle"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink medium square"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink small circle"></div>
<div class="mix green large triangle"></div>
<div class="mix blue small square"></div>
<div class="mix pink small square"></div>
<div class="mix green large square"></div>
<div class="mix blue large circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
}
});
</script>
</body>
</html>

234
demos/selects/style.css Normal file
View File

@@ -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;
}
}

View File

@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Text Inputs</title>
</head>
<body>
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<fieldset data-filter-group class="text-input-wrapper">
<input type="text" data-search-attribute="data-color" placeholder="Search by color"/>
</fieldset>
<fieldset data-filter-group class="text-input-wrapper">
<input type="text" data-search-attribute="data-shape" placeholder="Search by shape"/>
</fieldset>
<fieldset data-filter-group class="text-input-wrapper">
<input type="text" data-search-attribute="data-size" placeholder="Search by size"/>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square" data-color="green" data-shape="square" data-size="small"></div>
<div class="mix green medium square" data-color="green" data-shape="square" data-size="medium"></div>
<div class="mix blue large triangle" data-color="blue" data-shape="triangle" data-size="large"></div>
<div class="mix pink large circle" data-color="pink" data-shape="circle" data-size="large"></div>
<div class="mix green small circle" data-color="green" data-shape="circle" data-size="small"></div>
<div class="mix blue medium triangle" data-color="blue" data-shape="triangle" data-size="medium"></div>
<div class="mix pink medium square" data-color="pink" data-shape="square" data-size="medium"></div>
<div class="mix blue medium triangle" data-color="blue" data-shape="triangle" data-size="medium"></div>
<div class="mix pink small circle" data-color="pink" data-shape="circle" data-size="small"></div>
<div class="mix green large triangle" data-color="green" data-shape="triangle" data-size="large"></div>
<div class="mix blue small square" data-color="blue" data-shape="square" data-size="small"></div>
<div class="mix pink small square" data-color="pink" data-shape="square" data-size="small"></div>
<div class="mix green large square" data-color="green" data-shape="square" data-size="large"></div>
<div class="mix blue large circle" data-color="blue" data-shape="circle" data-size="large"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
}
});
</script>
</body>
</html>

242
demos/text-inputs/style.css Normal file
View File

@@ -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;
}
}

83
demos/toggles/index.html Normal file
View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="../reset.css" rel="stylesheet"/>
<link href="./style.css" rel="stylesheet"/>
<title>MixItUp MultiFilter Demo - Toggle Controls</title>
</head>
<body>
<form class="controls">
<button type="reset" class="control control-text">Reset</button>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Color</label>
<button type="button" class="control control-color" data-toggle=".green">Green</button>
<button type="button" class="control control-color" data-toggle=".blue">Blue</button>
<button type="button" class="control control-color" data-toggle=".pink">Pink</button>
</fieldset>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Shape</label>
<button type="button" class="control control-shape" data-toggle=".square">Square</button>
<button type="button" class="control control-shape" data-toggle=".circle">Circle</button>
<button type="button" class="control control-shape" data-toggle=".triangle">Triangle</button>
</fieldset>
<fieldset data-filter-group class="control-group">
<label class="control-group-label">Size</label>
<button type="button" class="control control-size" data-toggle=".small">Small</button>
<button type="button" class="control control-size" data-toggle=".medium">Medium</button>
<button type="button" class="control control-size" data-toggle=".large">Large</button>
</fieldset>
<div class="control-group">
<button type="button" class="control control-sort" data-sort="default:asc">Asc</button>
<button type="button" class="control control-sort" data-sort="default:desc">Desc</button>
</div>
</form>
<div class="container">
<div class="mix green small square"></div>
<div class="mix green medium square"></div>
<div class="mix blue large triangle"></div>
<div class="mix pink large circle"></div>
<div class="mix green small circle"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink medium square"></div>
<div class="mix blue medium triangle"></div>
<div class="mix pink small circle"></div>
<div class="mix green large triangle"></div>
<div class="mix blue small square"></div>
<div class="mix pink small square"></div>
<div class="mix green large square"></div>
<div class="mix blue large circle"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
<div class="gap"></div>
</div>
<script src="../mixitup.min.js"></script>
<script src="../mixitup-multifilter.min.js"></script>
<script>
var containerEl = document.querySelector('.container');
var mixer = mixitup(containerEl, {
multifilter: {
enable: true
},
animation: {
effects: 'fade translateZ(-100px)'
}
});
</script>
</body>
</html>

300
demos/toggles/style.css Normal file
View File

@@ -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;
}
}

1256
dist/mixitup-multifilter.js vendored Normal file

File diff suppressed because it is too large Load Diff

18
dist/mixitup-multifilter.min.js vendored Normal file

File diff suppressed because one or more lines are too long

175
docs/mixitup.Config.md Normal file
View File

@@ -0,0 +1,175 @@
# mixitup.Config
## Overview
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.
### Contents
- [callbacks](#callbacks)
- [multifilter](#multifilter)
<h2 id="callbacks">callbacks</h2>
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;
}
}
});
```
<h2 id="multifilter">multifilter</h2>
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 `<form>` 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`

112
docs/mixitup.Mixer.md Normal file
View File

@@ -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)
<h3 id="parseFilterGroups">parseFilterGroups()</h3>
*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.<mixitup.State>` | 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();
```
<h3 id="setFilterGroupSelectors">setFilterGroupSelectors()</h3>
*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.<string>` | `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();
```
<h3 id="getFilterGroupSelectors">getFilterGroupSelectors()</h3>
*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']
```

11
docs/mixitup.md Normal file
View File

@@ -0,0 +1,11 @@
#()
| |Type | Name | Description
|---|--- | --- | ---
|Returns |

95
gulpfile.js Normal file
View File

@@ -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());
});

33
package.json Normal file
View File

@@ -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"
}
}

17
src/banner.js Normal file
View File

@@ -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}}
*/

53
src/config-callbacks.js Normal file
View File

@@ -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 <caption>Example: Overriding the default filtering behavior with `onParseFilterGroups`</caption>
* 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;
});

125
src/config-multifilter.js Normal file
View File

@@ -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 `<form>` 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);
};

20
src/config.js Normal file
View File

@@ -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();
});

88
src/diacritics-map.js Normal file
View File

@@ -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]
];

7
src/facade.js Normal file
View File

@@ -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);
});

8
src/filter-group-dom.js Normal file
View File

@@ -0,0 +1,8 @@
/* global mixitup, h */
mixitup.FilterGroupDom = function() {
this.el = null;
this.form = null;
h.seal(this);
};

483
src/filter-group.js Normal file
View File

@@ -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.<HTMLELement>} [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;
}
}
}
});

5
src/mixer-dom.js Normal file
View File

@@ -0,0 +1,5 @@
/* global mixitup */
mixitup.MixerDom.registerAction('afterConstruct', 'multifilter', function() {
this.filterGroups = [];
});

416
src/mixer.js Normal file
View File

@@ -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.<Array.<string>>}
*/
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.<Array.<string>>} 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 <caption>Example: Triggering parsing after programmatically changing the values of a filter group</caption>
*
* 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.<mixitup.State>}
* 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 <caption>Example 1: Setting a single active selector for a "color" group</caption>
*
* mixer.setFilterGroupSelectors('color', '.green');
*
* mixer.parseFilterGroups();
*
* @example <caption>Example 2: Setting multiple active selectors for a "size" group</caption>
*
* 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.<string>)} 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 <caption>Example: Retrieving the active selectors for a "size" group</caption>
*
* 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();
}
});

13
src/module-definitions.js Normal file
View File

@@ -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');
}

View File

@@ -0,0 +1,9 @@
/* global mixitup, h */
mixitup.MultifilterFormEventTracker = function() {
this.form = null;
this.totalBound = 0;
this.totalHandled = 0;
h.seal(this);
};

13
src/version-check.js Normal file
View File

@@ -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
);
}

39
src/wrapper.hbs Normal file
View File

@@ -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);