From fa05241a632649ca89608786ba93c6047c2897b4 Mon Sep 17 00:00:00 2001 From: Pascal Martineau Date: Wed, 17 Jan 2018 14:09:34 -0500 Subject: [PATCH] Initial version --- .gitignore | 6 + .jscsrc | 97 ++ .jshintrc | 19 + CHANGELOG.md | 20 + README.md | 169 ++ demos/basic/index.html | 72 + demos/basic/style.css | 270 ++++ demos/index.html | 17 + demos/infinite-scroll/index.html | 207 +++ demos/infinite-scroll/style.css | 170 ++ demos/load-more-button/index.html | 113 ++ demos/load-more-button/style.css | 198 +++ demos/mixitup.min.js | 18 + demos/multiple-pagination-ui/index.html | 77 + demos/multiple-pagination-ui/style.css | 270 ++++ demos/reset.css | 72 + demos/responsive-pagination/index.html | 168 ++ demos/responsive-pagination/style.css | 295 ++++ dist/mixitup-pagination.js | 1974 +++++++++++++++++++++++ dist/mixitup-pagination.min.js | 18 + docs/mixitup.Config.md | 629 ++++++++ docs/mixitup.Mixer.md | 142 ++ docs/mixitup.State.md | 42 + docs/mixitup.md | 11 + gulpfile.js | 94 ++ package.json | 33 + src/banner.js | 17 + src/command-multimix.js | 5 + src/command-paginate.js | 17 + src/config-callbacks.js | 48 + src/config-class-names.js | 155 ++ src/config-load.js | 39 + src/config-pagination.js | 217 +++ src/config-render.js | 53 + src/config-selectors.js | 44 + src/config-templates.js | 84 + src/config.js | 20 + src/control-definition.js | 3 + src/control.js | 61 + src/events.js | 27 + src/facade.js | 7 + src/messages.js | 8 + src/mixer-dom.js | 6 + src/mixer.js | 1071 ++++++++++++ src/model-page-stats.js | 9 + src/model-pager.js | 14 + src/module-definitions.js | 13 + src/operation.js | 8 + src/state.js | 43 + src/ui-class-names.js | 12 + src/version-check.js | 12 + src/wrapper.hbs | 60 + 52 files changed, 7254 insertions(+) create mode 100644 .gitignore create mode 100644 .jscsrc create mode 100644 .jshintrc create mode 100644 CHANGELOG.md create mode 100644 README.md create mode 100644 demos/basic/index.html create mode 100644 demos/basic/style.css create mode 100644 demos/index.html create mode 100644 demos/infinite-scroll/index.html create mode 100644 demos/infinite-scroll/style.css create mode 100644 demos/load-more-button/index.html create mode 100644 demos/load-more-button/style.css create mode 100644 demos/mixitup.min.js create mode 100644 demos/multiple-pagination-ui/index.html create mode 100644 demos/multiple-pagination-ui/style.css create mode 100644 demos/reset.css create mode 100644 demos/responsive-pagination/index.html create mode 100644 demos/responsive-pagination/style.css create mode 100644 dist/mixitup-pagination.js create mode 100644 dist/mixitup-pagination.min.js create mode 100644 docs/mixitup.Config.md create mode 100644 docs/mixitup.Mixer.md create mode 100644 docs/mixitup.State.md create mode 100644 docs/mixitup.md create mode 100644 gulpfile.js create mode 100644 package.json create mode 100644 src/banner.js create mode 100644 src/command-multimix.js create mode 100644 src/command-paginate.js create mode 100644 src/config-callbacks.js create mode 100644 src/config-class-names.js create mode 100644 src/config-load.js create mode 100644 src/config-pagination.js create mode 100644 src/config-render.js create mode 100644 src/config-selectors.js create mode 100644 src/config-templates.js create mode 100644 src/config.js create mode 100644 src/control-definition.js create mode 100644 src/control.js create mode 100644 src/events.js create mode 100644 src/facade.js create mode 100644 src/messages.js create mode 100644 src/mixer-dom.js create mode 100644 src/mixer.js create mode 100644 src/model-page-stats.js create mode 100644 src/model-pager.js create mode 100644 src/module-definitions.js create mode 100644 src/operation.js create mode 100644 src/state.js create mode 100644 src/ui-class-names.js create mode 100644 src/version-check.js create mode 100644 src/wrapper.hbs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f2fdb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +components +.tmp +.DS_Store +.log +coverage/ \ No newline at end of file diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..8e6356c --- /dev/null +++ b/.jscsrc @@ -0,0 +1,97 @@ +{ + "requireCurlyBraces": [ + "else", + "for", + "while", + "do", + "try", + "catch" + ], + "requireSpaceAfterKeywords": [ + "if", + "else", + "for", + "while", + "do", + "case", + "return", + "try", + "typeof" + ], + "safeContextKeyword": ["self"], + "maximumLineLength": { + "value": 140, + "allowComments": false, + "allowRegex": true + }, + "requireSpaceBeforeBlockStatements": true, + "requireParenthesesAroundIIFE": true, + "requireSpaceAfterLineComment": { + "allExcept": ["#", "="] + }, + "requireSpacesInConditionalExpression": true, + "disallowSpacesInNamedFunctionExpression": { + "beforeOpeningRoundBrace": true + }, + "disallowSpacesInFunctionDeclaration": { + "beforeOpeningRoundBrace": true + }, + "disallowFunctionDeclarations": true, + "requireSpaceBetweenArguments": true, + "requireMultipleVarDecl": { + "allExcept": ["require"] + }, + "requireBlocksOnNewline": true, + "requireSemicolons": true, + "disallowEmptyBlocks": true, + "disallowSpacesInsideArrayBrackets": true, + "disallowSpacesInsideParentheses": true, + "requireCommaBeforeLineBreak": true, + "requireLineBreakAfterVariableAssignment": true, + "requirePaddingNewlinesBeforeKeywords": [ + "do", + "for", + "if", + "switch", + "try", + "void", + "while", + "return" + ], + "requirePaddingNewLinesInObjects": true, + "disallowSpaceAfterPrefixUnaryOperators": true, + "disallowSpaceBeforePostfixUnaryOperators": true, + "disallowSpaceBeforeBinaryOperators": [ + "," + ], + "requireSpacesInForStatement": true, + "requireSpaceBeforeBinaryOperators": true, + "requireSpaceAfterBinaryOperators": true, + "disallowKeywords": [ + "with" + ], + "validateIndentation": 4, + "disallowMixedSpacesAndTabs": true, + "disallowTrailingWhitespace": true, + "disallowTrailingComma": true, + "disallowKeywordsOnNewLine": [ + "else" + ], + "requireCapitalizedConstructors": true, + "disallowNewlineBeforeBlockStatements": true, + "disallowMultipleLineStrings": true, + "disallowMultipleLineBreaks": true, + "requireSpaceBeforeObjectValues": true, + "validateQuoteMarks": "'", + "jsDoc": { + "checkAnnotations": true, + "requireParamTypes": true, + "checkParamNames": true, + "checkParamExistence": true, + // "checkRedundantParams": true, + "checkReturnTypes": true, + "requireNewlineAfterDescription": true, + // "requireParamDescription": true, + "requireDescriptionCompleteSentence": true + } +} \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..aad2dc0 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,19 @@ +{ + "-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" + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b3b3bcd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +Change Log +========== + +## 3.3.0 +- Adds missing documentation for various configuration options. +- Adds `onPaginateStart` and `onPaginateEnd` callbacks, and corresponding events. +- Adds a load more button demo. + +## 3.2.0 + +- Adds ability to query and render multiple `pageList` and `pageStats` elements in the DOM. +- Adds two new demos + +## 3.1.0 + +- Bump core dependency to 3.1.2, improves version comparison functionality. + +## 3.0.0 + +- Release diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce4f6c5 --- /dev/null +++ b/README.md @@ -0,0 +1,169 @@ +# MixItUp Pagination + +MixItUp Pagination is a premium extension for the MixItUp 3, adding dynamic and responsive client-side pagination to filterable and sortable content. + +### Features + +- Paginate through **filtered** and **sorted** content +- Real-time page limit configuration – ideal for **responsive** grids +- Real-time **dynamically generated controls** and **page statistics** based on the current filter, sort, and page limit values +- New API methods +- New configuration options + +### Uses + +- Increase usability and reduce excessive scrolling when dealing with large datasets +- Provide visual feedback about content to users as they filter and sort + +### Limitations + +- Client-side only - the entire target collection must exist in the DOM + +*NB: If you're looking to integrate server-side ajax pagination 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 pagination distribution script (i.e. `mixitup-pagination.min.js`) **after** mixitup, and the extension will automatically detect the `mixitup` global variable and install itself. + +```html + ... + + + + + +``` + +#### Module Import + +If you are building a modular JavaScript project with Webpack, Browserify, or RequireJS, no global variables are exposed. Firstly require both the MixItUp core *and* the Pagination 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 mixitupPagination from '../path/to/mixitup-pagination'; // 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(mixitupPagination); +``` + +```js +// CommonJS + +var mixitup = require('mixitup'); +var mixitupPagination = require('../path/to/mixitup-pagination'); + +mixitup.use(mixitupPagination); +``` + +```js +// AMD + +require([ + 'mixitup', + '../path/to/mixitup-pagination' +], function( + mixitup, + mixitupPagination +) { + mixitup.use(mixitupPagination); +}); +``` + +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 Pagination + +MixItUp Pagination extends MixItUp's API and configuration object with various new methods and properties. + +By default, pagination functionality is disabled so that you can use MixItUp as normal, even with the extension installed. To enable pagination functionality for a mixer, you simply need to set a value for the `pagination.limit` configuration option. + +```js +var mixer = mixitup(containerEl, { + pagination: { + limit: 8 // impose a limit of 8 targets per page + } +}); +``` + +### Page List UI + +Given a "page list" element in DOM, MixItUp Pagination will automatically generate a list of "pager" controls allowing the user to move from page to page, as well as providing visual feedback about the current number of pages. + +MixItUp Pagination will query the DOM for an element matching the `selectors.pageList` configuration option (`'.mixitup-page-list'` by default). If a matching element is found, a list of pager buttons will be automatically rendered inside this element. + +```html +
+
+
+ ... +
+ +
+``` + +### Page Stats UI + +Given a "page stats" element in the DOM, MixItUp Pagination will automatically render information about the page and matching dataset, for example "5-8 of 32". + +MixItUp Pagination will query the DOM for an element matching the `selectors.pageStats` configuration option (`'.mixitup-page-stats'` by default). If a matching element is found, content will be rendered inside this element. + +```html +
+
+
+ ... +
+ +
+
+``` + +## Using the API + +As with the MixItUp core, the default "pager" controls are optional. If you wish to change the curent page or page limit via the API, MixItUp Pagination adds several new mixer API methods allowing API-based full control. + +###### Example: Calling an API method + +```js +var mixer = mixitup(containerEl, { + pagination: { + limit: 12 + } +}); + +mixer.paginate(2); // go to page 2 +``` + +Further reading: [Mixer API Methods](./docs/mixitup.Mixer.md) + +## Configuration + +MixItUp Pagination adds various new properties to the configuration object. If no configuration object is passed, the default settings will be used. However, `pagination.limit` must always be set to a value greater than `0` if you wish to enable pagination on instantiation. + +###### Example: Customizing pagination options + +```js +var mixer = mixitup(containerEl, { + pagination: { + limit: 4, + maintainActivePage: false, + loop: true, + hidePageListIfSinglePage: true + }, + load: { + page: 3 // load page 3 on instantiation + } +}); +``` + +Further reading: [Configuration Object](/docs/mixitup.Config.md) + diff --git a/demos/basic/index.html b/demos/basic/index.html new file mode 100644 index 0000000..bec454e --- /dev/null +++ b/demos/basic/index.html @@ -0,0 +1,72 @@ + + + + + + + + + MixItUp Pagination Demo - Basic Pagination Functionality + + +
+ + + + + + + + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/basic/style.css b/demos/basic/style.css new file mode 100644 index 0000000..458dc5b --- /dev/null +++ b/demos/basic/style.css @@ -0,0 +1,270 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.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:hover { + background: #3f3f3f; +} + +.control[data-filter]:after { + content: ''; + position: absolute; + 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[data-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(1px) rotate(45deg); +} + +.control[data-sort*=":desc"]:after { + transform: translateY(-4px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.mixitup-control-active[data-filter]:after { + background: transparent; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.control[data-filter] + .control[data-sort] { + margin-left: .75rem; +} + +.control[data-filter=".green"] { + color: #91e6c7; +} + +.control[data-filter=".blue"] { + color: #5ecdde; +} + +.control[data-filter=".pink"] { + color: #d595aa; +} + +.control[data-filter="none"] { + color: #2f2f2f; +} + +/* 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; + border-top: .5rem solid currentColor; + border-radius: 2px; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 56.25%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +/* 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)); + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } +} + + diff --git a/demos/index.html b/demos/index.html new file mode 100644 index 0000000..9f77c0c --- /dev/null +++ b/demos/index.html @@ -0,0 +1,17 @@ + + + + MixItUp Pagination Demos + + +

MixItUp Pagination Demos

+ + + + \ No newline at end of file diff --git a/demos/infinite-scroll/index.html b/demos/infinite-scroll/index.html new file mode 100644 index 0000000..7f797b2 --- /dev/null +++ b/demos/infinite-scroll/index.html @@ -0,0 +1,207 @@ + + + + + + + + + MixItUp Pagination Demo - Infinite Scroll + + + + +
+ + + + + + + + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/infinite-scroll/style.css b/demos/infinite-scroll/style.css new file mode 100644 index 0000000..201651f --- /dev/null +++ b/demos/infinite-scroll/style.css @@ -0,0 +1,170 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.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:hover { + background: #3f3f3f; +} + +.control[data-filter]:after { + content: ''; + position: absolute; + 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[data-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(1px) rotate(45deg); +} + +.control[data-sort*=":desc"]:after { + transform: translateY(-4px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.mixitup-control-active[data-filter]:after { + background: transparent; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.control[data-filter] + .control[data-sort] { + margin-left: .75rem; +} + +.control[data-filter=".green"] { + color: #91e6c7; +} + +.control[data-filter=".blue"] { + color: #5ecdde; +} + +.control[data-filter=".pink"] { + color: #d595aa; +} + +.control[data-filter="none"] { + color: #2f2f2f; +} + +/* 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; + border-top: .5rem solid currentColor; + border-radius: 2px; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 56.25%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } +} \ No newline at end of file diff --git a/demos/load-more-button/index.html b/demos/load-more-button/index.html new file mode 100644 index 0000000..e93bdde --- /dev/null +++ b/demos/load-more-button/index.html @@ -0,0 +1,113 @@ + + + + + + + + + MixItUp Pagination Demo - Load More Button + + + + + +
+ + + + + + + + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+ + + + + + + \ No newline at end of file diff --git a/demos/load-more-button/style.css b/demos/load-more-button/style.css new file mode 100644 index 0000000..d08afd5 --- /dev/null +++ b/demos/load-more-button/style.css @@ -0,0 +1,198 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.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:hover { + background: #3f3f3f; +} + +.control[data-filter]:after { + content: ''; + position: absolute; + 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[data-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(1px) rotate(45deg); +} + +.control[data-sort*=":desc"]:after { + transform: translateY(-4px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.mixitup-control-active[data-filter]:after { + background: transparent; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.control[data-filter] + .control[data-sort] { + margin-left: .75rem; +} + +.control[data-filter=".green"] { + color: #91e6c7; +} + +.control[data-filter=".blue"] { + color: #5ecdde; +} + +.control[data-filter=".pink"] { + color: #d595aa; +} + +.control[data-filter="none"] { + color: #2f2f2f; +} + +/* Load More Button +---------------------------------------------------------------------- */ + +.load-more-wrapper { + text-align: center; + padding: 1rem; +} + +.load-more { + display: inline-block; + background: white; + font-size: 1rem; + font-family: 'helvetica', arial, sans-serif; + padding: 1rem; + min-width: 8rem; + cursor: pointer; + color: #333; + box-shadow: 0 0 8px rgba(0, 0, 0, .05); + border-radius: 3px; +} + +.load-more:disabled { + opacity: .5; + color: #aaa; + cursor: default; +} + +/* 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; + border-top: .5rem solid currentColor; + border-radius: 2px; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 56.25%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +/* Grid Breakpoints +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } +} + diff --git a/demos/mixitup.min.js b/demos/mixitup.min.js new file mode 100644 index 0000000..970c32c --- /dev/null +++ b/demos/mixitup.min.js @@ -0,0 +1,18 @@ +/**! + * MixItUp v3.2.2 + * A high-performance, dependency-free library for animated filtering, sorting and more + * Build 20a1a182-d7bd-4c8f-807d-b888e325e44d + * + * @copyright Copyright 2014-2017 KunkaLabs Limited. + * @author KunkaLabs Limited. + * @link https://www.kunkalabs.com/mixitup/ + * + * @license Commercial use requires a commercial license. + * https://www.kunkalabs.com/mixitup/licenses/ + * + * Non-commercial use permitted under same terms as CC BY-NC 3.0 license. + * http://creativecommons.org/licenses/by-nc/3.0/ + */ +!function(t){"use strict";var e=null,n=null;!function(){var e=["webkit","moz","o","ms"],n=t.document.createElement("div"),a=-1;for(a=0;a-1}}(t.Element.prototype),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!1,n=[],a=-1;return e=!{toString:null}.propertyIsEnumerable("toString"),n=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],a=n.length,function(i){var o=[],r="",s=-1;if("object"!=typeof i&&("function"!=typeof i||null===i))throw new TypeError("Object.keys called on non-object");for(r in i)t.call(i,r)&&o.push(r);if(e)for(s=0;s>>0,0===i)return-1;if(e=0,arguments.length>1&&(e=Number(arguments[1]),e!==e?e=0:0!==e&&e!==1/0&&e!==-(1/0)&&(e=(e>0||-1)*Math.floor(Math.abs(e)))),e>=i)return-1;for(n=e>=0?e:Math.max(i-Math.abs(e),0);n0)||s);g++)r.id?d=r.id:(d="MixItUp"+n.randomHex(),r.id=d),e.instances[d]instanceof e.Mixer?(l=e.instances[d],(!i||i&&i.debug&&i.debug.showWarnings!==!1)&&console.warn(e.messages.warningFactoryPreexistingInstance())):(l=new e.Mixer,l.attach(r,u,d,i),e.instances[d]=l),c=new e.Facade(l),i&&i.debug&&i.debug.enable?h.push(l):h.push(c);return f=s?new e.Collection(h):h[0]},e.use=function(t){e.Base.prototype.callActions.call(e,"beforeUse",arguments),"function"==typeof t&&"mixitup-extension"===t.TYPE?"undefined"==typeof e.extensions[t.NAME]&&(t(e),e.extensions[t.NAME]=t):t.fn&&t.fn.jquery&&(e.libraries.$=t),e.Base.prototype.callActions.call(e,"afterUse",arguments)},e.instances={},e.extensions={},e.libraries={},n={hasClass:function(t,e){return!!t.className.match(new RegExp("(\\s|^)"+e+"(\\s|$)"))},addClass:function(t,e){this.hasClass(t,e)||(t.className+=t.className?" "+e:e)},removeClass:function(t,e){if(this.hasClass(t,e)){var n=new RegExp("(\\s|^)"+e+"(\\s|$)");t.className=t.className.replace(n," ").trim()}},extend:function(t,e,n,a){var i=[],o="",r=-1;n=n||!1,a=a||!1;try{if(Array.isArray(e))for(r=0;ru&&(u=f,l=c)}throw u>1&&(s=e.messages.errorConfigInvalidPropertySuggestion({probableMatch:l})),r=e.messages.errorConfigInvalidProperty({erroneous:o,suggestion:s}),new TypeError(r)}throw t},template:function(t){for(var e=/\${([\w]*)}/g,n={},a=null;a=e.exec(t);)n[a[1]]=new RegExp("\\${"+a[1]+"}","g");return function(e){var a="",i=t;e=e||{};for(a in n)i=i.replace(n[a],"undefined"!=typeof e[a]?e[a]:"");return i}},on:function(e,n,a,i){e&&(e.addEventListener?e.addEventListener(n,a,i):e.attachEvent&&(e["e"+n+a]=a,e[n+a]=function(){e["e"+n+a](t.event)},e.attachEvent("on"+n,e[n+a])))},off:function(t,e,n){t&&(t.removeEventListener?t.removeEventListener(e,n,!1):t.detachEvent&&(t.detachEvent("on"+e,t[e+n]),t[e+n]=null))},getCustomEvent:function(e,n,a){var i=null;return a=a||t.document,"function"==typeof t.CustomEvent?i=new t.CustomEvent(e,{detail:n,bubbles:!0,cancelable:!0}):"function"==typeof a.createEvent?(i=a.createEvent("CustomEvent"),i.initCustomEvent(e,!0,!0,n)):(i=a.createEventObject(),i.type=e,i.returnValue=!1,i.cancelBubble=!1,i.detail=n),i},getOriginalEvent:function(t){return t.touches&&t.touches.length?t.touches[0]:t.changedTouches&&t.changedTouches.length?t.changedTouches[0]:t},index:function(t,e){for(var n=0;null!==(t=t.previousElementSibling);)e&&!t.matches(e)||++n;return n},camelCase:function(t){return t.toLowerCase().replace(/([_-][a-z])/g,function(t){return t.toUpperCase().replace(/[_-]/,"")})},pascalCase:function(t){return(t=this.camelCase(t)).charAt(0).toUpperCase()+t.slice(1)},dashCase:function(t){return t.replace(/([A-Z])/g,"-$1").replace(/^-/,"").toLowerCase()},isElement:function(e,n){return n=n||t.document,!!(t.HTMLElement&&e instanceof t.HTMLElement)||(!!(n.defaultView&&n.defaultView.HTMLElement&&e instanceof n.defaultView.HTMLElement)||null!==e&&1===e.nodeType&&"string"==typeof e.nodeName)},createElement:function(e,n){var a=null,i=null;for(n=n||t.document,a=n.createDocumentFragment(),i=n.createElement("div"),i.innerHTML=e.trim();i.firstChild;)a.appendChild(i.firstChild);return a},removeWhitespace:function(t){for(var e;t&&"#text"===t.nodeName;)e=t,t=t.previousSibling,e.parentElement&&e.parentElement.removeChild(e)},isEqualArray:function(t,e){var n=t.length;if(n!==e.length)return!1;for(;n--;)if(t[n]!==e[n])return!1;return!0},deepEquals:function(t,e){var n;if("object"==typeof t&&t&&"object"==typeof e&&e){if(Object.keys(t).length!==Object.keys(e).length)return!1;for(n in t)if(!e.hasOwnProperty(n)||!this.deepEquals(t[n],e[n]))return!1}else if(t!==e)return!1;return!0},arrayShuffle:function(t){for(var e=t.slice(),n=e.length,a=n,i=-1,o=[];a--;)i=~~(Math.random()*n),o=e[a],e[a]=e[i],e[i]=o;return e},arrayFromList:function(t){var e,n;try{return Array.prototype.slice.call(t)}catch(a){for(e=[],n=0;n "+n),o&&e.removeAttribute("id")),i},clean:function(t){var e=[],n=-1;for(n=0;ni)return!0}return!0},Deferred:function(){this.promise=null,this.resolve=null,this.reject=null,this.id=n.randomHex()},isEmptyObject:function(t){var e="";if("function"==typeof Object.keys)return 0===Object.keys(t).length;for(e in t)if(t.hasOwnProperty(e))return!1;return!0},getClassname:function(t,e,n){var a="";return a+=t.block,a.length&&(a+=t.delineatorElement),a+=t["element"+this.pascalCase(e)],n?(a.length&&(a+=t.delineatorModifier),a+=n):a},getProperty:function(t,e){var n=e.split("."),a=null,i="",o=0;if(!e)return t;for(a=function(t){return t?t[i]:null};o-1,e.callFilters("afterIsBound",n,arguments)},addBinding:function(t){var e=this;this.callActions("beforeAddBinding",arguments),e.isBound()||e.bound.push(t),this.callActions("afterAddBinding",arguments)},removeBinding:function(t){var n=this,a=-1;this.callActions("beforeRemoveBinding",arguments),(a=n.bound.indexOf(t))>-1&&n.bound.splice(a,1),n.bound.length<1&&(n.unbindClick(),a=e.controls.indexOf(n),e.controls.splice(a,1),"active"===n.status&&n.renderStatus(n.el,"inactive")),this.callActions("afterRemoveBinding",arguments)},bindClick:function(){var t=this;this.callActions("beforeBindClick",arguments),t.handler=function(e){t.handleClick(e)},n.on(t.el,"click",t.handler),this.callActions("afterBindClick",arguments)},unbindClick:function(){var t=this;this.callActions("beforeUnbindClick",arguments),n.off(t.el,"click",t.handler),t.handler=null,this.callActions("afterUnbindClick",arguments)},handleClick:function(t){var a=this,i=null,o=null,r=!1,s=void 0,l={},c=null,u=[],f=-1;if(this.callActions("beforeHandleClick",arguments),this.pending=0,o=a.bound[0],i=a.selector?n.closestParent(t.target,o.config.selectors.control+a.selector,!0,o.dom.document):a.el,!i)return void a.callActions("afterHandleClick",arguments);switch(a.type){case"filter":l.filter=a.filter||i.getAttribute("data-filter");break;case"sort":l.sort=a.sort||i.getAttribute("data-sort");break;case"multimix":l.filter=a.filter||i.getAttribute("data-filter"),l.sort=a.sort||i.getAttribute("data-sort");break;case"toggle":l.filter=a.filter||i.getAttribute("data-toggle"),r="live"===a.status?n.hasClass(i,a.classNames.active):"active"===a.status}for(f=0;f0||("live"===a.status?a.updateLive(t,n):(i.sort=a.sort,i.filter=a.filter,a.callFilters("actionsUpdate",i,arguments),a.parseStatusChange(a.el,t,i,n)),a.callActions("afterUpdate",arguments))},updateLive:function(t,n){var a=this,i=null,o=null,r=null,s=-1;if(a.callActions("beforeUpdateLive",arguments),a.el){for(i=a.el.querySelectorAll(a.selector),s=0;r=i[s];s++){switch(o=new e.CommandMultimix,a.type){case"filter":o.filter=r.getAttribute("data-filter");break;case"sort":o.sort=r.getAttribute("data-sort");break;case"multimix":o.filter=r.getAttribute("data-filter"),o.sort=r.getAttribute("data-sort");break;case"toggle":o.filter=r.getAttribute("data-toggle")}o=a.callFilters("actionsUpdateLive",o,arguments),a.parseStatusChange(r,t,o,n)}a.callActions("afterUpdateLive",arguments)}},parseStatusChange:function(t,e,n,a){var i=this,o="",r="",s=-1;switch(i.callActions("beforeParseStatusChange",arguments),i.type){case"filter":e.filter===n.filter?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"multimix":e.sort===n.sort&&e.filter===n.filter?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"sort":e.sort.match(/:asc/g)&&(o=e.sort.replace(/:asc/g,"")),e.sort===n.sort||o===n.sort?i.renderStatus(t,"active"):i.renderStatus(t,"inactive");break;case"toggle":for(a.length<1&&i.renderStatus(t,"inactive"),e.filter===n.filter&&i.renderStatus(t,"active"),s=0;s-1)throw new Error(e.messages.errorInsertPreexistingElement());c.style.display="none",s.appendChild(c),s.appendChild(i.dom.document.createTextNode(" ")),n.isElement(c,i.dom.document)&&c.matches(i.config.selectors.target)&&(l=new e.Target,l.init(c,i),l.isInDom=!0,i.targets.splice(r,0,l),r++)}i.dom.parent.insertBefore(s,o)}a.startOrder=i.origOrder=i.targets,i.callActions("afterInsertTargets",arguments)},getNextSibling:function(t,e,n){var a=this,i=null;return t=Math.max(t,0),e&&"before"===n?i=e:e&&"after"===n?i=e.nextElementSibling||null:a.targets.length>0&&"undefined"!=typeof t?i=t0&&(a.config.layout.siblingAfter?i=a.config.layout.siblingAfter:a.config.layout.siblingBefore?i=a.config.layout.siblingBefore.nextElementSibling:a.dom.parent.children[0]),a.callFilters("elementGetNextSibling",i,arguments)},filterOperation:function(t){var e=this,n=!1,a=-1,i="",o=null,r=-1;for(e.callActions("beforeFilterOperation",arguments),i=t.newFilter.action,r=0;o=t.newOrder[r];r++)n=t.newFilter.collection?t.newFilter.collection.indexOf(o.dom.el)>-1:""!==t.newFilter.selector&&o.dom.el.matches(t.newFilter.selector),e.evaluateHideShow(n,o,i,t);if(t.toRemove.length)for(r=0;o=t.show[r];r++)t.toRemove.indexOf(o)>-1&&(t.show.splice(r,1),(a=t.toShow.indexOf(o))>-1&&t.toShow.splice(a,1),t.toHide.push(o),t.hide.push(o),r--);t.matching=t.show.slice(),0===t.show.length&&""!==t.newFilter.selector&&0!==e.targets.length&&(t.hasFailed=!0),e.callActions("afterFilterOperation",arguments)},evaluateHideShow:function(t,e,n,a){var i=this;i.callActions("beforeEvaluateHideShow",arguments),t===!0&&"show"===n||t===!1&&"hide"===n?(a.show.push(e),!e.isShown&&a.toShow.push(e)):(a.hide.push(e),e.isShown&&a.toHide.push(e)),i.callActions("afterEvaluateHideShow",arguments)},sortOperation:function(t){var e=this;e.callActions("beforeSortOperation",arguments),t.startOrder=e.targets,t.newSort.collection?t.newOrder=t.newSort.collection:"random"===t.newSort.order?t.newOrder=n.arrayShuffle(t.startOrder):""===t.newSort.attribute?(t.newOrder=e.origOrder.slice(),"desc"===t.newSort.order&&t.newOrder.reverse()):(t.newOrder=t.startOrder.slice(),t.newOrder.sort(function(n,a){return e.compare(n,a,t.newSort)})),n.isEqualArray(t.newOrder,t.startOrder)&&(t.willSort=!1),e.callActions("afterSortOperation",arguments)},compare:function(t,e,n){var a=this,i=n.order,o=a.getAttributeValue(t,n.attribute),r=a.getAttributeValue(e,n.attribute);return isNaN(1*o)||isNaN(1*r)?(o=o.toLowerCase(),r=r.toLowerCase()):(o=1*o,r=1*r),or?"asc"===i?1:-1:o===r&&n.next?a.compare(t,e,n.next):0},getAttributeValue:function(t,n){var a=this,i="";return i=t.dom.el.getAttribute("data-"+n),null===i&&a.config.debug.showWarnings&&console.warn(e.messages.warningInconsistentSortingAttributes({attribute:"data-"+n})),a.callFilters("valueGetAttributeValue",i||0,arguments)},printSort:function(e,a){var i=this,o=e?a.newOrder:a.startOrder,r=e?a.startOrder:a.newOrder,s=o.length?o[o.length-1].dom.el.nextElementSibling:null,l=t.document.createDocumentFragment(),c=null,u=null,f=null,h=-1;for(i.callActions("beforePrintSort",arguments),h=0;u=o[h];h++)f=u.dom.el,"absolute"!==f.style.position&&(n.removeWhitespace(f.previousSibling),f.parentElement.removeChild(f));for(c=s?s.previousSibling:i.dom.parent.lastChild,c&&"#text"===c.nodeName&&n.removeWhitespace(c),h=0;u=r[h];h++)f=u.dom.el,n.isElement(l.lastChild)&&l.appendChild(t.document.createTextNode(" ")),l.appendChild(f);i.dom.parent.firstChild&&i.dom.parent.firstChild!==s&&l.insertBefore(t.document.createTextNode(" "),l.childNodes[0]),s?(l.appendChild(t.document.createTextNode(" ")),i.dom.parent.insertBefore(l,s)):i.dom.parent.appendChild(l),i.callActions("afterPrintSort",arguments)},parseSortString:function(t,a){var i=this,o=t.split(" "),r=a,s=[],l=-1;for(l=0;l-1&&(c=n.substring(l),u=s.exec(c),f=u[1]),t){case"fade":a.opacity=f?parseFloat(f):0;break;case"stagger":r.staggerDuration=f?parseFloat(f):100;break;default:if(o&&r.config.animation.reverseOut&&"scale"!==t?a[t].value=(f?parseFloat(f):e.transformDefaults[t].value)*-1:a[t].value=f?parseFloat(f):e.transformDefaults[t].value,f){for(m=0;d=h[m];m++)if(f.indexOf(d)>-1){a[t].unit=d;break}}else a[t].unit=e.transformDefaults[t].unit;i.push(t+"("+a[t].value+a[t].unit+")")}r.callActions("afterParseEffect",arguments)},buildState:function(t){var n=this,a=new e.State,i=null,o=-1;for(n.callActions("beforeBuildState",arguments),o=0;i=n.targets[o];o++)(!t.toRemove.length||t.toRemove.indexOf(i)<0)&&a.targets.push(i.dom.el);for(o=0;i=t.matching[o];o++)a.matching.push(i.dom.el);for(o=0;i=t.show[o];o++)a.show.push(i.dom.el);for(o=0;i=t.hide[o];o++)(!t.toRemove.length||t.toRemove.indexOf(i)<0)&&a.hide.push(i.dom.el);return a.id=n.id,a.container=n.dom.container,a.activeFilter=t.newFilter,a.activeSort=t.newSort,a.activeDataset=t.newDataset,a.activeContainerClassName=t.newContainerClassName,a.hasFailed=t.hasFailed,a.totalTargets=n.targets.length,a.totalShow=t.show.length,a.totalHide=t.hide.length,a.totalMatching=t.matching.length,a.triggerElement=t.triggerElement,n.callFilters("stateBuildState",a,arguments)},goMix:function(a,i){var o=this,r=null;return o.callActions("beforeGoMix",arguments),o.config.animation.duration&&o.config.animation.effects&&n.isVisible(o.dom.container)||(a=!1),i.toShow.length||i.toHide.length||i.willSort||i.willChangeLayout||(a=!1),i.startState.show.length||i.show.length||(a=!1),e.events.fire("mixStart",o.dom.container,{state:i.startState,futureState:i.newState,instance:o},o.dom.document),"function"==typeof o.config.callbacks.onMixStart&&o.config.callbacks.onMixStart.call(o.dom.container,i.startState,i.newState,o),n.removeClass(o.dom.container,n.getClassname(o.config.classNames,"container",o.config.classNames.modifierFailed)),r=o.userDeferred?o.userDeferred:o.userDeferred=n.defer(e.libraries),o.isBusy=!0,a&&e.features.has.transitions?(t.pageYOffset!==i.docState.scrollTop&&t.scrollTo(i.docState.scrollLeft,i.docState.scrollTop),o.config.animation.applyPerspective&&(o.dom.parent.style[e.features.perspectiveProp]=o.config.animation.perspectiveDistance,o.dom.parent.style[e.features.perspectiveOriginProp]=o.config.animation.perspectiveOrigin),o.config.animation.animateResizeContainer&&i.startHeight!==i.newHeight&&i.viewportDeltaY!==i.startHeight-i.newHeight&&(o.dom.parent.style.height=i.startHeight+"px"),o.config.animation.animateResizeContainer&&i.startWidth!==i.newWidth&&i.viewportDeltaX!==i.startWidth-i.newWidth&&(o.dom.parent.style.width=i.startWidth+"px"),i.startHeight===i.newHeight&&(o.dom.parent.style.height=i.startHeight+"px"),i.startWidth===i.newWidth&&(o.dom.parent.style.width=i.startWidth+"px"),i.startHeight===i.newHeight&&i.startWidth===i.newWidth&&(o.dom.parent.style.overflow="hidden"),requestAnimationFrame(function(){o.moveTargets(i)}),o.callFilters("promiseGoMix",r.promise,arguments)):(o.config.debug.fauxAsync?setTimeout(function(){o.cleanUp(i)},o.config.animation.duration):o.cleanUp(i),o.callFilters("promiseGoMix",r.promise,arguments))},getStartMixData:function(n){var a=this,i=t.getComputedStyle(a.dom.parent),o=a.dom.parent.getBoundingClientRect(),r=null,s={},l=-1,c=i[e.features.boxSizingProp];for(a.incPadding="border-box"===c,a.callActions("beforeGetStartMixData",arguments),l=0;r=n.show[l];l++)s=r.getPosData(),n.showPosData[l]={startPosData:s};for(l=0;r=n.toHide[l];l++)s=r.getPosData(),n.toHidePosData[l]={startPosData:s};n.startX=o.left,n.startY=o.top,n.startHeight=a.incPadding?o.height:o.height-parseFloat(i.paddingTop)-parseFloat(i.paddingBottom)-parseFloat(i.borderTop)-parseFloat(i.borderBottom),n.startWidth=a.incPadding?o.width:o.width-parseFloat(i.paddingLeft)-parseFloat(i.paddingRight)-parseFloat(i.borderLeft)-parseFloat(i.borderRight),a.callActions("afterGetStartMixData",arguments)},setInter:function(t){var e=this,a=null,i=-1;for(e.callActions("beforeSetInter",arguments),e.config.animation.clampHeight&&(e.dom.parent.style.height=t.startHeight+"px",e.dom.parent.style.overflow="hidden"),e.config.animation.clampWidth&&(e.dom.parent.style.width=t.startWidth+"px",e.dom.parent.style.overflow="hidden"),i=0;a=t.toShow[i];i++)a.show();t.willChangeLayout&&(n.removeClass(e.dom.container,t.startContainerClassName),n.addClass(e.dom.container,t.newContainerClassName)),e.callActions("afterSetInter",arguments)},getInterMixData:function(t){var e=this,n=null,a=-1;for(e.callActions("beforeGetInterMixData",arguments),a=0;n=t.show[a];a++)t.showPosData[a].interPosData=n.getPosData();for(a=0;n=t.toHide[a];a++)t.toHidePosData[a].interPosData=n.getPosData();e.callActions("afterGetInterMixData",arguments)},setFinal:function(t){var e=this,n=null,a=-1;for(e.callActions("beforeSetFinal",arguments),t.willSort&&e.printSort(!1,t),a=0;n=t.toHide[a];a++)n.hide();e.callActions("afterSetFinal",arguments)},getFinalMixData:function(e){var a=this,i=null,o=null,r=null,s=-1;for(a.callActions("beforeGetFinalMixData",arguments),s=0;r=e.show[s];s++)e.showPosData[s].finalPosData=r.getPosData();for(s=0;r=e.toHide[s];s++)e.toHidePosData[s].finalPosData=r.getPosData();for((a.config.animation.clampHeight||a.config.animation.clampWidth)&&(a.dom.parent.style.height=a.dom.parent.style.width=a.dom.parent.style.overflow=""),a.incPadding||(i=t.getComputedStyle(a.dom.parent)),o=a.dom.parent.getBoundingClientRect(),e.newX=o.left,e.newY=o.top,e.newHeight=a.incPadding?o.height:o.height-parseFloat(i.paddingTop)-parseFloat(i.paddingBottom)-parseFloat(i.borderTop)-parseFloat(i.borderBottom),e.newWidth=a.incPadding?o.width:o.width-parseFloat(i.paddingLeft)-parseFloat(i.paddingRight)-parseFloat(i.borderLeft)-parseFloat(i.borderRight),e.viewportDeltaX=e.docState.viewportWidth-this.dom.document.documentElement.clientWidth,e.viewportDeltaY=e.docState.viewportHeight-this.dom.document.documentElement.clientHeight,e.willSort&&a.printSort(!0,e),s=0;r=e.toShow[s];s++)r.hide();for(s=0;r=e.toHide[s];s++)r.show();e.willChangeLayout&&(n.removeClass(a.dom.container,e.newContainerClassName),n.addClass(a.dom.container,a.config.layout.containerClassName)),a.callActions("afterGetFinalMixData",arguments)},getTweenData:function(t){var n=this,a=null,i=null,o=Object.getOwnPropertyNames(n.effectsIn),r="",s=null,l=-1,c=-1,u=-1,f=-1;for(n.callActions("beforeGetTweenData",arguments),u=0;a=t.show[u];u++)for(i=t.showPosData[u],i.posIn=new e.StyleData,i.posOut=new e.StyleData,i.tweenData=new e.StyleData,a.isShown?(i.posIn.x=i.startPosData.x-i.interPosData.x,i.posIn.y=i.startPosData.y-i.interPosData.y):i.posIn.x=i.posIn.y=0,i.posOut.x=i.finalPosData.x-i.interPosData.x,i.posOut.y=i.finalPosData.y-i.interPosData.y,i.posIn.opacity=a.isShown?1:n.effectsIn.opacity,i.posOut.opacity=1,i.tweenData.opacity=i.posOut.opacity-i.posIn.opacity,a.isShown||n.config.animation.nudge||(i.posIn.x=i.posOut.x,i.posIn.y=i.posOut.y),i.tweenData.x=i.posOut.x-i.posIn.x,i.tweenData.y=i.posOut.y-i.posIn.y,n.config.animation.animateResizeTargets&&(i.posIn.width=i.startPosData.width,i.posIn.height=i.startPosData.height,l=(i.startPosData.width||i.finalPosData.width)-i.interPosData.width,i.posIn.marginRight=i.startPosData.marginRight-l,c=(i.startPosData.height||i.finalPosData.height)-i.interPosData.height,i.posIn.marginBottom=i.startPosData.marginBottom-c,i.posOut.width=i.finalPosData.width,i.posOut.height=i.finalPosData.height,l=(i.finalPosData.width||i.startPosData.width)-i.interPosData.width,i.posOut.marginRight=i.finalPosData.marginRight-l,c=(i.finalPosData.height||i.startPosData.height)-i.interPosData.height,i.posOut.marginBottom=i.finalPosData.marginBottom-c,i.tweenData.width=i.posOut.width-i.posIn.width,i.tweenData.height=i.posOut.height-i.posIn.height,i.tweenData.marginRight=i.posOut.marginRight-i.posIn.marginRight,i.tweenData.marginBottom=i.posOut.marginBottom-i.posIn.marginBottom),f=0;r=o[f];f++)s=n.effectsIn[r],s instanceof e.TransformData&&s.value&&(i.posIn[r].value=s.value,i.posOut[r].value=0,i.tweenData[r].value=i.posOut[r].value-i.posIn[r].value,i.posIn[r].unit=i.posOut[r].unit=i.tweenData[r].unit=s.unit);for(u=0;a=t.toHide[u];u++)for(i=t.toHidePosData[u],i.posIn=new e.StyleData,i.posOut=new e.StyleData,i.tweenData=new e.StyleData,i.posIn.x=a.isShown?i.startPosData.x-i.interPosData.x:0,i.posIn.y=a.isShown?i.startPosData.y-i.interPosData.y:0,i.posOut.x=n.config.animation.nudge?0:i.posIn.x,i.posOut.y=n.config.animation.nudge?0:i.posIn.y,i.tweenData.x=i.posOut.x-i.posIn.x,i.tweenData.y=i.posOut.y-i.posIn.y,n.config.animation.animateResizeTargets&&(i.posIn.width=i.startPosData.width,i.posIn.height=i.startPosData.height,l=i.startPosData.width-i.interPosData.width,i.posIn.marginRight=i.startPosData.marginRight-l,c=i.startPosData.height-i.interPosData.height,i.posIn.marginBottom=i.startPosData.marginBottom-c),i.posIn.opacity=1,i.posOut.opacity=n.effectsOut.opacity,i.tweenData.opacity=i.posOut.opacity-i.posIn.opacity,f=0;r=o[f];f++)s=n.effectsOut[r],s instanceof e.TransformData&&s.value&&(i.posIn[r].value=0,i.posOut[r].value=s.value,i.tweenData[r].value=i.posOut[r].value-i.posIn[r].value,i.posIn[r].unit=i.posOut[r].unit=i.tweenData[r].unit=s.unit);n.callActions("afterGetTweenData",arguments)},moveTargets:function(t){var a=this,i=null,o=null,r=null,s="",l=!1,c=-1,u=-1,f=a.checkProgress.bind(a);for(a.callActions("beforeMoveTargets",arguments),u=0;i=t.show[u];u++)o=new e.IMoveData,r=t.showPosData[u],s=i.isShown?"none":"show",l=a.willTransition(s,t.hasEffect,r.posIn,r.posOut),l&&c++,i.show(),o.posIn=r.posIn,o.posOut=r.posOut,o.statusChange=s,o.staggerIndex=c,o.operation=t,o.callback=l?f:null,i.move(o);for(u=0;i=t.toHide[u];u++)r=t.toHidePosData[u],o=new e.IMoveData,s="hide",l=a.willTransition(s,r.posIn,r.posOut),o.posIn=r.posIn,o.posOut=r.posOut,o.statusChange=s,o.staggerIndex=u,o.operation=t,o.callback=l?f:null,i.move(o);a.config.animation.animateResizeContainer&&(a.dom.parent.style[e.features.transitionProp]="height "+a.config.animation.duration+"ms ease, width "+a.config.animation.duration+"ms ease ",requestAnimationFrame(function(){t.startHeight!==t.newHeight&&t.viewportDeltaY!==t.startHeight-t.newHeight&&(a.dom.parent.style.height=t.newHeight+"px"),t.startWidth!==t.newWidth&&t.viewportDeltaX!==t.startWidth-t.newWidth&&(a.dom.parent.style.width=t.newWidth+"px")})),t.willChangeLayout&&(n.removeClass(a.dom.container,a.config.layout.ContainerClassName),n.addClass(a.dom.container,t.newContainerClassName)),a.callActions("afterMoveTargets",arguments)},hasEffect:function(){var t=this,e=["scale","translateX","translateY","translateZ","rotateX","rotateY","rotateZ"],n="",a=null,i=!1,o=-1,r=-1;if(1!==t.effectsIn.opacity)return t.callFilters("resultHasEffect",!0,arguments);for(r=0;n=e[r];r++)if(a=t.effectsIn[n],o="undefined"!==a.value?a.value:a,0!==o){i=!0;break}return t.callFilters("resultHasEffect",i,arguments)},willTransition:function(t,e,a,i){var o=this,r=!1;return r=!!n.isVisible(o.dom.container)&&(!!("none"!==t&&e||a.x!==i.x||a.y!==i.y)||!!o.config.animation.animateResizeTargets&&(a.width!==i.width||a.height!==i.height||a.marginRight!==i.marginRight||a.marginTop!==i.marginTop)),o.callFilters("resultWillTransition",r,arguments)},checkProgress:function(t){var e=this;e.targetsDone++,e.targetsBound===e.targetsDone&&e.cleanUp(t)},cleanUp:function(t){var a=this,i=null,o=null,r=null,s=null,l=-1;for(a.callActions("beforeCleanUp",arguments),a.targetsMoved=a.targetsImmovable=a.targetsBound=a.targetsDone=0,l=0;i=t.show[l];l++)i.cleanUp(),i.show();for(l=0;i=t.toHide[l];l++)i.cleanUp(),i.hide();if(t.willSort&&a.printSort(!1,t),a.dom.parent.style[e.features.transitionProp]=a.dom.parent.style.height=a.dom.parent.style.width=a.dom.parent.style.overflow=a.dom.parent.style[e.features.perspectiveProp]=a.dom.parent.style[e.features.perspectiveOriginProp]="",t.willChangeLayout&&(n.removeClass(a.dom.container,t.startContainerClassName),n.addClass(a.dom.container,t.newContainerClassName)),t.toRemove.length){for(l=0;i=a.targets[l];l++)t.toRemove.indexOf(i)>-1&&((o=i.dom.el.previousSibling)&&"#text"===o.nodeName&&(r=i.dom.el.nextSibling)&&"#text"===r.nodeName&&n.removeWhitespace(o),t.willSort||a.dom.parent.removeChild(i.dom.el),a.targets.splice(l,1),i.isInDom=!1,l--);a.origOrder=a.targets}t.willSort&&(a.targets=t.newOrder),a.state=t.newState,a.lastOperation=t,a.dom.targets=a.state.targets,e.events.fire("mixEnd",a.dom.container,{state:a.state,instance:a},a.dom.document),"function"==typeof a.config.callbacks.onMixEnd&&a.config.callbacks.onMixEnd.call(a.dom.container,a.state,a),t.hasFailed&&(e.events.fire("mixFail",a.dom.container,{state:a.state,instance:a},a.dom.document),"function"==typeof a.config.callbacks.onMixFail&&a.config.callbacks.onMixFail.call(a.dom.container,a.state,a),n.addClass(a.dom.container,n.getClassname(a.config.classNames,"container",a.config.classNames.modifierFailed))),"function"==typeof a.userCallback&&a.userCallback.call(a.dom.container,a.state,a),"function"==typeof a.userDeferred.resolve&&a.userDeferred.resolve(a.state),a.userCallback=null,a.userDeferred=null,a.lastClicked=null,a.isToggling=!1,a.isBusy=!1,a.queue.length&&(a.callActions("beforeReadQueueCleanUp",arguments),s=a.queue.shift(),a.userDeferred=s.deferred,a.isToggling=s.isToggling,a.lastClicked=s.triggerElement,s.instruction.command instanceof e.CommandMultimix?a.multimix.apply(a,s.args):a.dataset.apply(a,s.args)),a.callActions("afterCleanUp",arguments)},parseMultimixArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandMultimix,r=0;r-1?i.command.position=o:"string"==typeof o?i.command.collection=n.arrayFromList(n.createElement(o).childNodes):"object"==typeof o&&n.isElement(o,a.dom.document)?i.command.collection.length?i.command.sibling=o:i.command.collection=[o]:"object"==typeof o&&o.length?i.command.collection.length?i.command.sibling=o[0]:i.command.collection=o:"object"==typeof o&&o.childNodes&&o.childNodes.length?i.command.collection.length?i.command.sibling=o.childNodes[0]:i.command.collection=n.arrayFromList(o.childNodes):"object"==typeof o?n.extend(i.command,o):"boolean"==typeof o?i.animate=o:"function"==typeof o&&(i.callback=o));if(i.command.index&&i.command.sibling)throw new Error(e.messages.errorInsertInvalidArguments());return!i.command.collection.length&&a.config.debug.showWarnings&&console.warn(e.messages.warningInsertNoElements()),i=a.callFilters("instructionParseInsertArgs",i,arguments),n.freeze(i),i},parseRemoveArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=null,s=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandRemove,s=0;s-1&&i.command.targets.push(o);return!i.command.targets.length&&a.config.debug.showWarnings&&console.warn(e.messages.warningRemoveNoElements()),n.freeze(i),i},parseDatasetArgs:function(t){var a=this,i=new e.UserInstruction,o=null,r=-1;for(i.animate=a.config.animation.enable,i.command=new e.CommandDataset,r=0;r-1&&t.toggleArray.splice(a,1),i=t.getToggleSelector(),t.multimix({filter:i},e.animate,e.callback)},sort:function(){var t=this,e=t.parseSortArgs(arguments);return t.multimix({sort:e.command},e.animate,e.callback)},changeLayout:function(){var t=this,e=t.parseChangeLayoutArgs(arguments);return t.multimix({changeLayout:e.command},e.animate,e.callback)},dataset:function(){var t=this,n=t.parseDatasetArgs(arguments),a=null,i=null,o=!1;return t.callActions("beforeDataset",arguments),t.isBusy?(i=new e.QueueItem,i.args=arguments,i.instruction=n,t.queueMix(i)):(n.callback&&(t.userCallback=n.callback),o=n.animate^t.config.animation.enable?n.animate:t.config.animation.enable,a=t.getDataOperation(n.command.dataset),t.goMix(o,a))},multimix:function(){var t=this,n=null,a=!1,i=null,o=t.parseMultimixArgs(arguments);return t.callActions("beforeMultimix",arguments),t.isBusy?(i=new e.QueueItem,i.args=arguments,i.instruction=o,i.triggerElement=t.lastClicked,i.isToggling=t.isToggling,t.queueMix(i)):(n=t.getOperation(o.command),t.config.controls.enable&&(o.command.filter&&!t.isToggling&&(t.toggleArray.length=0,t.buildToggleArray(n.command)),t.queue.length<1&&t.updateControls(n.command)),o.callback&&(t.userCallback=o.callback),a=o.animate^t.config.animation.enable?o.animate:t.config.animation.enable,t.callFilters("operationMultimix",n,arguments),t.goMix(a,n))},getOperation:function(t){var a=this,i=t.sort,o=t.filter,r=t.changeLayout,s=t.remove,l=t.insert,c=new e.Operation;return c=a.callFilters("operationUnmappedGetOperation",c,arguments),c.id=n.randomHex(),c.command=t,c.startState=a.state,c.triggerElement=a.lastClicked,a.isBusy?(a.config.debug.showWarnings&&console.warn(e.messages.warningGetOperationInstanceBusy()),null):(l&&a.insertTargets(l,c),s&&(c.toRemove=s.targets),c.startSort=c.newSort=c.startState.activeSort,c.startOrder=c.newOrder=a.targets,i&&(c.startSort=c.startState.activeSort,c.newSort=i,c.willSort=a.willSort(i,c.startState.activeSort),c.willSort&&a.sortOperation(c)),c.startFilter=c.startState.activeFilter,o?c.newFilter=o:c.newFilter=n.extend(new e.CommandFilter,c.startFilter),"all"===c.newFilter.selector?c.newFilter.selector=a.config.selectors.target:"none"===c.newFilter.selector&&(c.newFilter.selector=""),a.filterOperation(c),c.startContainerClassName=c.startState.activeContainerClassName,r?(c.newContainerClassName=r.containerClassName,c.newContainerClassName!==c.startContainerClassName&&(c.willChangeLayout=!0)):c.newContainerClassName=c.startContainerClassName,a.config.animation.enable&&(a.getStartMixData(c),a.setInter(c),c.docState=n.getDocumentState(a.dom.document),a.getInterMixData(c),a.setFinal(c),a.getFinalMixData(c),a.parseEffects(),c.hasEffect=a.hasEffect(),a.getTweenData(c)),c.willSort&&(a.targets=c.newOrder),c.newState=a.buildState(c),a.callFilters("operationMappedGetOperation",c,arguments))},tween:function(t,e){var n=null,a=null,i=-1,o=-1;for(e=Math.min(e,1),e=Math.max(e,0),o=0;n=t.show[o];o++)a=t.showPosData[o],n.applyTween(a,e);for(o=0;n=t.hide[o];o++)n.isShown&&n.hide(),(i=t.toHide.indexOf(n))>-1&&(a=t.toHidePosData[i],n.isShown||n.show(),n.applyTween(a,e))},insert:function(){var t=this,e=t.parseInsertArgs(arguments);return t.multimix({insert:e.command},e.animate,e.callback)},insertBefore:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(e.command.collection,"before",e.command.sibling,e.animate,e.callback)},insertAfter:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(e.command.collection,"after",e.command.sibling,e.animate,e.callback)},prepend:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(0,e.command.collection,e.animate,e.callback)},append:function(){var t=this,e=t.parseInsertArgs(arguments);return t.insert(t.state.totalTargets,e.command.collection,e.animate,e.callback)},remove:function(){var t=this,e=t.parseRemoveArgs(arguments);return t.multimix({remove:e.command},e.animate,e.callback)},getConfig:function(t){var e=this,a=null;return a=t?n.getProperty(e.config,t):e.config,e.callFilters("valueGetConfig",a,arguments)},configure:function(t){var e=this;e.callActions("beforeConfigure",arguments),n.extend(e.config,t,!0,!0),e.callActions("afterConfigure",arguments)},getState:function(){var t=this,a=null;return a=new e.State,n.extend(a,t.state),n.freeze(a),t.callFilters("stateGetState",a,arguments)},forceRefresh:function(){var t=this;t.indexTargets()},forceRender:function(){var t=this,e=null,n=null,a="";for(a in t.cache)e=t.cache[a],n=e.render(e.data),n!==e.dom.el&&(e.isInDom&&(e.unbindEvents(),t.dom.parent.replaceChild(n,e.dom.el)),e.isShown||(n.style.display="none"),e.dom.el=n,e.isInDom&&e.bindEvents());t.state=t.buildState(t.lastOperation)},destroy:function(t){var n=this,a=null,i=null,o=0;for(n.callActions("beforeDestroy",arguments),o=0;a=n.controls[o];o++)a.removeBinding(n);for(o=0;i=n.targets[o];o++)t&&i.show(),i.unbindEvents();n.dom.container.id.match(/^MixItUp/)&&n.dom.container.removeAttribute("id"),delete e.instances[n.id],n.callActions("afterDestroy",arguments)}}),e.IMoveData=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.posIn=null,this.posOut=null,this.operation=null,this.callback=null,this.statusChange="",this.duration=-1,this.staggerIndex=-1,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.IMoveData),e.IMoveData.prototype=Object.create(e.Base.prototype),e.IMoveData.prototype.constructor=e.IMoveData,e.TargetDom=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.el=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.TargetDom),e.TargetDom.prototype=Object.create(e.Base.prototype),e.TargetDom.prototype.constructor=e.TargetDom,e.Target=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.sortString="",this.mixer=null,this.callback=null,this.isShown=!1,this.isBound=!1,this.isExcluded=!1,this.isInDom=!1,this.handler=null,this.operation=null,this.data=null,this.dom=new e.TargetDom,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.Target),e.Target.prototype=Object.create(e.Base.prototype),n.extend(e.Target.prototype,{constructor:e.Target,init:function(t,n,a){var i=this,o="";if(i.callActions("beforeInit",arguments),i.mixer=n,t||(t=i.render(a)),i.cacheDom(t),i.bindEvents(),"none"!==i.dom.el.style.display&&(i.isShown=!0),a&&n.config.data.uidKey){if("undefined"==typeof(o=a[n.config.data.uidKey])||o.toString().length<1)throw new TypeError(e.messages.errorDatasetInvalidUidKey({uidKey:n.config.data.uidKey}));i.id=o,i.data=a,n.cache[o]=i}i.callActions("afterInit",arguments)},render:function(t){var a=this,i=null,o=null,r=null,s="";if(a.callActions("beforeRender",arguments),i=a.callFilters("renderRender",a.mixer.config.render.target,arguments),"function"!=typeof i)throw new TypeError(e.messages.errorDatasetRendererNotSet());return s=i(t),s&&"object"==typeof s&&n.isElement(s)?o=s:"string"==typeof s&&(r=document.createElement("div"),r.innerHTML=s,o=r.firstElementChild),a.callFilters("elRender",o,arguments)},cacheDom:function(t){var e=this;e.callActions("beforeCacheDom",arguments),e.dom.el=t,e.callActions("afterCacheDom",arguments)},getSortString:function(t){var e=this,n=e.dom.el.getAttribute("data-"+t)||"";e.callActions("beforeGetSortString",arguments),n=isNaN(1*n)?n.toLowerCase():1*n,e.sortString=n,e.callActions("afterGetSortString",arguments)},show:function(){var t=this;t.callActions("beforeShow",arguments),t.isShown||(t.dom.el.style.display="",t.isShown=!0),t.callActions("afterShow",arguments)},hide:function(){var t=this;t.callActions("beforeHide",arguments),t.isShown&&(t.dom.el.style.display="none",t.isShown=!1),t.callActions("afterHide",arguments)},move:function(t){var e=this;e.callActions("beforeMove",arguments),e.isExcluded||e.mixer.targetsMoved++,e.applyStylesIn(t),requestAnimationFrame(function(){e.applyStylesOut(t)}),e.callActions("afterMove",arguments)},applyTween:function(t,n){var a=this,i="",o=null,r=t.posIn,s=[],l=new e.StyleData,c=-1;for(a.callActions("beforeApplyTween",arguments),l.x=r.x,l.y=r.y,0===n?a.hide():a.isShown||a.show(),c=0;i=e.features.TWEENABLE[c];c++)if(o=t.tweenData[i],"x"===i){if(!o)continue;l.x=r.x+o*n}else if("y"===i){if(!o)continue;l.y=r.y+o*n}else if(o instanceof e.TransformData){if(!o.value)continue;l[i].value=r[i].value+o.value*n,l[i].unit=o.unit,s.push(i+"("+l[i].value+o.unit+")")}else{if(!o)continue;l[i]=r[i]+o*n,a.dom.el.style[i]=l[i]}(l.x||l.y)&&s.unshift("translate("+l.x+"px, "+l.y+"px)"),s.length&&(a.dom.el.style[e.features.transformProp]=s.join(" ")),a.callActions("afterApplyTween",arguments)},applyStylesIn:function(t){var n=this,a=t.posIn,i=1!==n.mixer.effectsIn.opacity,o=[];n.callActions("beforeApplyStylesIn",arguments),o.push("translate("+a.x+"px, "+a.y+"px)"),n.mixer.config.animation.animateResizeTargets&&("show"!==t.statusChange&&(n.dom.el.style.width=a.width+"px",n.dom.el.style.height=a.height+"px"),n.dom.el.style.marginRight=a.marginRight+"px",n.dom.el.style.marginBottom=a.marginBottom+"px"),i&&(n.dom.el.style.opacity=a.opacity),"show"===t.statusChange&&(o=o.concat(n.mixer.transformIn)),n.dom.el.style[e.features.transformProp]=o.join(" "),n.callActions("afterApplyStylesIn",arguments)},applyStylesOut:function(t){var n=this,a=[],i=[],o=n.mixer.config.animation.animateResizeTargets,r="undefined"!=typeof n.mixer.effectsIn.opacity;if(n.callActions("beforeApplyStylesOut",arguments),a.push(n.writeTransitionRule(e.features.transformRule,t.staggerIndex)),"none"!==t.statusChange&&a.push(n.writeTransitionRule("opacity",t.staggerIndex,t.duration)),o&&(a.push(n.writeTransitionRule("width",t.staggerIndex,t.duration)),a.push(n.writeTransitionRule("height",t.staggerIndex,t.duration)),a.push(n.writeTransitionRule("margin",t.staggerIndex,t.duration))),!t.callback)return n.mixer.targetsImmovable++,void(n.mixer.targetsMoved===n.mixer.targetsImmovable&&n.mixer.cleanUp(t.operation));switch(n.operation=t.operation,n.callback=t.callback,!n.isExcluded&&n.mixer.targetsBound++,n.isBound=!0,n.applyTransition(a),o&&t.posOut.width>0&&t.posOut.height>0&&(n.dom.el.style.width=t.posOut.width+"px",n.dom.el.style.height=t.posOut.height+"px",n.dom.el.style.marginRight=t.posOut.marginRight+"px",n.dom.el.style.marginBottom=t.posOut.marginBottom+"px"),n.mixer.config.animation.nudge||"hide"!==t.statusChange||i.push("translate("+t.posOut.x+"px, "+t.posOut.y+"px)"),t.statusChange){case"hide":r&&(n.dom.el.style.opacity=n.mixer.effectsOut.opacity),i=i.concat(n.mixer.transformOut);break;case"show":r&&(n.dom.el.style.opacity=1)}(n.mixer.config.animation.nudge||!n.mixer.config.animation.nudge&&"hide"!==t.statusChange)&&i.push("translate("+t.posOut.x+"px, "+t.posOut.y+"px)"),n.dom.el.style[e.features.transformProp]=i.join(" "),n.callActions("afterApplyStylesOut",arguments)},writeTransitionRule:function(t,e,n){var a=this,i=a.getDelay(e),o="";return o=t+" "+(n>0?n:a.mixer.config.animation.duration)+"ms "+i+"ms "+("opacity"===t?"linear":a.mixer.config.animation.easing),a.callFilters("ruleWriteTransitionRule",o,arguments)},getDelay:function(t){var e=this,n=-1;return"function"==typeof e.mixer.config.animation.staggerSequence&&(t=e.mixer.config.animation.staggerSequence.call(e,t,e.state)),n=e.mixer.staggerDuration?t*e.mixer.staggerDuration:0,e.callFilters("delayGetDelay",n,arguments)},applyTransition:function(t){var n=this,a=t.join(", ");n.callActions("beforeApplyTransition",arguments),n.dom.el.style[e.features.transitionProp]=a,n.callActions("afterApplyTransition",arguments)},handleTransitionEnd:function(t){var e=this,n=t.propertyName,a=e.mixer.config.animation.animateResizeTargets;e.callActions("beforeHandleTransitionEnd",arguments),e.isBound&&t.target.matches(e.mixer.config.selectors.target)&&(n.indexOf("transform")>-1||n.indexOf("opacity")>-1||a&&n.indexOf("height")>-1||a&&n.indexOf("width")>-1||a&&n.indexOf("margin")>-1)&&(e.callback.call(e,e.operation),e.isBound=!1,e.callback=null,e.operation=null),e.callActions("afterHandleTransitionEnd",arguments)},eventBus:function(t){var e=this;switch(e.callActions("beforeEventBus",arguments),t.type){case"webkitTransitionEnd":case"transitionend":e.handleTransitionEnd(t)}e.callActions("afterEventBus",arguments)},unbindEvents:function(){var t=this;t.callActions("beforeUnbindEvents",arguments),n.off(t.dom.el,"webkitTransitionEnd",t.handler),n.off(t.dom.el,"transitionend",t.handler),t.callActions("afterUnbindEvents",arguments)},bindEvents:function(){var t=this,a="";t.callActions("beforeBindEvents",arguments),a="webkit"===e.features.transitionPrefix?"webkitTransitionEnd":"transitionend",t.handler=function(e){return t.eventBus(e)},n.on(t.dom.el,a,t.handler),t.callActions("afterBindEvents",arguments)},getPosData:function(n){var a=this,i={},o=null,r=new e.StyleData;return a.callActions("beforeGetPosData",arguments),r.x=a.dom.el.offsetLeft,r.y=a.dom.el.offsetTop,(a.mixer.config.animation.animateResizeTargets||n)&&(o=a.dom.el.getBoundingClientRect(),r.top=o.top,r.right=o.right,r.bottom=o.bottom,r.left=o.left,r.width=o.width,r.height=o.height),a.mixer.config.animation.animateResizeTargets&&(i=t.getComputedStyle(a.dom.el),r.marginBottom=parseFloat(i.marginBottom),r.marginRight=parseFloat(i.marginRight)),a.callFilters("posDataGetPosData",r,arguments)},cleanUp:function(){var t=this;t.callActions("beforeCleanUp",arguments),t.dom.el.style[e.features.transformProp]="",t.dom.el.style[e.features.transitionProp]="",t.dom.el.style.opacity="",t.mixer.config.animation.animateResizeTargets&&(t.dom.el.style.width="",t.dom.el.style.height="",t.dom.el.style.marginRight="",t.dom.el.style.marginBottom=""),t.callActions("afterCleanUp",arguments)}}),e.Collection=function(t){var e=null,a=-1;for(this.callActions("beforeConstruct"),a=0;e=t[a];a++)this[a]=e;this.length=t.length,this.callActions("afterConstruct"),n.freeze(this)},e.BaseStatic.call(e.Collection),e.Collection.prototype=Object.create(e.Base.prototype),n.extend(e.Collection.prototype,{constructor:e.Collection,mixitup:function(t){var a=this,i=null,o=Array.prototype.slice.call(arguments),r=[],s=-1;for(this.callActions("beforeMixitup"),o.shift(),s=0;i=a[s];s++)r.push(i[t].apply(i,o));return a.callFilters("promiseMixitup",n.all(r,e.libraries),arguments)}}),e.Operation=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.args=[],this.command=null,this.showPosData=[],this.toHidePosData=[],this.startState=null,this.newState=null,this.docState=null,this.willSort=!1,this.willChangeLayout=!1,this.hasEffect=!1,this.hasFailed=!1,this.triggerElement=null,this.show=[],this.hide=[],this.matching=[],this.toShow=[],this.toHide=[],this.toMove=[],this.toRemove=[],this.startOrder=[],this.newOrder=[],this.startSort=null,this.newSort=null,this.startFilter=null,this.newFilter=null,this.startDataset=null,this.newDataset=null,this.viewportDeltaX=0,this.viewportDeltaY=0,this.startX=0,this.startY=0,this.startHeight=0,this.startWidth=0,this.newX=0,this.newY=0,this.newHeight=0,this.newWidth=0,this.startContainerClassName="",this.startDisplay="",this.newContainerClassName="",this.newDisplay="",this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.Operation),e.Operation.prototype=Object.create(e.Base.prototype),e.Operation.prototype.constructor=e.Operation,e.State=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.id="",this.activeFilter=null,this.activeSort=null,this.activeContainerClassName="",this.container=null,this.targets=[],this.hide=[],this.show=[],this.matching=[],this.totalTargets=-1,this.totalShow=-1,this.totalHide=-1,this.totalMatching=-1,this.hasFailed=!1,this.triggerElement=null,this.activeDataset=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.State),e.State.prototype=Object.create(e.Base.prototype),e.State.prototype.constructor=e.State,e.UserInstruction=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.command={},this.animate=!1,this.callback=null,this.callActions("afterConstruct"),n.seal(this)},e.BaseStatic.call(e.UserInstruction),e.UserInstruction.prototype=Object.create(e.Base.prototype),e.UserInstruction.prototype.constructor=e.UserInstruction,e.Messages=function(){e.Base.call(this),this.callActions("beforeConstruct"),this.ERROR_FACTORY_INVALID_CONTAINER="[MixItUp] An invalid selector or element reference was passed to the mixitup factory function",this.ERROR_FACTORY_CONTAINER_NOT_FOUND="[MixItUp] The provided selector yielded no container element",this.ERROR_CONFIG_INVALID_ANIMATION_EFFECTS="[MixItUp] Invalid value for `animation.effects`",this.ERROR_CONFIG_INVALID_CONTROLS_SCOPE="[MixItUp] Invalid value for `controls.scope`",this.ERROR_CONFIG_INVALID_PROPERTY='[MixitUp] Invalid configuration object property "${erroneous}"${suggestion}',this.ERROR_CONFIG_INVALID_PROPERTY_SUGGESTION='. Did you mean "${probableMatch}"?',this.ERROR_CONFIG_DATA_UID_KEY_NOT_SET="[MixItUp] To use the dataset API, a UID key must be specified using `data.uidKey`",this.ERROR_DATASET_INVALID_UID_KEY='[MixItUp] The specified UID key "${uidKey}" is not present on one or more dataset items',this.ERROR_DATASET_DUPLICATE_UID='[MixItUp] The UID "${uid}" was found on two or more dataset items. UIDs must be unique.',this.ERROR_INSERT_INVALID_ARGUMENTS="[MixItUp] Please provider either an index or a sibling and position to insert, not both",this.ERROR_INSERT_PREEXISTING_ELEMENT="[MixItUp] An element to be inserted already exists in the container",this.ERROR_FILTER_INVALID_ARGUMENTS="[MixItUp] Please provide either a selector or collection `.filter()`, not both",this.ERROR_DATASET_NOT_SET="[MixItUp] To use the dataset API with pre-rendered targets, a starting dataset must be set using `load.dataset`",this.ERROR_DATASET_PRERENDERED_MISMATCH="[MixItUp] `load.dataset` does not match pre-rendered targets",this.ERROR_DATASET_RENDERER_NOT_SET="[MixItUp] To insert an element via the dataset API, a target renderer function must be provided to `render.target`",this.WARNING_FACTORY_PREEXISTING_INSTANCE="[MixItUp] WARNING: This element already has an active MixItUp instance. The provided configuration object will be ignored. If you wish to perform additional methods on this instance, please create a reference.",this.WARNING_INSERT_NO_ELEMENTS="[MixItUp] WARNING: No valid elements were passed to `.insert()`",this.WARNING_REMOVE_NO_ELEMENTS="[MixItUp] WARNING: No valid elements were passed to `.remove()`",this.WARNING_MULTIMIX_INSTANCE_QUEUE_FULL="[MixItUp] WARNING: An operation was requested but the MixItUp instance was busy. The operation was rejected because the queue is full or queuing is disabled.",this.WARNING_GET_OPERATION_INSTANCE_BUSY="[MixItUp] WARNING: Operations can be be created while the MixItUp instance is busy.",this.WARNING_NO_PROMISE_IMPLEMENTATION="[MixItUp] WARNING: No Promise implementations could be found. If you wish to use promises with MixItUp please install an ES6 Promise polyfill.",this.WARNING_INCONSISTENT_SORTING_ATTRIBUTES='[MixItUp] WARNING: The requested sorting data attribute "${attribute}" was not present on one or more target elements which may product unexpected sort output',this.callActions("afterConstruct"),this.compileTemplates(),n.seal(this)},e.BaseStatic.call(e.Messages),e.Messages.prototype=Object.create(e.Base.prototype),e.Messages.prototype.constructor=e.Messages,e.Messages.prototype.compileTemplates=function(){var t="",e="";for(t in this)"string"==typeof(e=this[t])&&(this[n.camelCase(t)]=n.template(e))},e.messages=new e.Messages,e.Facade=function(t){e.Base.call(this),this.callActions("beforeConstruct",arguments),this.configure=t.configure.bind(t),this.show=t.show.bind(t),this.hide=t.hide.bind(t),this.filter=t.filter.bind(t),this.toggleOn=t.toggleOn.bind(t),this.toggleOff=t.toggleOff.bind(t),this.sort=t.sort.bind(t),this.changeLayout=t.changeLayout.bind(t),this.multimix=t.multimix.bind(t),this.dataset=t.dataset.bind(t),this.tween=t.tween.bind(t),this.insert=t.insert.bind(t),this.insertBefore=t.insertBefore.bind(t),this.insertAfter=t.insertAfter.bind(t),this.prepend=t.prepend.bind(t),this.append=t.append.bind(t),this.remove=t.remove.bind(t),this.destroy=t.destroy.bind(t),this.forceRefresh=t.forceRefresh.bind(t),this.forceRender=t.forceRender.bind(t),this.isMixing=t.isMixing.bind(t),this.getOperation=t.getOperation.bind(t),this.getConfig=t.getConfig.bind(t),this.getState=t.getState.bind(t),this.callActions("afterConstruct",arguments),n.freeze(this),n.seal(this)},e.BaseStatic.call(e.Facade),e.Facade.prototype=Object.create(e.Base.prototype),e.Facade.prototype.constructor=e.Facade,"object"==typeof exports&&"object"==typeof module?module.exports=e:"function"==typeof define&&define.amd?define(function(){return e}):"undefined"!=typeof t.mixitup&&"function"==typeof t.mixitup||(t.mixitup=e),e.BaseStatic.call(e.constructor),e.NAME="mixitup",e.CORE_VERSION="3.2.2"}(window); \ No newline at end of file diff --git a/demos/multiple-pagination-ui/index.html b/demos/multiple-pagination-ui/index.html new file mode 100644 index 0000000..13c1599 --- /dev/null +++ b/demos/multiple-pagination-ui/index.html @@ -0,0 +1,77 @@ + + + + + + + + + MixItUp Pagination Demo - Multiple Pagination UI + + +
+ + + + + + + + +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/demos/multiple-pagination-ui/style.css b/demos/multiple-pagination-ui/style.css new file mode 100644 index 0000000..458dc5b --- /dev/null +++ b/demos/multiple-pagination-ui/style.css @@ -0,0 +1,270 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.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:hover { + background: #3f3f3f; +} + +.control[data-filter]:after { + content: ''; + position: absolute; + 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[data-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(1px) rotate(45deg); +} + +.control[data-sort*=":desc"]:after { + transform: translateY(-4px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.mixitup-control-active[data-filter]:after { + background: transparent; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.control[data-filter] + .control[data-sort] { + margin-left: .75rem; +} + +.control[data-filter=".green"] { + color: #91e6c7; +} + +.control[data-filter=".blue"] { + color: #5ecdde; +} + +.control[data-filter=".pink"] { + color: #d595aa; +} + +.control[data-filter="none"] { + color: #2f2f2f; +} + +/* 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; + border-top: .5rem solid currentColor; + border-radius: 2px; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 56.25%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +/* 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)); + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } +} + + diff --git a/demos/reset.css b/demos/reset.css new file mode 100644 index 0000000..cd114cc --- /dev/null +++ b/demos/reset.css @@ -0,0 +1,72 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +body { + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: antialiased; +} + +blockquote, q { + quotes: none; +} + +a { + text-decoration: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +button { + background: transparent; + border-radius: 0; + border: 0; + padding: 0; + + -webkit-appearance: none; + -webkit-border-radius: 0; + + user-select: none; +} + +button:focus { + outline: 0 none; +} + +button::-moz-focus-inner { + padding: 0; + border: 0; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/demos/responsive-pagination/index.html b/demos/responsive-pagination/index.html new file mode 100644 index 0000000..116541a --- /dev/null +++ b/demos/responsive-pagination/index.html @@ -0,0 +1,168 @@ + + + + + + + + + MixItUp Pagination Demo - Responsive Pagination + + + + +
+ + + + + + + + +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/demos/responsive-pagination/style.css b/demos/responsive-pagination/style.css new file mode 100644 index 0000000..0295969 --- /dev/null +++ b/demos/responsive-pagination/style.css @@ -0,0 +1,295 @@ +html, +body { + height: 100%; + background: #f2f2f2; +} + +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Controls +---------------------------------------------------------------------- */ + +.controls { + padding: 1rem; + background: #333; + font-size: 0.1px; +} + +.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:hover { + background: #3f3f3f; +} + +.control[data-filter]:after { + content: ''; + position: absolute; + 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[data-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(1px) rotate(45deg); +} + +.control[data-sort*=":desc"]:after { + transform: translateY(-4px) rotate(-135deg); +} + +.mixitup-control-active { + background: #393939; +} + +.mixitup-control-active[data-filter]:after { + background: transparent; +} + +.control:first-of-type { + border-radius: 3px 0 0 3px; +} + +.control:last-of-type { + border-radius: 0 3px 3px 0; +} + +.control[data-filter] + .control[data-sort] { + margin-left: .75rem; +} + +.control[data-filter=".green"] { + color: #91e6c7; +} + +.control[data-filter=".blue"] { + color: #5ecdde; +} + +.control[data-filter=".pink"] { + color: #d595aa; +} + +.control[data-filter="none"] { + color: #2f2f2f; +} + +/* 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; + border-top: .5rem solid currentColor; + border-radius: 2px; + margin-bottom: 1rem; + position: relative; +} + +.mix:before { + content: ''; + display: inline-block; + padding-top: 56.25%; +} + +.mix.green { + color: #91e6c7; +} + +.mix.pink { + color: #d595aa; +} + +.mix.blue { + color: #5ecdde; +} + +/* Grid Breakpoints & Column Counts +---------------------------------------------------------------------- */ + +/* 2 Columns */ + +.mix, +.gap { + width: calc(100%/2 - (((2 - 1) * 1rem) / 2)); +} + +/** + * NB: The `font-size` property on the hidden `.column-counter` element is set to + * a value representing the number of columns. We will parse this value on init and + * on resize, which avoids the need to track viewport width and define + * breakpoints in our JavaScript which can also be unreliable when scrollbars + * are added/removed. Any CSS property which supports a numeric value could + * be used instead of font-size. + */ + +.column-counter { + font-size: 2px; +} + +/* 3 Columns */ + +@media screen and (min-width: 541px) { + .mix, + .gap { + width: calc(100%/3 - (((3 - 1) * 1rem) / 3)); + } + + .column-counter { + font-size: 3px; + } +} + +/* 4 Columns */ + +@media screen and (min-width: 961px) { + .mix, + .gap { + width: calc(100%/4 - (((4 - 1) * 1rem) / 4)); + } + + .column-counter { + font-size: 4px; + } +} + +/* 5 Columns */ + +@media screen and (min-width: 1281px) { + .mix, + .gap { + width: calc(100%/5 - (((5 - 1) * 1rem) / 5)); + } + + .column-counter { + font-size: 5px; + } +} + + diff --git a/dist/mixitup-pagination.js b/dist/mixitup-pagination.js new file mode 100644 index 0000000..55ba128 --- /dev/null +++ b/dist/mixitup-pagination.js @@ -0,0 +1,1974 @@ +/**! + * MixItUp Pagination v3.3.0 + * Client-side pagination for filtered and sorted content + * Build 875b7d31-63d1-4040-ac6f-b1c814027891 + * + * Requires mixitup.js >= v^3.1.8 + * + * @copyright Copyright 2014-2017 KunkaLabs Limited. + * @author KunkaLabs Limited. + * @link https://www.kunkalabs.com/mixitup-pagination/ + * + * @license Commercial use requires a commercial license. + * https://www.kunkalabs.com/mixitup-pagination/licenses/ + * + * Non-commercial use permitted under same terms as license. + * http://creativecommons.org/licenses/by-nc/3.0/ + */ + +(function(window) { + 'use strict'; + + var mixitupPagination = function(mixitup) { + var h = mixitup.h; + + if ( + !mixitup.CORE_VERSION || + !h.compareVersions(mixitupPagination.REQUIRE_CORE_VERSION, mixitup.CORE_VERSION) + ) { + throw new Error( + '[MixItUp Pagination] MixItUp Pagination ' + + mixitupPagination.EXTENSION_VERSION + + ' requires at least MixItUp ' + + mixitupPagination.REQUIRE_CORE_VERSION + ); + } + + /** + * A group of optional callback functions to be invoked at various + * points within the lifecycle of a mixer operation. + * + * @constructor + * @memberof mixitup.Config + * @name callbacks + * @namespace + * @public + * @since 2.0.0 + */ + + mixitup.ConfigCallbacks.registerAction('afterConstruct', 'pagination', function() { + /** + * A callback function invoked whenever a pagination operation starts. + * + * This function is equivalent to `onMixStart`, and is invoked immediately + * after it with the same arguments. Unlike `onMixStart` however, it will + * not be invoked for filter or sort operations. + * + * + * @name onPaginateStart + * @memberof mixitup.Config.callbacks + * @instance + * @type {function} + * @default null + */ + + this.onPaginateStart = null; + + /** + * A callback function invoked whenever a pagination operation ends. + * + * This function is equivalent to `onMixEnd`, and is invoked immediately + * after it with the same arguments. Unlike `onMixEnd` however, it will + * not be invoked for filter or sort operations. + * + * @name onPaginateStart + * @memberof mixitup.Config.callbacks + * @instance + * @type {function} + * @default null + */ + + this.onPaginateEnd = null; + }); + + /** + * A group of properties defining the output and structure of class names programmatically + * added to controls and containers to reflect the state of the mixer. + * + * @constructor + * @memberof mixitup.Config + * @name classNames + * @namespace + * @public + * @since 3.0.0 + */ + + mixitup.ConfigClassNames.registerAction('afterConstruct', 'pagination', function() { + + /** + * The "element" portion of the class name added to pager controls. + * + * @example Example: changing the `config.classNames.elementPager` value + * + * // Change from the default value of 'control' to 'pager' + * + * var mixer = mixitup(containerEl, { + * classNames: { + * elementPager: 'pager' + * } + * }); + * + * // Base pager output: "mixitup-pager" + * + * @name elementPager + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'control' + */ + + this.elementPager = 'control'; + + /** + * The "element" portion of the class name added to the page list element, when it is + * in its disabled state. + * + * The page list element is the containing element in which pagers are rendered. + * + * @example Example: changing the `config.classNames.elementPageList` value + * + * // Change from the default value of 'page-list' to 'pagination-links' + * + * var mixer = mixitup(containerEl, { + * classNames: { + * elementPageList: 'pagination-links' + * } + * }); + * + * // Disabled page-list output: "mixitup-pagination-links-disabled" + * + * @name elementPageList + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'page-list' + */ + + this.elementPageList = 'page-list'; + + /** + * The "element" portion of the class name added to the page stats element, when it is + * in its disabled state. + * + * The page stats element is the containing element in which information about the + * current page and total number of pages is rendered. + * + * @example Example: changing the `config.classNames.elementPageStats` value + * + * // Change from the default value of 'page-stats' to 'pagination-info' + * + * var mixer = mixitup(containerEl, { + * classNames: { + * elementPageList: 'pagination-info' + * } + * }); + * + * // Disabled page-list output: "mixitup-pagination-info-disabled" + * + * @name elementPageStats + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'page-stats' + */ + + this.elementPageStats = 'page-stats'; + + /** + * The "modifier" portion of the class name added to the first pager in the list of pager controls. + * + * @name modifierFirst + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'first' + */ + + this.modifierFirst = 'first'; + + /** + * The "modifier" portion of the class name added to the last pager in the list of pager controls. + * + * @name modifierLast + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'last' + */ + + this.modifierLast = 'last'; + + /** + * The "modifier" portion of the class name added to the previous pager in the list of pager controls. + * + * @name modifierLast + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'prev' + */ + + this.modifierPrev = 'prev'; + + /** + * The "modifier" portion of the class name added to the next pager in the list of pager controls. + * + * @name modifierNext + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'next' + */ + + this.modifierNext = 'next'; + + /** + * The "modifier" portion of the class name added to truncation markers in the list of pager controls. + * + * @name modifierTruncationMarker + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'truncation-marker' + */ + + this.modifierTruncationMarker = 'truncation-marker'; + }); + + /** + * A group of properties defining the initial state of the mixer on load (instantiation). + * + * @constructor + * @memberof mixitup.Config + * @name load + * @namespace + * @public + * @since 2.0.0 + */ + + mixitup.ConfigLoad.registerAction('afterConstruct', 'pagination', function() { + /** + * An integer defining the starting page on load, if a page limit is active. + * + * @example Example: Defining a start page other than 1 to be applied on load + * + * // The mixer will show page 3 on load, with 8 items per page. + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8 + * }, + * load: { + * page: 3 + * } + * }); + * + * @name page + * @memberof mixitup.Config.load + * @instance + * @type {number} + * @default 1 + */ + + this.page = 1; + }); + + /** + * A group of properties defining the mixer's pagination behavior. + * + * @constructor + * @memberof mixitup.Config + * @name pagination + * @namespace + * @public + * @since 2.0.0 + */ + + mixitup.ConfigPagination = function() { + + /** + * A boolean dictating whether or not MixItUp should render a list of pager controls. + * + * If you wish to control pagination functionality via the API, or your own UI, this can be set to `false`. + * + * In order for this functionality to work, you must provide MixItUp with a `pageList` + * element matching the selector defined in `selectors.pageList`. Pager controls will be + * rendered inside this element as per the templates defined for the `templates.pager` + * and related configuration options, or if set, a custom render + * function supplied to the `render.pager` configuration option. + * + * @example Example: Disabling the rendering of the built-in "page list" UI + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * generatePageList: false + * } + * }); + * + * @name generatePageList + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default true + */ + + this.generatePageList = true; + + /** + * A boolean dictating whether or not MixItUp should render a stats about the + * current page (e.g. "1 to 4 of 16"). + * + * In order for this functionality to work, you must provide MixItUp with a `pageStats` + * element matching the selector defined in `selectors.pageStats`. Page stats content will + * be rendered inside this element as per the templates defined for the `templates.pageStats` + * and `templates.pageStatsSingle` configuration options, or if set, a custom render + * function supplied to the `render.pageStats` configuration option. + * + * @example Example: Disabling the rendering of the built-in "page stats" UI + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * generatePageStats: false + * } + * }); + * + * @name generatePageStats + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default true + */ + + this.generatePageStats = true; + + /** + * A boolean dictating whether or not to maintain the active page when switching + * from filter to filter. + * + * By default, MixItUp will attempt to maintain the active page or its highest + * equivalent in the new collection of matching targets (e.g. page 3 would become + * page 2 if there are not enough targets in the new collection), but by setting + * this option to `false`, changing the active filter will always cause the mixer + * to revert to page one of the new collection. + * + * @example Example: Ensuring that the mixer reverts to page one when filtered + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * maintainActivePage: false + * } + * }); + * + * @name maintainActivePage + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default true + */ + + this.maintainActivePage = true; + + /** + * A boolean dictating whether or not to allow "looping" of the built-in previous + * and next pagination controls. + * + * By default, when on the first page, the "previous" button will be disabled, + * and when on the last page, the "next" button will be disabled. By setting + * this option to `true`, the user may loop from the first to last page and + * vice-versa. + * + * @example Example: Allowing prev/next controls to "loop" through pages + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * loop: true + * } + * }); + * + * @name loop + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default false + */ + + this.loop = false; + + /** + * A boolean dictating whether or not to prevent rendering of the built-in + * "page list" UI if the matching collection of targets has only enough content + * for one page. + * + * @example Example: Hiding the page list UI if only one page + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * hidePageListIfSinglePage: true + * } + * }); + * + * @name hidePageListIfSinglePage + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default false + */ + + this.hidePageListIfSinglePage = false; + + /** + * A boolean dictating whether or not to prevent rendering of the built-in + * "page stats" UI if the matching collection of targets has only enough content + * for one page. + * + * @example Example: Hiding the page stats UI if only one page + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * hidePageStatsIfSinglePage: true + * } + * }); + * + * @name hidePageStatsIfSinglePage + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default false + */ + + this.hidePageStatsIfSinglePage = false; + + /** + * A number defining the maximum number of items per page. + * + * By default, this is set to `-1` and pagination is effectively + * disabled. By setting this to any number greater than 0, pagination + * will be applied to the mixers targets, effectively activating the + * extension. + * + * @example Example: Activating the pagination extension by defining a valid limit + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8 + * } + * }); + * + * @name limit + * @memberof mixitup.Config.pagination + * @instance + * @type {number} + * @default -1 + */ + + this.limit = -1; + + /** + * A number dictating the maximum number of individual pager controls to render before + * truncating the list (e.g. adding an ellipses between non-consecutive pagers). + * + * The minimum value permitted for this option is 5, which ensures + * there will always be at least a first, last, and two padding pagers, in addition + * to the pager representing the currently active page. + * + * @name maxPagers + * @memberof mixitup.Config.pagination + * @instance + * @type {number} + * @default 5 + */ + + this.maxPagers = 5; + + h.seal(this); + }; + + /** + * A group of optional render functions for creating and updating elements. + * + * @constructor + * @memberof mixitup.Config + * @name render + * @namespace + * @public + * @since 3.0.0 + */ + + mixitup.ConfigRender.registerAction('afterConstruct', 'pagination', function() { + /** + * A function returning an HTML string representing a single pager control element. + * + * By default, MixItUp will render pager controls using its own internal renderer + * and templates (see `templates.pager`), but you may override this functionality by + * providing your own render function here instead. All pager elements must have a + * data-page element indicating the action of the control. + * + * The function receives an object containing all neccessary information + * about the pager as its first parameter. + * + * @name pager + * @memberof mixitup.Config.render + * @instance + * @type {function} + * @default 'null' + */ + + this.pager = null; + + /** + * A function returning an HTML string forming the contents of the "page stats" element. + * + * By default, MixItUp will render page stats using its own internal renderer + * and templates (see `templates.pageStats`), but you may override this functionality by + * providing your own render function here instead. + * + * The function receives an object containing all neccessary information + * about the current page and total pages as its first parameter. + * + * @name pageStats + * @memberof mixitup.Config.render + * @instance + * @type {function} + * @default 'null' + */ + + this.pageStats = null; + }); + + /** + * A group of properties defining the selectors used to query elements within a mixitup container. + * + * @constructor + * @memberof mixitup.Config + * @name selectors + * @namespace + * @public + * @since 2.0.0 + */ + + mixitup.ConfigSelectors.registerAction('afterConstruct', 'pagination', function() { + /** + * A selector string used to query the page list element. + * + * Depending on the value of `controls.scope`, MixItUp will either query the + * entire document for the page list element, or just the container. + * + * @name pageList + * @memberof mixitup.Config.selectors + * @instance + * @type {string} + * @default '.mixitup-page-list' + */ + + this.pageList = '.mixitup-page-list'; + + /** + * A selector string used to query the page stats element. + * + * Depending on the value of `controls.scope`, MixItUp will either query the + * entire document for the page stats element, or just the container. + * + * @name pageStats + * @memberof mixitup.Config.selectors + * @instance + * @type {string} + * @default '.mixitup-page-stats' + */ + + this.pageStats = '.mixitup-page-stats'; + }); + + /** + * A group of template strings used to render pager controls and page stats elements. + * + * @constructor + * @memberof mixitup.Config + * @name templates + * @namespace + * @public + * @since 3.0.0 + */ + + mixitup.ConfigTemplates.registerAction('afterConstruct', 'pagination', function() { + /** + * @name pager + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '' + */ + + this.pager = ''; + + /** + * @name pagerPrev + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '' + */ + + this.pagerPrev = ''; + + /** + * @name pagerNext + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '' + */ + + this.pagerNext = ''; + + /** + * @name pagerTruncationMarker + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '' + */ + + this.pagerTruncationMarker = ''; + + /** + * @name pageStats + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '${startPageAt} to ${endPageAt} of ${totalTargets}' + */ + + this.pageStats = '${startPageAt} to ${endPageAt} of ${totalTargets}'; + + /** + * @name pageStatsSingle + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '${startPageAt} of ${totalTargets}' + */ + + this.pageStatsSingle = '${startPageAt} of ${totalTargets}'; + + /** + * @name pageStatsFail + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default 'None found' + */ + + this.pageStatsFail = 'None found'; + }); + + /** + * The MixItUp configuration object is extended with the following properties + * relating to the Pagination 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', 'pagination', function() { + this.pagination = new mixitup.ConfigPagination(); + }); + + mixitup.ModelPager = function() { + this.pageNumber = -1; + this.classNames = ''; + this.classList = []; + this.isDisabled = false; + this.isPrev = false; + this.isNext = false; + this.isPageLink = false; + this.isTruncationMarker = false; + + h.seal(this); + }; + + mixitup.ModelPageStats = function() { + this.startPageAt = -1; + this.endPageAt = -1; + this.totalTargets = -1; + + h.seal(this); + }; + + mixitup.UiClassNames.registerAction('afterConstruct', 'pagination', function() { + this.first = ''; + this.last = ''; + this.prev = ''; + this.next = ''; + this.first = ''; + this.last = ''; + this.truncated = ''; + this.truncationMarker = ''; + }); + + mixitup.controlDefinitions.push(new mixitup.ControlDefinition('pager', '[data-page]', true, 'pageListEls')); + + /** + * @param {mixitup.MultimixCommand[]} commands + * @param {ClickEvent} e + * @return {object|null} + */ + + mixitup.Control.registerFilter('commandsHandleClick', 'pagination', function(commands, e) { + var self = this, + command = {}, + page = '', + pageNumber = -1, + mixer = null, + button = null, + i = -1; + + if (!self.selector || self.selector !== '[data-page]') { + // Static control or non-pager live control + + return commands; + } + + button = h.closestParent(e.target, self.selector, true, self.bound[0].dom.document); + + for (i = 0; mixer = self.bound[i]; i++) { + command = commands[i]; + + if (!mixer.config.pagination || mixer.config.pagination.limit < 0 || mixer.config.pagination.limit === Infinity) { + // Pagination is disabled for this instance. Do not handle. + + commands[i] = null; + + continue; + } + + if (!button || h.hasClass(button, mixer.classNamesPager.active) || h.hasClass(button, mixer.classNamesPager.disabled)) { + // No button was clicked or button is already active. Do not handle. + + commands[i] = null; + + continue; + } + + page = button.getAttribute('data-page'); + + if (page === 'prev') { + command.paginate = 'prev'; + } else if (page === 'next') { + command.paginate = 'next'; + } else if (pageNumber) { + command.paginate = parseInt(page); + } + + if (mixer.lastClicked) { + mixer.lastClicked = button; + } + } + + return commands; + }); + + mixitup.CommandMultimix.registerAction('afterConstruct', 'pagination', function() { + this.paginate = null; + }); + + /** + * @constructor + * @memberof mixitup + * @private + * @since 3.0.0 + */ + + mixitup.CommandPaginate = function() { + this.page = -1; + this.limit = -1; + this.action = ''; // enum: ['prev', 'next'] + this.anchor = null; + + h.seal(this); + }; + + mixitup.Events.registerAction('afterConstruct', 'pagination', function() { + /** + * A custom event triggered whenever a pagination operation starts. + * + * @name paginateStart + * @memberof mixitup.Events + * @static + * @type {CustomEvent} + */ + + this.paginateStart = null; + + /** + * A custom event triggered whenever a pagination operation ends. + * + * @name paginateEnd + * @memberof mixitup.Events + * @static + * @type {CustomEvent} + */ + + this.paginateEnd = null; + }); + + mixitup.events = new mixitup.Events(); + + mixitup.Operation.registerAction('afterConstruct', 'pagination', function() { + this.startPagination = null; + this.newPagination = null; + this.startTotalPages = -1; + this.newTotalPages = -1; + }); + + /** + * `mixitup.State` objects expose various pieces of data detailing the state of + * a MixItUp instance. They are provided at the start and end of any operation via + * callbacks and events, with the most recent state stored between operations + * for retrieval at any time via the API. + * + * @constructor + * @namespace + * @name State + * @memberof mixitup + * @public + * @since 3.0.0 + */ + + mixitup.State.registerAction('afterConstruct', 'pagination', function() { + + /** + * The currently active pagination command as set by a control click or API call. + * + * @name activePagination + * @memberof mixitup.State + * @instance + * @type {mixitup.CommandPagination} + * @default null + */ + + this.activePagination = null; + + /** + * The total number of pages produced as a combination of the current page + * limit and active filter. + * + * @name totalPages + * @memberof mixitup.State + * @instance + * @type {number} + * @default -1 + */ + + this.totalPages = -1; + }); + + mixitup.MixerDom.registerAction('afterConstruct', 'pagination', function() { + this.pageListEls = []; + this.pageStatsEls = []; + }); + + /** + * The mixitup.Mixer class is extended with the following methods relating to + * the Pagination extension. + * + * For the full list of methods, please refer to the MixItUp core documentation. + * + * @constructor + * @namespace + * @name Mixer + * @memberof mixitup + * @public + * @since 3.0.0 + */ + + mixitup.Mixer.registerAction('afterConstruct', 'pagination', function() { + this.classNamesPager = new mixitup.UiClassNames(); + this.classNamesPageList = new mixitup.UiClassNames(); + this.classNamesPageStats = new mixitup.UiClassNames(); + }); + + /** + * @private + * @return {void} + */ + + mixitup.Mixer.registerAction('afterAttach', 'pagination', function() { + var self = this, + el = null, + i = -1; + + if (self.config.pagination.limit < 0) return; + + // Map pagination ui classNames + + // jscs:disable + self.classNamesPager.base = h.getClassname(self.config.classNames, 'pager'); + self.classNamesPager.active = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierActive); + self.classNamesPager.disabled = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierDisabled); + self.classNamesPager.first = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierFirst); + self.classNamesPager.last = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierLast); + self.classNamesPager.prev = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierPrev); + self.classNamesPager.next = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierNext); + self.classNamesPager.truncationMarker = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierTruncationMarker); + + self.classNamesPageList.base = h.getClassname(self.config.classNames, 'page-list'); + self.classNamesPageList.disabled = h.getClassname(self.config.classNames, 'page-list', self.config.classNames.modifierDisabled); + + self.classNamesPageStats.base = h.getClassname(self.config.classNames, 'page-stats'); + self.classNamesPageStats.disabled = h.getClassname(self.config.classNames, 'page-stats', self.config.classNames.modifierDisabled); + // jscs:enable + + if (self.config.pagination.generatePageList && self.dom.pageListEls.length > 0) { + for (i = 0; (el = self.dom.pageListEls[i]); i++) { + self.renderPageListEl(el, self.lastOperation); + } + } + + if (self.config.pagination.generatePageStats && self.dom.pageStatsEls.length > 0) { + for (i = 0; (el = self.dom.pageStatsEls[i]); i++) { + self.renderPageStatsEl(el, self.lastOperation); + } + } + }); + + mixitup.Mixer.registerAction('afterSanitizeConfig', 'pagination', function() { + var self = this, + onMixStart = self.config.callbacks.onMixStart, + onMixEnd = self.config.callbacks.onMixEnd, + onPaginateStart = self.config.callbacks.onPaginateStart, + onPaginateEnd = self.config.callbacks.onPaginateEnd, + didPaginate = false; + + if (self.config.pagination.limit < 0) return; + + self.classNamesPager = new mixitup.UiClassNames(); + self.classNamesPageList = new mixitup.UiClassNames(); + self.classNamesPageStats = new mixitup.UiClassNames(); + + self.config.callbacks.onMixStart = function(prevState, nextState) { + if ( + prevState.activePagination.limit !== nextState.activePagination.limit || + prevState.activePagination.page !== nextState.activePagination.page + ) { + didPaginate = true; + } + + if (typeof onMixStart === 'function') onMixStart.apply(self.dom.container, arguments); + + if (!didPaginate) return; + + mixitup.events.fire('paginateStart', self.dom.container, { + state: prevState, + futureState: nextState, + instance: self + }, self.dom.document); + + if (typeof onPaginateStart === 'function') onPaginateStart.apply(self.dom.container, arguments); + }; + + self.config.callbacks.onMixEnd = function(state) { + if (typeof onMixEnd === 'function') onMixEnd.apply(self.dom.container, arguments); + + if (!didPaginate) return; + + didPaginate = false; + + mixitup.events.fire('paginateEnd', self.dom.container, { + state: state, + instance: self + }, self.dom.document); + + if (typeof onPaginateEnd === 'function') onPaginateEnd.apply(self.dom.container, arguments); + }; + }); + + /** + * @private + * @param {mixitup.Operation} operation + * @param {mixitup.State} state + * @return {mixitup.Operation} + */ + + mixitup.Mixer.registerFilter('operationGetInitialState', 'pagination', function(operation, state) { + var self = this; + + if (self.config.pagination.limit < 0) return operation; + + operation.newPagination = state.activePagination; + + return operation; + }); + + /** + * @private + * @param {mixitup.State} state + * @return {mixitup.State} + */ + + mixitup.Mixer.registerFilter('stateGetInitialState', 'pagination', function(state) { + var self = this; + + if (self.config.pagination.limit < 0) return state; + + state.activePagination = new mixitup.CommandPaginate(); + + state.activePagination.page = self.config.load.page; + state.activePagination.limit = self.config.pagination.limit; + + return state; + }); + + /** + * @private + * @return {void} + */ + + mixitup.Mixer.registerAction('afterGetFinalMixData', 'pagination', function() { + var self = this; + + if (self.config.pagination.limit < 0) return; + + if (typeof self.config.pagination.maxPagers === 'number') { + // Restrict max pagers to a minimum of 5. There must always + // be a first, last, and one on either side of the active pager. + // e.g. « 1 ... 4 5 6 ... 10 » + + self.config.pagination.maxPagers = Math.max(5, self.config.pagination.maxPagers); + } + }); + + /** + * @private + * @return {void} + */ + + mixitup.Mixer.registerAction('afterCacheDom', 'pagination', function() { + var self = this, + parent = null; + + if (self.config.pagination.limit < 0) return; + + if (!self.config.pagination.generatePageList) 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.pageListEls = parent.querySelectorAll(self.config.selectors.pageList); + self.dom.pageStatsEls = parent.querySelectorAll(self.config.selectors.pageStats); + }); + + /** + * @private + * @param {mixitup.State} state + * @param {mixitup.Operation} operation + * @return {mixitup.State} + */ + + mixitup.Mixer.registerFilter('stateBuildState', 'pagination', function(state, operation) { + var self = this; + + if (self.config.pagination.limit < 0) return state; + + // Map pagination-specific properties into state + + state.activePagination = operation.newPagination; + state.totalPages = operation.newTotalPages; + + return state; + }); + + /** + * @private + * @param {mixitup.UserInstruction} instruction + * @return {mixitup.UserInstruction} + */ + + mixitup.Mixer.registerFilter('instructionParseMultimixArgs', 'pagination', function(instruction) { + var self = this; + + if (self.config.pagination.limit < 0) return instruction; + + if (instruction.command.paginate && !(instruction.command.paginate instanceof mixitup.CommandPaginate)) { + instruction.command.paginate = self.parsePaginateArgs([instruction.command.paginate]).command; + } + + return instruction; + }); + + /** + * @private + * @param {mixitup.Operation} operation + * @return {void} + */ + + mixitup.Mixer.registerAction('afterFilterOperation', 'pagination', function(operation) { + var self = this, + startPageAt = -1, + endPageAt = -1, + inPage = [], + notInPage = [], + target = null, + index = -1, + i = -1; + + if (self.config.pagination.limit < 0) return; + + // Calculate the new total pages as a matter of course (i.e. a change in filter) + + // New matching array has already been set at this point + + operation.newTotalPages = operation.newPagination.limit ? + Math.max(Math.ceil(operation.matching.length / operation.newPagination.limit), 1) : + 1; + + if (self.config.pagination.maintainActivePage) { + operation.newPagination.page = (operation.newPagination.page > operation.newTotalPages) ? + operation.newTotalPages : + operation.newPagination.page; + } + + // Keep config in sync with latest limit + + self.config.pagination.limit = operation.newPagination.limit; + + if (operation.newPagination.anchor) { + // Start page at an anchor element + + for (i = 0; target = operation.matching[i]; i++) { + if (target.dom.el === operation.newPagination.anchor) break; + } + + startPageAt = i; + endPageAt = i + operation.newPagination.limit - 1; + } else { + // Start page based on limit and page index + + startPageAt = operation.newPagination.limit * (operation.newPagination.page - 1); + endPageAt = (operation.newPagination.limit * operation.newPagination.page) - 1; + + if (isNaN(startPageAt)) { + startPageAt = 0; + } + } + + if (operation.newPagination.limit < 0) return; + + for (i = 0; target = operation.show[i]; i++) { + // For each target in `show`, include in page, only if within the range + + if (i >= startPageAt && i <= endPageAt) { + inPage.push(target); + } else { + // Else move to `notInPage` + + notInPage.push(target); + } + } + + // override the operation's `show` array with the newly constructed `inPage` array + + operation.show = inPage; + + // For anything not in the page, make sure it is correctly assigned: + + for (i = 0; target = operation.toHide[i]; i++) { + // For example, if a target would normally be included in `toHide`, but is + // now already hidden as not in the page, make sure it is removed from `toHide` + // so it is not included in the operation. + + if (!target.isShown) { + operation.toHide.splice(i, 1); + + target.isShown = false; + + i--; + } + } + + for (i = 0; target = notInPage[i]; i++) { + // For each target not in page, move into `hide` + + operation.hide.push(target); + + if ((index = operation.toShow.indexOf(target)) > -1) { + // Any targets due to be shown will no longer be shown + + operation.toShow.splice(index, 1); + } + + if (target.isShown) { + // If currently shown, move to `toHide` + + operation.toHide.push(target); + } + } + }); + + /** + * @private + * @param {mixitup.Operation} operation + * @param {mixitup.CommandMultimix} command + * @return {mixitup.Operation} + */ + + mixitup.Mixer.registerFilter('operationUnmappedGetOperation', 'pagination', function(operation, command) { + var self = this; + + if (self.config.pagination.limit < 0) return operation; + + operation.startState = self.state; + operation.startPagination = self.state.activePagination; + operation.startTotalPages = self.state.totalPages; + + operation.newPagination = new mixitup.CommandPaginate(); + + operation.newPagination.limit = operation.startPagination.limit; + operation.newPagination.page = operation.startPagination.page; + + if (command.paginate) { + self.parsePaginateCommand(command.paginate, operation); + } else if (command.filter || command.sort) { + h.extend(operation.newPagination, operation.startPagination); + + // Reset to 1, or maintain active: + + if (!self.config.pagination.maintainActivePage) { + operation.newPagination.page = 1; + } else { + operation.newPagination.page = self.state.activePagination.page; + } + } + + return operation; + }); + + /** + * @private + * @param {mixitup.Operation} operation + * @param {object} command + * @param {boolean} [isPreFetch=false] + * @return {mixitup.Operation} + */ + + mixitup.Mixer.registerFilter('operationMappedGetOperation', 'pagination', function(operation, command, isPreFetch) { + var self = this, + el = null, + i = -1; + + if (self.config.pagination.limit < 0) return operation; + + if (isPreFetch) { + // The operation is being pre-fetched, so don't update the pagers or stats yet. + + return operation; + } + + if (self.config.pagination.generatePageList && self.dom.pageListEls.length > 0) { + for (i = 0; (el = self.dom.pageListEls[i]); i++) { + self.renderPageListEl(el, operation); + } + } + + if (self.config.pagination.generatePageStats && self.dom.pageStatsEls.length > 0) { + for (i = 0; (el = self.dom.pageStatsEls[i]); i++) { + self.renderPageStatsEl(el, operation); + } + } + + return operation; + }); + + mixitup.Mixer.extend( + /** @lends mixitup.Mixer */ + { + /** + * @private + * @param {mixitup.CommandPaginate} command + * @param {mixitup.Operation} operation + * @return {void} + */ + + parsePaginateCommand: function(command, operation) { + var self = this; + + // e.g. mixer.paginate({page: 3, limit: 2}); + // e.g. mixer.paginate({action: 'next'}); + // e.g. mixer.paginate({anchor: anchorTarget, limit: 5}); + + if (command.page > -1) { + if (command.page === 0) throw new Error(mixitup.messages.ERROR_PAGINATE_INDEX_RANGE); + + // TODO: replace Infinity with the highest possible page index + + operation.newPagination.page = Math.max(1, Math.min(Infinity, command.page)); + } else if (command.action === 'next') { + operation.newPagination.page = self.getNextPage(); + } else if (command.action === 'prev') { + operation.newPagination.page = self.getPrevPage(); + } else if (command.anchor) { + operation.newPagination.anchor = command.anchor; + } + + if (command.limit > -1) { + operation.newPagination.limit = command.limit; + } + + if (operation.newPagination.limit !== operation.startPagination.limit) { + // A new limit has been sent via the API, calculate total pages + + operation.newTotalPages = operation.newPagination.limit ? + Math.max(Math.ceil(operation.startState.matching.length / operation.newPagination.limit), 1) : + 1; + } + + if (operation.newPagination.limit <= 0 || operation.newPagination.limit === Infinity) { + operation.newPagination.page = 1; + } + }, + + /** + * @private + * @return {number} page + */ + + getNextPage: function() { + var self = this, + page = -1; + + page = self.state.activePagination.page + 1; + + if (page > self.state.totalPages) { + page = self.config.pagination.loop ? 1 : self.state.activePagination.page; + } + + return page; + }, + + /** + * @private + * @return {Number} page + */ + + getPrevPage: function() { + var self = this, + page = -1; + + page = self.state.activePagination.page - 1; + + if (page < 1) { + page = self.config.pagination.loop ? self.state.totalPages : self.state.activePagination.page; + } + + return page; + }, + + /** + * @private + * @param {HTMLElement} pageListEl + * @param {mixitup.Operation} operation + * @return {void} + */ + + renderPageListEl: function(pageListEl, operation) { + var self = this, + activeIndex = -1, + pagerHtml = '', + buttonList = [], + model = null, + renderer = null, + allowedIndices = [], + truncatedBefore = false, + truncatedAfter = false, + disabled = null, + el = null, + html = '', + i = -1; + + if ( + operation.newPagination.limit < 0 || + operation.newPagination.limit === Infinity || + (operation.newTotalPages < 2 && self.config.pagination.hidePageListIfSinglePage) + ) { + // Empty the pager list, and add disabled class + + pageListEl.innerHTML = ''; + + h.addClass(pageListEl, self.classNamesPageList.disabled); + + return; + } + + activeIndex = operation.newPagination.page - 1; + + renderer = typeof (renderer = self.config.render.pager) === 'function' ? renderer : null; + + if (self.config.pagination.maxPagers < Infinity && operation.newTotalPages > self.config.pagination.maxPagers) { + allowedIndices = self.getAllowedIndices(operation); + } + + // Render prev button + + model = new mixitup.ModelPager(); + + model.isPrev = true; + model.classList.push(self.classNamesPager.base, self.classNamesPager.prev); + + // If first and not looping, disable the prev button + + if (operation.newPagination.page === 1 && !self.config.pagination.loop) { + model.classList.push(self.classNamesPager.disabled); + + model.isDisabled = true; + } + + model.classNames = model.classList.join(' '); + + if (renderer) { + pagerHtml = renderer(model); + } else { + pagerHtml = h.template(self.config.templates.pagerPrev)(model); + } + + buttonList.push(pagerHtml); + + // Render per-page pagers + + for (i = 0; i < operation.newTotalPages; i++) { + pagerHtml = self.renderPager(i, operation, allowedIndices); + + if (pagerHtml || (i < activeIndex && truncatedBefore) || i > activeIndex && truncatedAfter) { + if (pagerHtml) { + buttonList.push(pagerHtml); + } + + continue; + } + + // Replace gaps between pagers with a truncation maker, but only once + + model = new mixitup.ModelPager(); + + model.isTruncationMarker = true; + + model.classList.push(self.classNamesPager.base, self.classNamesPager.truncationMarker); + model.classNames = model.classList.join(' '); + + if (renderer) { + pagerHtml = renderer(model); + } else { + pagerHtml = h.template(self.config.templates.pagerTruncationMarker)(model); + } + + buttonList.push(pagerHtml); + + // Prevent multiple truncation markers + + if (i < activeIndex) { + truncatedBefore = true; + } + + if (i > activeIndex) { + truncatedAfter = true; + } + } + + // Render next button + + model = new mixitup.ModelPager(); + + model.isNext = true; + model.classList.push(self.classNamesPager.base, self.classNamesPager.next); + + // If last page and not looping, disable the next button + + if (operation.newPagination.page === operation.newTotalPages && !self.config.pagination.loop) { + model.classList.push(self.classNamesPager.disabled); + } + + model.classNames = model.classList.join(' '); + + if (renderer) { + pagerHtml = renderer(model); + } else { + pagerHtml = h.template(self.config.templates.pagerNext)(model); + } + + buttonList.push(pagerHtml); + + // Replace markup + + html = buttonList.join(' '); + + pageListEl.innerHTML = html; + + // Add disabled attribute to disabled buttons + + disabled = pageListEl.querySelectorAll('.' + self.classNamesPager.disabled); + + for (i = 0; el = disabled[i]; i++) { + if (typeof el.disabled === 'boolean') { + el.disabled = true; + } + } + + if (truncatedBefore || truncatedAfter) { + h.addClass(pageListEl, self.classNamesPageList.truncated); + } else { + h.removeClass(pageListEl, self.classNamesPageList.truncated); + } + + if (operation.newTotalPages > 1) { + h.removeClass(pageListEl, self.classNamesPageList.disabled); + } else { + h.addClass(pageListEl, self.classNamesPageList.disabled); + } + }, + + /** + * An algorithm defining which pagers should be rendered based on their index + * and the current active page, when a `pagination.maxPagers` value is applied. + * + * @private + * @param {mixitup.Operation} operation + * @return {number[]} + */ + + getAllowedIndices: function(operation) { + var self = this, + activeIndex = operation.newPagination.page - 1, + lastIndex = operation.newTotalPages - 1, + indices = [], + paddingRange = -1, + paddingBack = -1, + paddingFront = -1, + paddingRangeStart = -1, + paddingRangeEnd = -1, + paddingRangeOffset = -1, + i = -1; + + // Examples: + + // « 1 2 *3* 4 5 » maxPagers = 5 + // « 1 ... 4 *5* 6 ... 10 » maxPagers = 5 + + // « 1 ... 6 7 *8* 9 10 » maxPagers = 6 + // « 1 ... 3 4 *5* 6 ... 10 » maxPagers = 6 + + // « 1 ... 3 4 *5* 6 7 ... 10 » maxPagers = 7 + // « *1* 2 3 4 5 6 ... 10 » maxPagers = 7 + + // This algorithm ensures that at any time, the active pager + // should be surrounded by as many "padding" pagers as possible to equal the + // value of `pagination.maxPagers`, accounting for the fact the first and last + // pager should also always be rendered. + + // Push in index 0 to represent the first pager + + indices.push(0); + + // Calculate the "padding range" by subtracting 2 from `pagination.maxPagers` + + paddingRange = self.config.pagination.maxPagers - 2; + + // Distribute the padding equally behind and in front of the active pager. + // If the padding range is an even number, we allow an extra pager behind the active pager. + + paddingBack = Math.ceil((paddingRange - 1) / 2); + paddingFront = Math.floor((paddingRange - 1) / 2); + + // Calculate where the range should start and finish based on the active index + + paddingRangeStart = activeIndex - paddingBack; + paddingRangeEnd = activeIndex + paddingFront; + + // Set the offset to 0 + + paddingRangeOffset = 0; + + // If the start of the range has collided with the first pager, positively offset as needed + + if (paddingRangeStart < 1) { + paddingRangeOffset = 1 - paddingRangeStart; + } + + // If the end of the range has collided with the last pager, negatively offset as needed + + if (paddingRangeEnd > lastIndex - 1) { + paddingRangeOffset = (lastIndex - 1) - paddingRangeEnd; + } + + // Calcuate the first index of the range taking into account any offset + + i = paddingRangeStart + paddingRangeOffset; + + // Iteratate through the range, adding the respective indices: + + while (paddingRange) { + indices.push(i); + + i++; + paddingRange--; + } + + indices.push(lastIndex); + + return indices; + }, + + /** + * Renderes individual, per-page pagers. + * + * @private + * @param {number} i + * @param {mixitup.Operation} operation + * @param {number[]} [allowedIndices] + * @return {string} + */ + + renderPager: function(i, operation, allowedIndices) { + var self = this, + renderer = null, + activePage = operation.newPagination.page - 1, + model = new mixitup.ModelPager(), + output = ''; + + if ( + self.config.pagination.maxPagers < Infinity && + allowedIndices.length && + allowedIndices.indexOf(i) < 0 + ) { + // maxPagers is set, and this pager is not in the allowed range + + return ''; + } + + renderer = typeof (renderer = self.config.render.pager) === 'function' ? renderer : null; + + model.isPageLink = true; + + model.classList.push(self.classNamesPager.base); + + if (i === 0) { + model.classList.push(self.classNamesPager.first); + } + + if (i === operation.newTotalPages - 1) { + model.classList.push(self.classNamesPager.last); + } + + if (i === activePage) { + model.classList.push(self.classNamesPager.active); + } + + model.classNames = model.classList.join(' '); + model.pageNumber = i + 1; + + if (renderer) { + output = renderer(model); + } else { + output = h.template(self.config.templates.pager)(model); + } + + return output; + }, + + /** + * @private + * @param {HTMLElement} pageStatsEl + * @param {mixitup.Operation} operation + * @return {void} + */ + + renderPageStatsEl: function(pageStatsEl, operation) { + var self = this, + model = new mixitup.ModelPageStats(), + renderer = null, + output = '', + template = ''; + + if ( + operation.newPagination.limit < 0 || + operation.newPagination.limit === Infinity || + (operation.newTotalPages < 2 && self.config.pagination.hidePageStatsIfSinglePage) + ) { + // Empty the pager list, and add disabled class + + pageStatsEl.innerHTML = ''; + + h.addClass(pageStatsEl, self.classNamesPageStats.disabled); + + return; + } + + renderer = typeof (renderer = self.config.render.pageStats) === 'function' ? renderer : null; + + model.totalTargets = operation.matching.length; + + if (model.totalTargets) { + template = operation.newPagination.limit === 1 ? + self.config.templates.pageStatsSingle : + self.config.templates.pageStats; + } else { + template = self.config.templates.pageStatsFail; + } + + if (model.totalTargets && operation.newPagination.limit > 0) { + model.startPageAt = ((operation.newPagination.page - 1) * operation.newPagination.limit) + 1; + model.endPageAt = Math.min(model.startPageAt + operation.newPagination.limit - 1, model.totalTargets); + } else { + model.startPageAt = model.endPageAt = 0; + } + + if (renderer) { + output = renderer(model); + } else { + output = h.template(template)(model); + } + + pageStatsEl.innerHTML = output; + + if (model.totalTargets) { + h.removeClass(pageStatsEl, self.classNamesPageStats.disabled); + } else { + h.addClass(pageStatsEl, self.classNamesPageStats.disabled); + } + }, + + /** + * @private + * @param {Array<*>} args + * @return {mixitup.UserInstruction} instruction + */ + + parsePaginateArgs: function(args) { + var self = this, + instruction = new mixitup.UserInstruction(), + arg = null, + i = -1; + + instruction.animate = self.config.animation.enable; + instruction.command = new mixitup.CommandPaginate(); + + for (i = 0; i < args.length; i++) { + arg = args[i]; + + if (arg === null) continue; + + if (typeof arg === 'object' && h.isElement(arg, self.dom.document)) { + instruction.command.anchor = arg; + } else if (arg instanceof mixitup.CommandPaginate || typeof arg === 'object') { + h.extend(instruction.command, arg); + } else if (typeof arg === 'number') { + instruction.command.page = arg; + } else if (typeof arg === 'string' && !isNaN(parseInt(arg))) { + // e.g. "4" + + instruction.command.page = parseInt(arg); + } else if (typeof arg === 'string') { + instruction.command.action = arg; + } else if (typeof arg === 'boolean') { + instruction.animate = arg; + } else if (typeof arg === 'function') { + instruction.callback = arg; + } + } + + h.freeze(instruction); + + // NB: Don't freeze command as may need to be sanitized later by other methods + + return instruction; + }, + + /** + * Changes the current page and/or the current page limit. + * + * @example + * + * .paginate(page [, animate] [, callback]) + * + * @example Example 1: Changing the active page + * + * console.log(mixer.getState().activePagination.page); // 1 + * + * mixer.paginate(2) + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 2); // true + * }); + * + * @example Example 2: Progressing to the next page + * + * console.log(mixer.getState().activePagination.page); // 1 + * + * mixer.paginate('next') + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 2); // true + * }); + * + * @example Example 3: Starting a page from an abitrary "anchor" element + * + * var anchorEl = mixer.getState().show[3]; + * + * mixer.paginate(anchorEl) + * .then(function(state) { + * console.log(mixer.getState().activePagination.anchor === anchorEl); // true + * console.log(mixer.getState().show[0] === anchorEl); // true + * }); + * + * @example Example 4: Changing the page limit + * + * var anchorEl = mixer.getState().show[3]; + * + * console.log(mixer.getState().activePagination.limit); // 8 + * + * mixer.paginate({ + * limit: 4 + * }) + * .then(function(state) { + * console.log(mixer.getState().activePagination.limit === 4); // true + * }); + * + * @example Example 5: Changing the active page and page limit + * + * mixer.paginate({ + * limit: 4, + * page: 2 + * }) + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 2); // true + * console.log(mixer.getState().activePagination.limit === 4); // true + * }); + * + * @public + * @instance + * @param {(number|string|object|HTMLElement)} page + * A page number, string (`'next'`, `'prev'`), HTML element reference, or command object. + * @param {boolean} [animate=true] + * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. + * @param {function} [callback=null] + * An optional callback function to be invoked after the operation has completed. + * @return {Promise.} + * A promise resolving with the current state object. + */ + + paginate: function() { + var self = this, + instruction = self.parsePaginateArgs(arguments); + + return self.multimix({ + paginate: instruction.command + }, instruction.animate, instruction.callback); + }, + + /** + * A shorthand for `.paginate('next')`. Moves to the next page. + * + * @example + * + * .nextPage() + * + * @example Example: Moving to the next page + * + * console.log(mixer.getState().activePagination.page); // 1 + * + * mixer.nextPage() + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 2); // true + * }); + * + * @public + * @instance + * @return {Promise.} + * A promise resolving with the current state object. + */ + + nextPage: function() { + var self = this, + instruction = self.parsePaginateArgs(arguments); + + return self.multimix({ + paginate: { + action: 'next' + } + }, instruction.animate, instruction.callback); + }, + + /** + * A shorthand for `.paginate('prev')`. Moves to the previous page. + * + * @example + * + * .prevPage() + * + * @example Example: Moving to the previous page + * + * console.log(mixer.getState().activePagination.page); // 5 + * + * mixer.prevPage() + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 4); // true + * }); + * + * @public + * @instance + * @return {Promise.} + * A promise resolving with the current state object. + */ + + prevPage: function() { + var self = this, + instruction = self.parsePaginateArgs(arguments); + + return self.multimix({ + paginate: { + action: 'prev' + } + }, instruction.animate, instruction.callback); + } + }); + + mixitup.Facade.registerAction('afterConstruct', 'pagination', function(mixer) { + this.paginate = mixer.paginate.bind(mixer); + this.nextPage = mixer.nextPage.bind(mixer); + this.prevPage = mixer.prevPage.bind(mixer); + }); }; + + mixitupPagination.TYPE = 'mixitup-extension'; + mixitupPagination.NAME = 'mixitup-pagination'; + mixitupPagination.EXTENSION_VERSION = '3.3.0'; + mixitupPagination.REQUIRE_CORE_VERSION = '^3.1.8'; + + if (typeof exports === 'object' && typeof module === 'object') { + module.exports = mixitupPagination; + } else if (typeof define === 'function' && define.amd) { + define(function() { + return mixitupPagination; + }); + } else if (window.mixitup && typeof window.mixitup === 'function') { + mixitupPagination(window.mixitup); + } else { + throw new Error('[MixItUp Pagination] MixItUp core not found'); + }})(window); \ No newline at end of file diff --git a/dist/mixitup-pagination.min.js b/dist/mixitup-pagination.min.js new file mode 100644 index 0000000..4521439 --- /dev/null +++ b/dist/mixitup-pagination.min.js @@ -0,0 +1,18 @@ +/**! + * MixItUp Pagination v3.3.0 + * Client-side pagination for filtered and sorted content + * Build 875b7d31-63d1-4040-ac6f-b1c814027891 + * + * Requires mixitup.js >= v^3.1.8 + * + * @copyright Copyright 2014-2017 KunkaLabs Limited. + * @author KunkaLabs Limited. + * @link https://www.kunkalabs.com/mixitup-pagination/ + * + * @license Commercial use requires a commercial license. + * https://www.kunkalabs.com/mixitup-pagination/licenses/ + * + * Non-commercial use permitted under same terms as license. + * http://creativecommons.org/licenses/by-nc/3.0/ + */ +!function(a){"use strict";var t=function(a){var i=a.h;if(!a.CORE_VERSION||!i.compareVersions(t.REQUIRE_CORE_VERSION,a.CORE_VERSION))throw new Error("[MixItUp Pagination] MixItUp Pagination "+t.EXTENSION_VERSION+" requires at least MixItUp "+t.REQUIRE_CORE_VERSION);a.ConfigCallbacks.registerAction("afterConstruct","pagination",function(){this.onPaginateStart=null,this.onPaginateEnd=null}),a.ConfigClassNames.registerAction("afterConstruct","pagination",function(){this.elementPager="control",this.elementPageList="page-list",this.elementPageStats="page-stats",this.modifierFirst="first",this.modifierLast="last",this.modifierPrev="prev",this.modifierNext="next",this.modifierTruncationMarker="truncation-marker"}),a.ConfigLoad.registerAction("afterConstruct","pagination",function(){this.page=1}),a.ConfigPagination=function(){this.generatePageList=!0,this.generatePageStats=!0,this.maintainActivePage=!0,this.loop=!1,this.hidePageListIfSinglePage=!1,this.hidePageStatsIfSinglePage=!1,this.limit=-1,this.maxPagers=5,i.seal(this)},a.ConfigRender.registerAction("afterConstruct","pagination",function(){this.pager=null,this.pageStats=null}),a.ConfigSelectors.registerAction("afterConstruct","pagination",function(){this.pageList=".mixitup-page-list",this.pageStats=".mixitup-page-stats"}),a.ConfigTemplates.registerAction("afterConstruct","pagination",function(){this.pager='',this.pagerPrev='',this.pagerNext='',this.pagerTruncationMarker='',this.pageStats="${startPageAt} to ${endPageAt} of ${totalTargets}",this.pageStatsSingle="${startPageAt} of ${totalTargets}",this.pageStatsFail="None found"}),a.Config.registerAction("beforeConstruct","pagination",function(){this.pagination=new a.ConfigPagination}),a.ModelPager=function(){this.pageNumber=-1,this.classNames="",this.classList=[],this.isDisabled=!1,this.isPrev=!1,this.isNext=!1,this.isPageLink=!1,this.isTruncationMarker=!1,i.seal(this)},a.ModelPageStats=function(){this.startPageAt=-1,this.endPageAt=-1,this.totalTargets=-1,i.seal(this)},a.UiClassNames.registerAction("afterConstruct","pagination",function(){this.first="",this.last="",this.prev="",this.next="",this.first="",this.last="",this.truncated="",this.truncationMarker=""}),a.controlDefinitions.push(new a.ControlDefinition("pager","[data-page]",(!0),"pageListEls")),a.Control.registerFilter("commandsHandleClick","pagination",function(a,t){var e=this,n={},s="",o=-1,g=null,r=null,l=-1;if(!e.selector||"[data-page]"!==e.selector)return a;for(r=i.closestParent(t.target,e.selector,!0,e.bound[0].dom.document),l=0;g=e.bound[l];l++)n=a[l],!g.config.pagination||g.config.pagination.limit<0||g.config.pagination.limit===1/0?a[l]=null:!r||i.hasClass(r,g.classNamesPager.active)||i.hasClass(r,g.classNamesPager.disabled)?a[l]=null:(s=r.getAttribute("data-page"),"prev"===s?n.paginate="prev":"next"===s?n.paginate="next":o&&(n.paginate=parseInt(s)),g.lastClicked&&(g.lastClicked=r));return a}),a.CommandMultimix.registerAction("afterConstruct","pagination",function(){this.paginate=null}),a.CommandPaginate=function(){this.page=-1,this.limit=-1,this.action="",this.anchor=null,i.seal(this)},a.Events.registerAction("afterConstruct","pagination",function(){this.paginateStart=null,this.paginateEnd=null}),a.events=new a.Events,a.Operation.registerAction("afterConstruct","pagination",function(){this.startPagination=null,this.newPagination=null,this.startTotalPages=-1,this.newTotalPages=-1}),a.State.registerAction("afterConstruct","pagination",function(){this.activePagination=null,this.totalPages=-1}),a.MixerDom.registerAction("afterConstruct","pagination",function(){this.pageListEls=[],this.pageStatsEls=[]}),a.Mixer.registerAction("afterConstruct","pagination",function(){this.classNamesPager=new a.UiClassNames,this.classNamesPageList=new a.UiClassNames,this.classNamesPageStats=new a.UiClassNames}),a.Mixer.registerAction("afterAttach","pagination",function(){var a=this,t=null,e=-1;if(!(a.config.pagination.limit<0)){if(a.classNamesPager.base=i.getClassname(a.config.classNames,"pager"),a.classNamesPager.active=i.getClassname(a.config.classNames,"pager",a.config.classNames.modifierActive),a.classNamesPager.disabled=i.getClassname(a.config.classNames,"pager",a.config.classNames.modifierDisabled),a.classNamesPager.first=i.getClassname(a.config.classNames,"pager",a.config.classNames.modifierFirst),a.classNamesPager.last=i.getClassname(a.config.classNames,"pager",a.config.classNames.modifierLast),a.classNamesPager.prev=i.getClassname(a.config.classNames,"pager",a.config.classNames.modifierPrev),a.classNamesPager.next=i.getClassname(a.config.classNames,"pager",a.config.classNames.modifierNext),a.classNamesPager.truncationMarker=i.getClassname(a.config.classNames,"pager",a.config.classNames.modifierTruncationMarker),a.classNamesPageList.base=i.getClassname(a.config.classNames,"page-list"),a.classNamesPageList.disabled=i.getClassname(a.config.classNames,"page-list",a.config.classNames.modifierDisabled),a.classNamesPageStats.base=i.getClassname(a.config.classNames,"page-stats"),a.classNamesPageStats.disabled=i.getClassname(a.config.classNames,"page-stats",a.config.classNames.modifierDisabled),a.config.pagination.generatePageList&&a.dom.pageListEls.length>0)for(e=0;t=a.dom.pageListEls[e];e++)a.renderPageListEl(t,a.lastOperation);if(a.config.pagination.generatePageStats&&a.dom.pageStatsEls.length>0)for(e=0;t=a.dom.pageStatsEls[e];e++)a.renderPageStatsEl(t,a.lastOperation)}}),a.Mixer.registerAction("afterSanitizeConfig","pagination",function(){var t=this,i=t.config.callbacks.onMixStart,e=t.config.callbacks.onMixEnd,n=t.config.callbacks.onPaginateStart,s=t.config.callbacks.onPaginateEnd,o=!1;t.config.pagination.limit<0||(t.classNamesPager=new a.UiClassNames,t.classNamesPageList=new a.UiClassNames,t.classNamesPageStats=new a.UiClassNames,t.config.callbacks.onMixStart=function(e,s){e.activePagination.limit===s.activePagination.limit&&e.activePagination.page===s.activePagination.page||(o=!0),"function"==typeof i&&i.apply(t.dom.container,arguments),o&&(a.events.fire("paginateStart",t.dom.container,{state:e,futureState:s,instance:t},t.dom.document),"function"==typeof n&&n.apply(t.dom.container,arguments))},t.config.callbacks.onMixEnd=function(i){"function"==typeof e&&e.apply(t.dom.container,arguments),o&&(o=!1,a.events.fire("paginateEnd",t.dom.container,{state:i,instance:t},t.dom.document),"function"==typeof s&&s.apply(t.dom.container,arguments))})}),a.Mixer.registerFilter("operationGetInitialState","pagination",function(a,t){var i=this;return i.config.pagination.limit<0?a:(a.newPagination=t.activePagination,a)}),a.Mixer.registerFilter("stateGetInitialState","pagination",function(t){var i=this;return i.config.pagination.limit<0?t:(t.activePagination=new a.CommandPaginate,t.activePagination.page=i.config.load.page,t.activePagination.limit=i.config.pagination.limit,t)}),a.Mixer.registerAction("afterGetFinalMixData","pagination",function(){var a=this;a.config.pagination.limit<0||"number"==typeof a.config.pagination.maxPagers&&(a.config.pagination.maxPagers=Math.max(5,a.config.pagination.maxPagers))}),a.Mixer.registerAction("afterCacheDom","pagination",function(){var t=this,i=null;if(!(t.config.pagination.limit<0)&&t.config.pagination.generatePageList){switch(t.config.controls.scope){case"local":i=t.dom.container;break;case"global":i=t.dom.document;break;default:throw new Error(a.messages.ERROR_CONFIG_INVALID_CONTROLS_SCOPE)}t.dom.pageListEls=i.querySelectorAll(t.config.selectors.pageList),t.dom.pageStatsEls=i.querySelectorAll(t.config.selectors.pageStats)}}),a.Mixer.registerFilter("stateBuildState","pagination",function(a,t){var i=this;return i.config.pagination.limit<0?a:(a.activePagination=t.newPagination,a.totalPages=t.newTotalPages,a)}),a.Mixer.registerFilter("instructionParseMultimixArgs","pagination",function(t){var i=this;return i.config.pagination.limit<0?t:(!t.command.paginate||t.command.paginate instanceof a.CommandPaginate||(t.command.paginate=i.parsePaginateArgs([t.command.paginate]).command),t)}),a.Mixer.registerAction("afterFilterOperation","pagination",function(a){var t=this,i=-1,e=-1,n=[],s=[],o=null,g=-1,r=-1;if(!(t.config.pagination.limit<0)){if(a.newTotalPages=a.newPagination.limit?Math.max(Math.ceil(a.matching.length/a.newPagination.limit),1):1,t.config.pagination.maintainActivePage&&(a.newPagination.page=a.newPagination.page>a.newTotalPages?a.newTotalPages:a.newPagination.page),t.config.pagination.limit=a.newPagination.limit,a.newPagination.anchor){for(r=0;(o=a.matching[r])&&o.dom.el!==a.newPagination.anchor;r++);i=r,e=r+a.newPagination.limit-1}else i=a.newPagination.limit*(a.newPagination.page-1),e=a.newPagination.limit*a.newPagination.page-1,isNaN(i)&&(i=0);if(!(a.newPagination.limit<0)){for(r=0;o=a.show[r];r++)r>=i&&r<=e?n.push(o):s.push(o);for(a.show=n,r=0;o=a.toHide[r];r++)o.isShown||(a.toHide.splice(r,1),o.isShown=!1,r--);for(r=0;o=s[r];r++)a.hide.push(o),(g=a.toShow.indexOf(o))>-1&&a.toShow.splice(g,1),o.isShown&&a.toHide.push(o)}}}),a.Mixer.registerFilter("operationUnmappedGetOperation","pagination",function(t,e){var n=this;return n.config.pagination.limit<0?t:(t.startState=n.state,t.startPagination=n.state.activePagination,t.startTotalPages=n.state.totalPages,t.newPagination=new a.CommandPaginate,t.newPagination.limit=t.startPagination.limit,t.newPagination.page=t.startPagination.page,e.paginate?n.parsePaginateCommand(e.paginate,t):(e.filter||e.sort)&&(i.extend(t.newPagination,t.startPagination),n.config.pagination.maintainActivePage?t.newPagination.page=n.state.activePagination.page:t.newPagination.page=1),t)}),a.Mixer.registerFilter("operationMappedGetOperation","pagination",function(a,t,i){var e=this,n=null,s=-1;if(e.config.pagination.limit<0)return a;if(i)return a;if(e.config.pagination.generatePageList&&e.dom.pageListEls.length>0)for(s=0;n=e.dom.pageListEls[s];s++)e.renderPageListEl(n,a);if(e.config.pagination.generatePageStats&&e.dom.pageStatsEls.length>0)for(s=0;n=e.dom.pageStatsEls[s];s++)e.renderPageStatsEl(n,a);return a}),a.Mixer.extend({parsePaginateCommand:function(t,i){var e=this;if(t.page>-1){if(0===t.page)throw new Error(a.messages.ERROR_PAGINATE_INDEX_RANGE);i.newPagination.page=Math.max(1,Math.min(1/0,t.page))}else"next"===t.action?i.newPagination.page=e.getNextPage():"prev"===t.action?i.newPagination.page=e.getPrevPage():t.anchor&&(i.newPagination.anchor=t.anchor);t.limit>-1&&(i.newPagination.limit=t.limit),i.newPagination.limit!==i.startPagination.limit&&(i.newTotalPages=i.newPagination.limit?Math.max(Math.ceil(i.startState.matching.length/i.newPagination.limit),1):1),(i.newPagination.limit<=0||i.newPagination.limit===1/0)&&(i.newPagination.page=1)},getNextPage:function(){var a=this,t=-1;return t=a.state.activePagination.page+1,t>a.state.totalPages&&(t=a.config.pagination.loop?1:a.state.activePagination.page),t},getPrevPage:function(){var a=this,t=-1;return t=a.state.activePagination.page-1,t<1&&(t=a.config.pagination.loop?a.state.totalPages:a.state.activePagination.page),t},renderPageListEl:function(t,e){var n=this,s=-1,o="",g=[],r=null,l=null,c=[],p=!1,m=!1,f=null,P=null,u="",d=-1;if(e.newPagination.limit<0||e.newPagination.limit===1/0||e.newTotalPages<2&&n.config.pagination.hidePageListIfSinglePage)return t.innerHTML="",void i.addClass(t,n.classNamesPageList.disabled);for(s=e.newPagination.page-1,l="function"==typeof(l=n.config.render.pager)?l:null,n.config.pagination.maxPagers<1/0&&e.newTotalPages>n.config.pagination.maxPagers&&(c=n.getAllowedIndices(e)),r=new a.ModelPager,r.isPrev=!0,r.classList.push(n.classNamesPager.base,n.classNamesPager.prev),1!==e.newPagination.page||n.config.pagination.loop||(r.classList.push(n.classNamesPager.disabled),r.isDisabled=!0),r.classNames=r.classList.join(" "),o=l?l(r):i.template(n.config.templates.pagerPrev)(r),g.push(o),d=0;ds&&m?o&&g.push(o):(r=new a.ModelPager,r.isTruncationMarker=!0,r.classList.push(n.classNamesPager.base,n.classNamesPager.truncationMarker),r.classNames=r.classList.join(" "),o=l?l(r):i.template(n.config.templates.pagerTruncationMarker)(r),g.push(o),ds&&(m=!0));for(r=new a.ModelPager,r.isNext=!0,r.classList.push(n.classNamesPager.base,n.classNamesPager.next),e.newPagination.page!==e.newTotalPages||n.config.pagination.loop||r.classList.push(n.classNamesPager.disabled),r.classNames=r.classList.join(" "),o=l?l(r):i.template(n.config.templates.pagerNext)(r),g.push(o),u=g.join(" "),t.innerHTML=u,f=t.querySelectorAll("."+n.classNamesPager.disabled),d=0;P=f[d];d++)"boolean"==typeof P.disabled&&(P.disabled=!0);p||m?i.addClass(t,n.classNamesPageList.truncated):i.removeClass(t,n.classNamesPageList.truncated),e.newTotalPages>1?i.removeClass(t,n.classNamesPageList.disabled):i.addClass(t,n.classNamesPageList.disabled)},getAllowedIndices:function(a){var t=this,i=a.newPagination.page-1,e=a.newTotalPages-1,n=[],s=-1,o=-1,g=-1,r=-1,l=-1,c=-1,p=-1;for(n.push(0),s=t.config.pagination.maxPagers-2,o=Math.ceil((s-1)/2),g=Math.floor((s-1)/2),r=i-o,l=i+g,c=0,r<1&&(c=1-r),l>e-1&&(c=e-1-l),p=r+c;s;)n.push(p),p++,s--;return n.push(e),n},renderPager:function(t,e,n){var s=this,o=null,g=e.newPagination.page-1,r=new a.ModelPager,l="";return s.config.pagination.maxPagers<1/0&&n.length&&n.indexOf(t)<0?"":(o="function"==typeof(o=s.config.render.pager)?o:null,r.isPageLink=!0,r.classList.push(s.classNamesPager.base),0===t&&r.classList.push(s.classNamesPager.first),t===e.newTotalPages-1&&r.classList.push(s.classNamesPager.last),t===g&&r.classList.push(s.classNamesPager.active),r.classNames=r.classList.join(" "),r.pageNumber=t+1,l=o?o(r):i.template(s.config.templates.pager)(r))},renderPageStatsEl:function(t,e){var n=this,s=new a.ModelPageStats,o=null,g="",r="";return e.newPagination.limit<0||e.newPagination.limit===1/0||e.newTotalPages<2&&n.config.pagination.hidePageStatsIfSinglePage?(t.innerHTML="",void i.addClass(t,n.classNamesPageStats.disabled)):(o="function"==typeof(o=n.config.render.pageStats)?o:null,s.totalTargets=e.matching.length,r=s.totalTargets?1===e.newPagination.limit?n.config.templates.pageStatsSingle:n.config.templates.pageStats:n.config.templates.pageStatsFail,s.totalTargets&&e.newPagination.limit>0?(s.startPageAt=(e.newPagination.page-1)*e.newPagination.limit+1,s.endPageAt=Math.min(s.startPageAt+e.newPagination.limit-1,s.totalTargets)):s.startPageAt=s.endPageAt=0,g=o?o(s):i.template(r)(s),t.innerHTML=g,void(s.totalTargets?i.removeClass(t,n.classNamesPageStats.disabled):i.addClass(t,n.classNamesPageStats.disabled)))},parsePaginateArgs:function(t){var e=this,n=new a.UserInstruction,s=null,o=-1;for(n.animate=e.config.animation.enable,n.command=new a.CommandPaginate,o=0;ocallbacks + +A group of optional callback functions to be invoked at various +points within the lifecycle of a mixer operation. + +### onPaginateStart + + + + +A callback function invoked whenever a pagination operation starts. + +This function is equivalent to `onMixStart`, and is invoked immediately +after it with the same arguments. Unlike `onMixStart` however, it will +not be invoked for filter or sort operations. + + +|Type | Default +|--- | --- +|`function`| `null` + +### onPaginateStart + + + + +A callback function invoked whenever a pagination operation ends. + +This function is equivalent to `onMixEnd`, and is invoked immediately +after it with the same arguments. Unlike `onMixEnd` however, it will +not be invoked for filter or sort operations. + + +|Type | Default +|--- | --- +|`function`| `null` + + +

classNames

+ +A group of properties defining the output and structure of class names programmatically +added to controls and containers to reflect the state of the mixer. + +### elementPager + + + + +The "element" portion of the class name added to pager controls. + + +|Type | Default +|--- | --- +|`string`| `'control'` + +###### Example: changing the `config.classNames.elementPager` value + +```js + +// Change from the default value of 'control' to 'pager' + +var mixer = mixitup(containerEl, { + classNames: { + elementPager: 'pager' + } +}); + +// Base pager output: "mixitup-pager" +``` +### elementPageList + + + + +The "element" portion of the class name added to the page list element, when it is +in its disabled state. + +The page list element is the containing element in which pagers are rendered. + + +|Type | Default +|--- | --- +|`string`| `'page-list'` + +###### Example: changing the `config.classNames.elementPageList` value + +```js + +// Change from the default value of 'page-list' to 'pagination-links' + +var mixer = mixitup(containerEl, { + classNames: { + elementPageList: 'pagination-links' + } +}); + +// Disabled page-list output: "mixitup-pagination-links-disabled" +``` +### elementPageStats + + + + +The "element" portion of the class name added to the page stats element, when it is +in its disabled state. + +The page stats element is the containing element in which information about the +current page and total number of pages is rendered. + + +|Type | Default +|--- | --- +|`string`| `'page-stats'` + +###### Example: changing the `config.classNames.elementPageStats` value + +```js + +// Change from the default value of 'page-stats' to 'pagination-info' + +var mixer = mixitup(containerEl, { + classNames: { + elementPageList: 'pagination-info' + } +}); + +// Disabled page-list output: "mixitup-pagination-info-disabled" +``` +### modifierFirst + + + + +The "modifier" portion of the class name added to the first pager in the list of pager controls. + + +|Type | Default +|--- | --- +|`string`| `'first'` + +### modifierLast + + + + +The "modifier" portion of the class name added to the last pager in the list of pager controls. + + +|Type | Default +|--- | --- +|`string`| `'last'` + +### modifierLast + + + + +The "modifier" portion of the class name added to the previous pager in the list of pager controls. + + +|Type | Default +|--- | --- +|`string`| `'prev'` + +### modifierNext + + + + +The "modifier" portion of the class name added to the next pager in the list of pager controls. + + +|Type | Default +|--- | --- +|`string`| `'next'` + +### modifierTruncationMarker + + + + +The "modifier" portion of the class name added to truncation markers in the list of pager controls. + + +|Type | Default +|--- | --- +|`string`| `'truncation-marker'` + + +

load

+ +A group of properties defining the initial state of the mixer on load (instantiation). + +### page + + + + +An integer defining the starting page on load, if a page limit is active. + + +|Type | Default +|--- | --- +|`number`| `1` + +###### Example: Defining a start page other than 1 to be applied on load + +```js + +// The mixer will show page 3 on load, with 8 items per page. + +var mixer = mixitup(containerEl, { + pagination: { + limit: 8 + }, + load: { + page: 3 + } +}); +``` + +

pagination

+ +A group of properties defining the mixer's pagination behavior. + +### generatePageList + + + + +A boolean dictating whether or not MixItUp should render a list of pager controls. + +If you wish to control pagination functionality via the API, or your own UI, this can be set to `false`. + +In order for this functionality to work, you must provide MixItUp with a `pageList` +element matching the selector defined in `selectors.pageList`. Pager controls will be +rendered inside this element as per the templates defined for the `templates.pager` +and related configuration options, or if set, a custom render +function supplied to the `render.pager` configuration option. + + +|Type | Default +|--- | --- +|`boolean`| `true` + +###### Example: Disabling the rendering of the built-in "page list" UI + +```js + +var mixer = mixitup(containerEl, { + pagination: { + limit: 8, + generatePageList: false + } +}); +``` +### generatePageStats + + + + +A boolean dictating whether or not MixItUp should render a stats about the +current page (e.g. "1 to 4 of 16"). + +In order for this functionality to work, you must provide MixItUp with a `pageStats` +element matching the selector defined in `selectors.pageStats`. Page stats content will +be rendered inside this element as per the templates defined for the `templates.pageStats` +and `templates.pageStatsSingle` configuration options, or if set, a custom render +function supplied to the `render.pageStats` configuration option. + + +|Type | Default +|--- | --- +|`boolean`| `true` + +###### Example: Disabling the rendering of the built-in "page stats" UI + +```js + +var mixer = mixitup(containerEl, { + pagination: { + limit: 8, + generatePageStats: false + } +}); +``` +### maintainActivePage + + + + +A boolean dictating whether or not to maintain the active page when switching +from filter to filter. + +By default, MixItUp will attempt to maintain the active page or its highest +equivalent in the new collection of matching targets (e.g. page 3 would become +page 2 if there are not enough targets in the new collection), but by setting +this option to `false`, changing the active filter will always cause the mixer +to revert to page one of the new collection. + + +|Type | Default +|--- | --- +|`boolean`| `true` + +###### Example: Ensuring that the mixer reverts to page one when filtered + +```js + +var mixer = mixitup(containerEl, { + pagination: { + limit: 8, + maintainActivePage: false + } +}); +``` +### loop + + + + +A boolean dictating whether or not to allow "looping" of the built-in previous +and next pagination controls. + +By default, when on the first page, the "previous" button will be disabled, +and when on the last page, the "next" button will be disabled. By setting +this option to `true`, the user may loop from the first to last page and +vice-versa. + + +|Type | Default +|--- | --- +|`boolean`| `false` + +###### Example: Allowing prev/next controls to "loop" through pages + +```js + +var mixer = mixitup(containerEl, { + pagination: { + limit: 8, + loop: true + } +}); +``` +### hidePageListIfSinglePage + + + + +A boolean dictating whether or not to prevent rendering of the built-in +"page list" UI if the matching collection of targets has only enough content +for one page. + + +|Type | Default +|--- | --- +|`boolean`| `false` + +###### Example: Hiding the page list UI if only one page + +```js + +var mixer = mixitup(containerEl, { + pagination: { + limit: 8, + hidePageListIfSinglePage: true + } +}); +``` +### hidePageStatsIfSinglePage + + + + +A boolean dictating whether or not to prevent rendering of the built-in +"page stats" UI if the matching collection of targets has only enough content +for one page. + + +|Type | Default +|--- | --- +|`boolean`| `false` + +###### Example: Hiding the page stats UI if only one page + +```js + +var mixer = mixitup(containerEl, { + pagination: { + limit: 8, + hidePageStatsIfSinglePage: true + } +}); +``` +### limit + + + + +A number defining the maximum number of items per page. + +By default, this is set to `-1` and pagination is effectively +disabled. By setting this to any number greater than 0, pagination +will be applied to the mixers targets, effectively activating the +extension. + + +|Type | Default +|--- | --- +|`number`| `-1` + +###### Example: Activating the pagination extension by defining a valid limit + +```js + +var mixer = mixitup(containerEl, { + pagination: { + limit: 8 + } +}); +``` +### maxPagers + + + + +A number dictating the maximum number of individual pager controls to render before +truncating the list (e.g. adding an ellipses between non-consecutive pagers). + +The minimum value permitted for this option is 5, which ensures +there will always be at least a first, last, and two padding pagers, in addition +to the pager representing the currently active page. + + +|Type | Default +|--- | --- +|`number`| `5` + + +

render

+ +A group of optional render functions for creating and updating elements. + +### pager + + + + +A function returning an HTML string representing a single pager control element. + +By default, MixItUp will render pager controls using its own internal renderer +and templates (see `templates.pager`), but you may override this functionality by +providing your own render function here instead. All pager elements must have a +data-page element indicating the action of the control. + +The function receives an object containing all neccessary information +about the pager as its first parameter. + + +|Type | Default +|--- | --- +|`function`| `'null'` + +### pageStats + + + + +A function returning an HTML string forming the contents of the "page stats" element. + +By default, MixItUp will render page stats using its own internal renderer +and templates (see `templates.pageStats`), but you may override this functionality by +providing your own render function here instead. + +The function receives an object containing all neccessary information +about the current page and total pages as its first parameter. + + +|Type | Default +|--- | --- +|`function`| `'null'` + + +

selectors

+ +A group of properties defining the selectors used to query elements within a mixitup container. + +### pageList + + + + +A selector string used to query the page list element. + +Depending on the value of `controls.scope`, MixItUp will either query the +entire document for the page list element, or just the container. + + +|Type | Default +|--- | --- +|`string`| `'.mixitup-page-list'` + +### pageStats + + + + +A selector string used to query the page stats element. + +Depending on the value of `controls.scope`, MixItUp will either query the +entire document for the page stats element, or just the container. + + +|Type | Default +|--- | --- +|`string`| `'.mixitup-page-stats'` + + +

templates

+ +A group of template strings used to render pager controls and page stats elements. + +### pager + + + + + + + +|Type | Default +|--- | --- +|`string`| `''` + +### pagerPrev + + + + + + + +|Type | Default +|--- | --- +|`string`| `''` + +### pagerNext + + + + + + + +|Type | Default +|--- | --- +|`string`| `''` + +### pagerTruncationMarker + + + + + + + +|Type | Default +|--- | --- +|`string`| `''` + +### pageStats + + + + + + + +|Type | Default +|--- | --- +|`string`| `'${startPageAt} to ${endPageAt} of ${totalTargets}'` + +### pageStatsSingle + + + + + + + +|Type | Default +|--- | --- +|`string`| `'${startPageAt} of ${totalTargets}'` + +### pageStatsFail + + + + + + + +|Type | Default +|--- | --- +|`string`| `'None found'` + + diff --git a/docs/mixitup.Mixer.md b/docs/mixitup.Mixer.md new file mode 100644 index 0000000..33b9211 --- /dev/null +++ b/docs/mixitup.Mixer.md @@ -0,0 +1,142 @@ +# mixitup.Mixer + +## Overview + +The mixitup.Mixer class is extended with the following methods relating to +the Pagination extension. + +For the full list of methods, please refer to the MixItUp core documentation. + +### Contents + +- [paginate()](#paginate) +- [nextPage()](#nextPage) +- [prevPage()](#prevPage) + + +

paginate()

+ + +`.paginate(page [, animate] [, callback])` + +Changes the current page and/or the current page limit. + +| |Type | Name | Description +|---|--- | --- | --- +|Param |`number, string, object, HTMLElement` | `page` | A page number, string (`'next'`, `'prev'`), HTML element reference, or command object. +|Param |`boolean` | `[animate]` | An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. +|Param |`function` | `[callback]` | An optional callback function to be invoked after the operation has completed. +|Returns |`Promise.` | A promise resolving with the current state object. + + +###### Example 1: Changing the active page + +```js + +console.log(mixer.getState().activePagination.page); // 1 + +mixer.paginate(2) + .then(function(state) { + console.log(mixer.getState().activePagination.page === 2); // true + }); +``` +###### Example 2: Progressing to the next page + +```js + +console.log(mixer.getState().activePagination.page); // 1 + +mixer.paginate('next') + .then(function(state) { + console.log(mixer.getState().activePagination.page === 2); // true + }); +``` +###### Example 3: Starting a page from an abitrary "anchor" element + +```js + +var anchorEl = mixer.getState().show[3]; + +mixer.paginate(anchorEl) + .then(function(state) { + console.log(mixer.getState().activePagination.anchor === anchorEl); // true + console.log(mixer.getState().show[0] === anchorEl); // true + }); +``` +###### Example 4: Changing the page limit + +```js + +var anchorEl = mixer.getState().show[3]; + +console.log(mixer.getState().activePagination.limit); // 8 + +mixer.paginate({ + limit: 4 +}) + .then(function(state) { + console.log(mixer.getState().activePagination.limit === 4); // true + }); +``` +###### Example 5: Changing the active page and page limit + +```js + +mixer.paginate({ + limit: 4, + page: 2 +}) + .then(function(state) { + console.log(mixer.getState().activePagination.page === 2); // true + console.log(mixer.getState().activePagination.limit === 4); // true + }); +``` + +

nextPage()

+ + +`.nextPage()` + +A shorthand for `.paginate('next')`. Moves to the next page. + +| |Type | Name | Description +|---|--- | --- | --- +|Returns |`Promise.` | A promise resolving with the current state object. + + +###### Example: Moving to the next page + +```js + +console.log(mixer.getState().activePagination.page); // 1 + +mixer.nextPage() + .then(function(state) { + console.log(mixer.getState().activePagination.page === 2); // true + }); +``` + +

prevPage()

+ + +`.prevPage()` + +A shorthand for `.paginate('prev')`. Moves to the previous page. + +| |Type | Name | Description +|---|--- | --- | --- +|Returns |`Promise.` | A promise resolving with the current state object. + + +###### Example: Moving to the previous page + +```js + +console.log(mixer.getState().activePagination.page); // 5 + +mixer.prevPage() + .then(function(state) { + console.log(mixer.getState().activePagination.page === 4); // true + }); +``` + diff --git a/docs/mixitup.State.md b/docs/mixitup.State.md new file mode 100644 index 0000000..c40a467 --- /dev/null +++ b/docs/mixitup.State.md @@ -0,0 +1,42 @@ +# mixitup.State + +## Overview + +`mixitup.State` objects expose various pieces of data detailing the state of +a MixItUp instance. They are provided at the start and end of any operation via +callbacks and events, with the most recent state stored between operations +for retrieval at any time via the API. + +### Contents + +- [activePagination](#activePagination) +- [totalPages](#totalPages) + + +

activePagination

+ + + + +The currently active pagination command as set by a control click or API call. + + +|Type | Default +|--- | --- +|`mixitup.CommandPagination`| `null` + + +

totalPages

+ + + + +The total number of pages produced as a combination of the current page +limit and active filter. + + +|Type | Default +|--- | --- +|`number`| `-1` + + diff --git a/docs/mixitup.md b/docs/mixitup.md new file mode 100644 index 0000000..09409a1 --- /dev/null +++ b/docs/mixitup.md @@ -0,0 +1,11 @@ +#() + + + + + + +| |Type | Name | Description +|---|--- | --- | --- +|Returns | + diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..4b2fb9d --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,94 @@ +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-pagination.js' + ]) + .pipe(uglify({ + preserveComments: 'license' + })) + .pipe(rename('mixitup-pagination.min.js')) + .on('error', function(e) { + console.error('[uglify] ' + e.message); + }) + .pipe(sourcemaps.write('./')) + .pipe(gulp.dest('./dist/')); +}); + +gulp.task('build', ['build-dist'], function(done) { + exec('node node_modules/mixitup-build/docs.js -s mixitup-pagination.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-pagination.js', function(e, out) { + if (out) { + console.log(out); + } + + done(e); + }); +}); + +gulp.task('lint', function() { + return gulp.src([ + './src/*.js' + ], + { + base: '/' + }) + .pipe(jshint('./.jshintrc')) + .pipe(jshint.reporter(stylish)) + .pipe(jshint.reporter('fail')); +}); + +gulp.task('code-style', function() { + return gulp.src([ + './src/*.js' + ], + { + base: '/' + }) + .pipe(jscs()) + .pipe(jscs.reporter()); +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..69fe7d7 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "mixitup-pagination", + "title": "MixItUp Pagination", + "version": "3.3.0", + "description": "Client-side pagination for filtered and sorted content", + "author": "KunkaLabs Limited", + "homepage": "https://www.kunkalabs.com/mixitup-pagination/", + "license" : "SEE LICENSE IN license.md", + "private": true, + "main": "./dist/mixitup-pagination.js", + "dependencies": { + "mixitup": "^3.1.8" + }, + "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": "~1.5.1", + "jshint-stylish": "~1.0.0", + "merge-stream": "^0.1.7", + "vinyl-buffer": "^1.0.0", + "vinyl-source-stream": "^1.1.0", + "mocha": "^3.0.2", + "jsdom-global": "2.0.0", + "chai": "^3.5.0", + "chai-as-promised": "^5.3.0", + "chai-shallow-deep-equal": "^1.4.0", + "mixitup-build": "git://github.com/patrickkunka/mixitup-build.git" + } +} \ No newline at end of file diff --git a/src/banner.js b/src/banner.js new file mode 100644 index 0000000..8e31b1d --- /dev/null +++ b/src/banner.js @@ -0,0 +1,17 @@ +/**! + * {{title}} v{{version}} + * {{description}} + * Build {{buildId}} + * + * Requires mixitup.js >= v{{coreVersion}} + * + * @copyright Copyright {{beginCopyrightYear}}-{{currentYear}} {{author}}. + * @author {{author}}. + * @link {{websiteUrl}} + * + * @license Commercial use requires a commercial license. + * {{commercialLicenseUrl}} + * + * Non-commercial use permitted under same terms as {{nonComercialLicenseTitle}} license. + * {{nonCommercialLicenseUrl}} + */ diff --git a/src/command-multimix.js b/src/command-multimix.js new file mode 100644 index 0000000..f37473e --- /dev/null +++ b/src/command-multimix.js @@ -0,0 +1,5 @@ +/* globals mixitup */ + +mixitup.CommandMultimix.registerAction('afterConstruct', 'pagination', function() { + this.paginate = null; +}); \ No newline at end of file diff --git a/src/command-paginate.js b/src/command-paginate.js new file mode 100644 index 0000000..5cdf860 --- /dev/null +++ b/src/command-paginate.js @@ -0,0 +1,17 @@ +/* global mixitup, h */ + +/** + * @constructor + * @memberof mixitup + * @private + * @since 3.0.0 + */ + +mixitup.CommandPaginate = function() { + this.page = -1; + this.limit = -1; + this.action = ''; // enum: ['prev', 'next'] + this.anchor = null; + + h.seal(this); +}; \ No newline at end of file diff --git a/src/config-callbacks.js b/src/config-callbacks.js new file mode 100644 index 0000000..9d5edea --- /dev/null +++ b/src/config-callbacks.js @@ -0,0 +1,48 @@ +/* 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 2.0.0 + */ + +mixitup.ConfigCallbacks.registerAction('afterConstruct', 'pagination', function() { + /** + * A callback function invoked whenever a pagination operation starts. + * + * This function is equivalent to `onMixStart`, and is invoked immediately + * after it with the same arguments. Unlike `onMixStart` however, it will + * not be invoked for filter or sort operations. + * + * + * @name onPaginateStart + * @memberof mixitup.Config.callbacks + * @instance + * @type {function} + * @default null + */ + + this.onPaginateStart = null; + + /** + * A callback function invoked whenever a pagination operation ends. + * + * This function is equivalent to `onMixEnd`, and is invoked immediately + * after it with the same arguments. Unlike `onMixEnd` however, it will + * not be invoked for filter or sort operations. + * + * @name onPaginateStart + * @memberof mixitup.Config.callbacks + * @instance + * @type {function} + * @default null + */ + + this.onPaginateEnd = null; +}); \ No newline at end of file diff --git a/src/config-class-names.js b/src/config-class-names.js new file mode 100644 index 0000000..e61401f --- /dev/null +++ b/src/config-class-names.js @@ -0,0 +1,155 @@ +/* global mixitup */ + +/** + * A group of properties defining the output and structure of class names programmatically + * added to controls and containers to reflect the state of the mixer. + * + * @constructor + * @memberof mixitup.Config + * @name classNames + * @namespace + * @public + * @since 3.0.0 + */ + +mixitup.ConfigClassNames.registerAction('afterConstruct', 'pagination', function() { + + /** + * The "element" portion of the class name added to pager controls. + * + * @example Example: changing the `config.classNames.elementPager` value + * + * // Change from the default value of 'control' to 'pager' + * + * var mixer = mixitup(containerEl, { + * classNames: { + * elementPager: 'pager' + * } + * }); + * + * // Base pager output: "mixitup-pager" + * + * @name elementPager + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'control' + */ + + this.elementPager = 'control'; + + /** + * The "element" portion of the class name added to the page list element, when it is + * in its disabled state. + * + * The page list element is the containing element in which pagers are rendered. + * + * @example Example: changing the `config.classNames.elementPageList` value + * + * // Change from the default value of 'page-list' to 'pagination-links' + * + * var mixer = mixitup(containerEl, { + * classNames: { + * elementPageList: 'pagination-links' + * } + * }); + * + * // Disabled page-list output: "mixitup-pagination-links-disabled" + * + * @name elementPageList + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'page-list' + */ + + this.elementPageList = 'page-list'; + + /** + * The "element" portion of the class name added to the page stats element, when it is + * in its disabled state. + * + * The page stats element is the containing element in which information about the + * current page and total number of pages is rendered. + * + * @example Example: changing the `config.classNames.elementPageStats` value + * + * // Change from the default value of 'page-stats' to 'pagination-info' + * + * var mixer = mixitup(containerEl, { + * classNames: { + * elementPageList: 'pagination-info' + * } + * }); + * + * // Disabled page-list output: "mixitup-pagination-info-disabled" + * + * @name elementPageStats + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'page-stats' + */ + + this.elementPageStats = 'page-stats'; + + /** + * The "modifier" portion of the class name added to the first pager in the list of pager controls. + * + * @name modifierFirst + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'first' + */ + + this.modifierFirst = 'first'; + + /** + * The "modifier" portion of the class name added to the last pager in the list of pager controls. + * + * @name modifierLast + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'last' + */ + + this.modifierLast = 'last'; + + /** + * The "modifier" portion of the class name added to the previous pager in the list of pager controls. + * + * @name modifierLast + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'prev' + */ + + this.modifierPrev = 'prev'; + + /** + * The "modifier" portion of the class name added to the next pager in the list of pager controls. + * + * @name modifierNext + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'next' + */ + + this.modifierNext = 'next'; + + /** + * The "modifier" portion of the class name added to truncation markers in the list of pager controls. + * + * @name modifierTruncationMarker + * @memberof mixitup.Config.classNames + * @instance + * @type {string} + * @default 'truncation-marker' + */ + + this.modifierTruncationMarker = 'truncation-marker'; +}); \ No newline at end of file diff --git a/src/config-load.js b/src/config-load.js new file mode 100644 index 0000000..70c48a2 --- /dev/null +++ b/src/config-load.js @@ -0,0 +1,39 @@ +/* global mixitup */ + +/** + * A group of properties defining the initial state of the mixer on load (instantiation). + * + * @constructor + * @memberof mixitup.Config + * @name load + * @namespace + * @public + * @since 2.0.0 + */ + +mixitup.ConfigLoad.registerAction('afterConstruct', 'pagination', function() { + /** + * An integer defining the starting page on load, if a page limit is active. + * + * @example Example: Defining a start page other than 1 to be applied on load + * + * // The mixer will show page 3 on load, with 8 items per page. + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8 + * }, + * load: { + * page: 3 + * } + * }); + * + * @name page + * @memberof mixitup.Config.load + * @instance + * @type {number} + * @default 1 + */ + + this.page = 1; +}); \ No newline at end of file diff --git a/src/config-pagination.js b/src/config-pagination.js new file mode 100644 index 0000000..8de1877 --- /dev/null +++ b/src/config-pagination.js @@ -0,0 +1,217 @@ +/* global mixitup, h */ + +/** + * A group of properties defining the mixer's pagination behavior. + * + * @constructor + * @memberof mixitup.Config + * @name pagination + * @namespace + * @public + * @since 2.0.0 + */ + +mixitup.ConfigPagination = function() { + + /** + * A boolean dictating whether or not MixItUp should render a list of pager controls. + * + * If you wish to control pagination functionality via the API, or your own UI, this can be set to `false`. + * + * In order for this functionality to work, you must provide MixItUp with a `pageList` + * element matching the selector defined in `selectors.pageList`. Pager controls will be + * rendered inside this element as per the templates defined for the `templates.pager` + * and related configuration options, or if set, a custom render + * function supplied to the `render.pager` configuration option. + * + * @example Example: Disabling the rendering of the built-in "page list" UI + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * generatePageList: false + * } + * }); + * + * @name generatePageList + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default true + */ + + this.generatePageList = true; + + /** + * A boolean dictating whether or not MixItUp should render a stats about the + * current page (e.g. "1 to 4 of 16"). + * + * In order for this functionality to work, you must provide MixItUp with a `pageStats` + * element matching the selector defined in `selectors.pageStats`. Page stats content will + * be rendered inside this element as per the templates defined for the `templates.pageStats` + * and `templates.pageStatsSingle` configuration options, or if set, a custom render + * function supplied to the `render.pageStats` configuration option. + * + * @example Example: Disabling the rendering of the built-in "page stats" UI + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * generatePageStats: false + * } + * }); + * + * @name generatePageStats + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default true + */ + + this.generatePageStats = true; + + /** + * A boolean dictating whether or not to maintain the active page when switching + * from filter to filter. + * + * By default, MixItUp will attempt to maintain the active page or its highest + * equivalent in the new collection of matching targets (e.g. page 3 would become + * page 2 if there are not enough targets in the new collection), but by setting + * this option to `false`, changing the active filter will always cause the mixer + * to revert to page one of the new collection. + * + * @example Example: Ensuring that the mixer reverts to page one when filtered + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * maintainActivePage: false + * } + * }); + * + * @name maintainActivePage + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default true + */ + + this.maintainActivePage = true; + + /** + * A boolean dictating whether or not to allow "looping" of the built-in previous + * and next pagination controls. + * + * By default, when on the first page, the "previous" button will be disabled, + * and when on the last page, the "next" button will be disabled. By setting + * this option to `true`, the user may loop from the first to last page and + * vice-versa. + * + * @example Example: Allowing prev/next controls to "loop" through pages + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * loop: true + * } + * }); + * + * @name loop + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default false + */ + + this.loop = false; + + /** + * A boolean dictating whether or not to prevent rendering of the built-in + * "page list" UI if the matching collection of targets has only enough content + * for one page. + * + * @example Example: Hiding the page list UI if only one page + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * hidePageListIfSinglePage: true + * } + * }); + * + * @name hidePageListIfSinglePage + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default false + */ + + this.hidePageListIfSinglePage = false; + + /** + * A boolean dictating whether or not to prevent rendering of the built-in + * "page stats" UI if the matching collection of targets has only enough content + * for one page. + * + * @example Example: Hiding the page stats UI if only one page + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8, + * hidePageStatsIfSinglePage: true + * } + * }); + * + * @name hidePageStatsIfSinglePage + * @memberof mixitup.Config.pagination + * @instance + * @type {boolean} + * @default false + */ + + this.hidePageStatsIfSinglePage = false; + + /** + * A number defining the maximum number of items per page. + * + * By default, this is set to `-1` and pagination is effectively + * disabled. By setting this to any number greater than 0, pagination + * will be applied to the mixers targets, effectively activating the + * extension. + * + * @example Example: Activating the pagination extension by defining a valid limit + * + * var mixer = mixitup(containerEl, { + * pagination: { + * limit: 8 + * } + * }); + * + * @name limit + * @memberof mixitup.Config.pagination + * @instance + * @type {number} + * @default -1 + */ + + this.limit = -1; + + /** + * A number dictating the maximum number of individual pager controls to render before + * truncating the list (e.g. adding an ellipses between non-consecutive pagers). + * + * The minimum value permitted for this option is 5, which ensures + * there will always be at least a first, last, and two padding pagers, in addition + * to the pager representing the currently active page. + * + * @name maxPagers + * @memberof mixitup.Config.pagination + * @instance + * @type {number} + * @default 5 + */ + + this.maxPagers = 5; + + h.seal(this); +}; \ No newline at end of file diff --git a/src/config-render.js b/src/config-render.js new file mode 100644 index 0000000..b38104a --- /dev/null +++ b/src/config-render.js @@ -0,0 +1,53 @@ +/* global mixitup */ + +/** + * A group of optional render functions for creating and updating elements. + * + * @constructor + * @memberof mixitup.Config + * @name render + * @namespace + * @public + * @since 3.0.0 + */ + +mixitup.ConfigRender.registerAction('afterConstruct', 'pagination', function() { + /** + * A function returning an HTML string representing a single pager control element. + * + * By default, MixItUp will render pager controls using its own internal renderer + * and templates (see `templates.pager`), but you may override this functionality by + * providing your own render function here instead. All pager elements must have a + * data-page element indicating the action of the control. + * + * The function receives an object containing all neccessary information + * about the pager as its first parameter. + * + * @name pager + * @memberof mixitup.Config.render + * @instance + * @type {function} + * @default 'null' + */ + + this.pager = null; + + /** + * A function returning an HTML string forming the contents of the "page stats" element. + * + * By default, MixItUp will render page stats using its own internal renderer + * and templates (see `templates.pageStats`), but you may override this functionality by + * providing your own render function here instead. + * + * The function receives an object containing all neccessary information + * about the current page and total pages as its first parameter. + * + * @name pageStats + * @memberof mixitup.Config.render + * @instance + * @type {function} + * @default 'null' + */ + + this.pageStats = null; +}); \ No newline at end of file diff --git a/src/config-selectors.js b/src/config-selectors.js new file mode 100644 index 0000000..1ebd4db --- /dev/null +++ b/src/config-selectors.js @@ -0,0 +1,44 @@ +/* global mixitup */ + +/** + * A group of properties defining the selectors used to query elements within a mixitup container. + * + * @constructor + * @memberof mixitup.Config + * @name selectors + * @namespace + * @public + * @since 2.0.0 + */ + +mixitup.ConfigSelectors.registerAction('afterConstruct', 'pagination', function() { + /** + * A selector string used to query the page list element. + * + * Depending on the value of `controls.scope`, MixItUp will either query the + * entire document for the page list element, or just the container. + * + * @name pageList + * @memberof mixitup.Config.selectors + * @instance + * @type {string} + * @default '.mixitup-page-list' + */ + + this.pageList = '.mixitup-page-list'; + + /** + * A selector string used to query the page stats element. + * + * Depending on the value of `controls.scope`, MixItUp will either query the + * entire document for the page stats element, or just the container. + * + * @name pageStats + * @memberof mixitup.Config.selectors + * @instance + * @type {string} + * @default '.mixitup-page-stats' + */ + + this.pageStats = '.mixitup-page-stats'; +}); \ No newline at end of file diff --git a/src/config-templates.js b/src/config-templates.js new file mode 100644 index 0000000..d5b6059 --- /dev/null +++ b/src/config-templates.js @@ -0,0 +1,84 @@ +/* global mixitup */ + +/** + * A group of template strings used to render pager controls and page stats elements. + * + * @constructor + * @memberof mixitup.Config + * @name templates + * @namespace + * @public + * @since 3.0.0 + */ + +mixitup.ConfigTemplates.registerAction('afterConstruct', 'pagination', function() { + /** + * @name pager + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '' + */ + + this.pager = ''; + + /** + * @name pagerPrev + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '' + */ + + this.pagerPrev = ''; + + /** + * @name pagerNext + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '' + */ + + this.pagerNext = ''; + + /** + * @name pagerTruncationMarker + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '' + */ + + this.pagerTruncationMarker = ''; + + /** + * @name pageStats + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '${startPageAt} to ${endPageAt} of ${totalTargets}' + */ + + this.pageStats = '${startPageAt} to ${endPageAt} of ${totalTargets}'; + + /** + * @name pageStatsSingle + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default '${startPageAt} of ${totalTargets}' + */ + + this.pageStatsSingle = '${startPageAt} of ${totalTargets}'; + + /** + * @name pageStatsFail + * @memberof mixitup.Config.templates + * @instance + * @type {string} + * @default 'None found' + */ + + this.pageStatsFail = 'None found'; +}); \ No newline at end of file diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..3a40796 --- /dev/null +++ b/src/config.js @@ -0,0 +1,20 @@ +/* global mixitup */ + +/** + * The MixItUp configuration object is extended with the following properties + * relating to the Pagination 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', 'pagination', function() { + this.pagination = new mixitup.ConfigPagination(); +}); \ No newline at end of file diff --git a/src/control-definition.js b/src/control-definition.js new file mode 100644 index 0000000..c74b01e --- /dev/null +++ b/src/control-definition.js @@ -0,0 +1,3 @@ +/* global mixitup */ + +mixitup.controlDefinitions.push(new mixitup.ControlDefinition('pager', '[data-page]', true, 'pageListEls')); \ No newline at end of file diff --git a/src/control.js b/src/control.js new file mode 100644 index 0000000..878801f --- /dev/null +++ b/src/control.js @@ -0,0 +1,61 @@ +/* globals mixitup, h */ + +/** + * @param {mixitup.MultimixCommand[]} commands + * @param {ClickEvent} e + * @return {object|null} + */ + +mixitup.Control.registerFilter('commandsHandleClick', 'pagination', function(commands, e) { + var self = this, + command = {}, + page = '', + pageNumber = -1, + mixer = null, + button = null, + i = -1; + + if (!self.selector || self.selector !== '[data-page]') { + // Static control or non-pager live control + + return commands; + } + + button = h.closestParent(e.target, self.selector, true, self.bound[0].dom.document); + + for (i = 0; mixer = self.bound[i]; i++) { + command = commands[i]; + + if (!mixer.config.pagination || mixer.config.pagination.limit < 0 || mixer.config.pagination.limit === Infinity) { + // Pagination is disabled for this instance. Do not handle. + + commands[i] = null; + + continue; + } + + if (!button || h.hasClass(button, mixer.classNamesPager.active) || h.hasClass(button, mixer.classNamesPager.disabled)) { + // No button was clicked or button is already active. Do not handle. + + commands[i] = null; + + continue; + } + + page = button.getAttribute('data-page'); + + if (page === 'prev') { + command.paginate = 'prev'; + } else if (page === 'next') { + command.paginate = 'next'; + } else if (pageNumber) { + command.paginate = parseInt(page); + } + + if (mixer.lastClicked) { + mixer.lastClicked = button; + } + } + + return commands; +}); \ No newline at end of file diff --git a/src/events.js b/src/events.js new file mode 100644 index 0000000..d51967b --- /dev/null +++ b/src/events.js @@ -0,0 +1,27 @@ +/* globals mixitup */ + +mixitup.Events.registerAction('afterConstruct', 'pagination', function() { + /** + * A custom event triggered whenever a pagination operation starts. + * + * @name paginateStart + * @memberof mixitup.Events + * @static + * @type {CustomEvent} + */ + + this.paginateStart = null; + + /** + * A custom event triggered whenever a pagination operation ends. + * + * @name paginateEnd + * @memberof mixitup.Events + * @static + * @type {CustomEvent} + */ + + this.paginateEnd = null; +}); + +mixitup.events = new mixitup.Events(); \ No newline at end of file diff --git a/src/facade.js b/src/facade.js new file mode 100644 index 0000000..9e67f66 --- /dev/null +++ b/src/facade.js @@ -0,0 +1,7 @@ +/* global mixitup */ + +mixitup.Facade.registerAction('afterConstruct', 'pagination', function(mixer) { + this.paginate = mixer.paginate.bind(mixer); + this.nextPage = mixer.nextPage.bind(mixer); + this.prevPage = mixer.prevPage.bind(mixer); +}); \ No newline at end of file diff --git a/src/messages.js b/src/messages.js new file mode 100644 index 0000000..ca35e2e --- /dev/null +++ b/src/messages.js @@ -0,0 +1,8 @@ +/* globals mixitup */ + +mixitup.Messages.registerAction('afterConstruct', 'pagination', function() { + /* Pagination extension errors + ----------------------------------------------------------------------------- */ + + this.ERROR_PAGINATE_INDEX_RANGE = '[MixItUp] Page indices must start from 1'; +}); \ No newline at end of file diff --git a/src/mixer-dom.js b/src/mixer-dom.js new file mode 100644 index 0000000..d7d802f --- /dev/null +++ b/src/mixer-dom.js @@ -0,0 +1,6 @@ +/* global mixitup */ + +mixitup.MixerDom.registerAction('afterConstruct', 'pagination', function() { + this.pageListEls = []; + this.pageStatsEls = []; +}); \ No newline at end of file diff --git a/src/mixer.js b/src/mixer.js new file mode 100644 index 0000000..1dd661a --- /dev/null +++ b/src/mixer.js @@ -0,0 +1,1071 @@ +/* global mixitup, h */ + +/** + * The mixitup.Mixer class is extended with the following methods relating to + * the Pagination extension. + * + * For the full list of methods, please refer to the MixItUp core documentation. + * + * @constructor + * @namespace + * @name Mixer + * @memberof mixitup + * @public + * @since 3.0.0 + */ + +mixitup.Mixer.registerAction('afterConstruct', 'pagination', function() { + this.classNamesPager = new mixitup.UiClassNames(); + this.classNamesPageList = new mixitup.UiClassNames(); + this.classNamesPageStats = new mixitup.UiClassNames(); +}); + +/** + * @private + * @return {void} + */ + +mixitup.Mixer.registerAction('afterAttach', 'pagination', function() { + var self = this, + el = null, + i = -1; + + if (self.config.pagination.limit < 0) return; + + // Map pagination ui classNames + + // jscs:disable + self.classNamesPager.base = h.getClassname(self.config.classNames, 'pager'); + self.classNamesPager.active = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierActive); + self.classNamesPager.disabled = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierDisabled); + self.classNamesPager.first = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierFirst); + self.classNamesPager.last = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierLast); + self.classNamesPager.prev = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierPrev); + self.classNamesPager.next = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierNext); + self.classNamesPager.truncationMarker = h.getClassname(self.config.classNames, 'pager', self.config.classNames.modifierTruncationMarker); + + self.classNamesPageList.base = h.getClassname(self.config.classNames, 'page-list'); + self.classNamesPageList.disabled = h.getClassname(self.config.classNames, 'page-list', self.config.classNames.modifierDisabled); + + self.classNamesPageStats.base = h.getClassname(self.config.classNames, 'page-stats'); + self.classNamesPageStats.disabled = h.getClassname(self.config.classNames, 'page-stats', self.config.classNames.modifierDisabled); + // jscs:enable + + if (self.config.pagination.generatePageList && self.dom.pageListEls.length > 0) { + for (i = 0; (el = self.dom.pageListEls[i]); i++) { + self.renderPageListEl(el, self.lastOperation); + } + } + + if (self.config.pagination.generatePageStats && self.dom.pageStatsEls.length > 0) { + for (i = 0; (el = self.dom.pageStatsEls[i]); i++) { + self.renderPageStatsEl(el, self.lastOperation); + } + } +}); + +mixitup.Mixer.registerAction('afterSanitizeConfig', 'pagination', function() { + var self = this, + onMixStart = self.config.callbacks.onMixStart, + onMixEnd = self.config.callbacks.onMixEnd, + onPaginateStart = self.config.callbacks.onPaginateStart, + onPaginateEnd = self.config.callbacks.onPaginateEnd, + didPaginate = false; + + if (self.config.pagination.limit < 0) return; + + self.classNamesPager = new mixitup.UiClassNames(); + self.classNamesPageList = new mixitup.UiClassNames(); + self.classNamesPageStats = new mixitup.UiClassNames(); + + self.config.callbacks.onMixStart = function(prevState, nextState) { + if ( + prevState.activePagination.limit !== nextState.activePagination.limit || + prevState.activePagination.page !== nextState.activePagination.page + ) { + didPaginate = true; + } + + if (typeof onMixStart === 'function') onMixStart.apply(self.dom.container, arguments); + + if (!didPaginate) return; + + mixitup.events.fire('paginateStart', self.dom.container, { + state: prevState, + futureState: nextState, + instance: self + }, self.dom.document); + + if (typeof onPaginateStart === 'function') onPaginateStart.apply(self.dom.container, arguments); + }; + + self.config.callbacks.onMixEnd = function(state) { + if (typeof onMixEnd === 'function') onMixEnd.apply(self.dom.container, arguments); + + if (!didPaginate) return; + + didPaginate = false; + + mixitup.events.fire('paginateEnd', self.dom.container, { + state: state, + instance: self + }, self.dom.document); + + if (typeof onPaginateEnd === 'function') onPaginateEnd.apply(self.dom.container, arguments); + }; +}); + +/** + * @private + * @param {mixitup.Operation} operation + * @param {mixitup.State} state + * @return {mixitup.Operation} + */ + +mixitup.Mixer.registerFilter('operationGetInitialState', 'pagination', function(operation, state) { + var self = this; + + if (self.config.pagination.limit < 0) return operation; + + operation.newPagination = state.activePagination; + + return operation; +}); + +/** + * @private + * @param {mixitup.State} state + * @return {mixitup.State} + */ + +mixitup.Mixer.registerFilter('stateGetInitialState', 'pagination', function(state) { + var self = this; + + if (self.config.pagination.limit < 0) return state; + + state.activePagination = new mixitup.CommandPaginate(); + + state.activePagination.page = self.config.load.page; + state.activePagination.limit = self.config.pagination.limit; + + return state; +}); + +/** + * @private + * @return {void} + */ + +mixitup.Mixer.registerAction('afterGetFinalMixData', 'pagination', function() { + var self = this; + + if (self.config.pagination.limit < 0) return; + + if (typeof self.config.pagination.maxPagers === 'number') { + // Restrict max pagers to a minimum of 5. There must always + // be a first, last, and one on either side of the active pager. + // e.g. « 1 ... 4 5 6 ... 10 » + + self.config.pagination.maxPagers = Math.max(5, self.config.pagination.maxPagers); + } +}); + +/** + * @private + * @return {void} + */ + +mixitup.Mixer.registerAction('afterCacheDom', 'pagination', function() { + var self = this, + parent = null; + + if (self.config.pagination.limit < 0) return; + + if (!self.config.pagination.generatePageList) 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.pageListEls = parent.querySelectorAll(self.config.selectors.pageList); + self.dom.pageStatsEls = parent.querySelectorAll(self.config.selectors.pageStats); +}); + +/** + * @private + * @param {mixitup.State} state + * @param {mixitup.Operation} operation + * @return {mixitup.State} + */ + +mixitup.Mixer.registerFilter('stateBuildState', 'pagination', function(state, operation) { + var self = this; + + if (self.config.pagination.limit < 0) return state; + + // Map pagination-specific properties into state + + state.activePagination = operation.newPagination; + state.totalPages = operation.newTotalPages; + + return state; +}); + +/** + * @private + * @param {mixitup.UserInstruction} instruction + * @return {mixitup.UserInstruction} + */ + +mixitup.Mixer.registerFilter('instructionParseMultimixArgs', 'pagination', function(instruction) { + var self = this; + + if (self.config.pagination.limit < 0) return instruction; + + if (instruction.command.paginate && !(instruction.command.paginate instanceof mixitup.CommandPaginate)) { + instruction.command.paginate = self.parsePaginateArgs([instruction.command.paginate]).command; + } + + return instruction; +}); + +/** + * @private + * @param {mixitup.Operation} operation + * @return {void} + */ + +mixitup.Mixer.registerAction('afterFilterOperation', 'pagination', function(operation) { + var self = this, + startPageAt = -1, + endPageAt = -1, + inPage = [], + notInPage = [], + target = null, + index = -1, + i = -1; + + if (self.config.pagination.limit < 0) return; + + // Calculate the new total pages as a matter of course (i.e. a change in filter) + + // New matching array has already been set at this point + + operation.newTotalPages = operation.newPagination.limit ? + Math.max(Math.ceil(operation.matching.length / operation.newPagination.limit), 1) : + 1; + + if (self.config.pagination.maintainActivePage) { + operation.newPagination.page = (operation.newPagination.page > operation.newTotalPages) ? + operation.newTotalPages : + operation.newPagination.page; + } + + // Keep config in sync with latest limit + + self.config.pagination.limit = operation.newPagination.limit; + + if (operation.newPagination.anchor) { + // Start page at an anchor element + + for (i = 0; target = operation.matching[i]; i++) { + if (target.dom.el === operation.newPagination.anchor) break; + } + + startPageAt = i; + endPageAt = i + operation.newPagination.limit - 1; + } else { + // Start page based on limit and page index + + startPageAt = operation.newPagination.limit * (operation.newPagination.page - 1); + endPageAt = (operation.newPagination.limit * operation.newPagination.page) - 1; + + if (isNaN(startPageAt)) { + startPageAt = 0; + } + } + + if (operation.newPagination.limit < 0) return; + + for (i = 0; target = operation.show[i]; i++) { + // For each target in `show`, include in page, only if within the range + + if (i >= startPageAt && i <= endPageAt) { + inPage.push(target); + } else { + // Else move to `notInPage` + + notInPage.push(target); + } + } + + // override the operation's `show` array with the newly constructed `inPage` array + + operation.show = inPage; + + // For anything not in the page, make sure it is correctly assigned: + + for (i = 0; target = operation.toHide[i]; i++) { + // For example, if a target would normally be included in `toHide`, but is + // now already hidden as not in the page, make sure it is removed from `toHide` + // so it is not included in the operation. + + if (!target.isShown) { + operation.toHide.splice(i, 1); + + target.isShown = false; + + i--; + } + } + + for (i = 0; target = notInPage[i]; i++) { + // For each target not in page, move into `hide` + + operation.hide.push(target); + + if ((index = operation.toShow.indexOf(target)) > -1) { + // Any targets due to be shown will no longer be shown + + operation.toShow.splice(index, 1); + } + + if (target.isShown) { + // If currently shown, move to `toHide` + + operation.toHide.push(target); + } + } +}); + +/** + * @private + * @param {mixitup.Operation} operation + * @param {mixitup.CommandMultimix} command + * @return {mixitup.Operation} + */ + +mixitup.Mixer.registerFilter('operationUnmappedGetOperation', 'pagination', function(operation, command) { + var self = this; + + if (self.config.pagination.limit < 0) return operation; + + operation.startState = self.state; + operation.startPagination = self.state.activePagination; + operation.startTotalPages = self.state.totalPages; + + operation.newPagination = new mixitup.CommandPaginate(); + + operation.newPagination.limit = operation.startPagination.limit; + operation.newPagination.page = operation.startPagination.page; + + if (command.paginate) { + self.parsePaginateCommand(command.paginate, operation); + } else if (command.filter || command.sort) { + h.extend(operation.newPagination, operation.startPagination); + + // Reset to 1, or maintain active: + + if (!self.config.pagination.maintainActivePage) { + operation.newPagination.page = 1; + } else { + operation.newPagination.page = self.state.activePagination.page; + } + } + + return operation; +}); + +/** + * @private + * @param {mixitup.Operation} operation + * @param {object} command + * @param {boolean} [isPreFetch=false] + * @return {mixitup.Operation} + */ + +mixitup.Mixer.registerFilter('operationMappedGetOperation', 'pagination', function(operation, command, isPreFetch) { + var self = this, + el = null, + i = -1; + + if (self.config.pagination.limit < 0) return operation; + + if (isPreFetch) { + // The operation is being pre-fetched, so don't update the pagers or stats yet. + + return operation; + } + + if (self.config.pagination.generatePageList && self.dom.pageListEls.length > 0) { + for (i = 0; (el = self.dom.pageListEls[i]); i++) { + self.renderPageListEl(el, operation); + } + } + + if (self.config.pagination.generatePageStats && self.dom.pageStatsEls.length > 0) { + for (i = 0; (el = self.dom.pageStatsEls[i]); i++) { + self.renderPageStatsEl(el, operation); + } + } + + return operation; +}); + +mixitup.Mixer.extend( +/** @lends mixitup.Mixer */ +{ + /** + * @private + * @param {mixitup.CommandPaginate} command + * @param {mixitup.Operation} operation + * @return {void} + */ + + parsePaginateCommand: function(command, operation) { + var self = this; + + // e.g. mixer.paginate({page: 3, limit: 2}); + // e.g. mixer.paginate({action: 'next'}); + // e.g. mixer.paginate({anchor: anchorTarget, limit: 5}); + + if (command.page > -1) { + if (command.page === 0) throw new Error(mixitup.messages.ERROR_PAGINATE_INDEX_RANGE); + + // TODO: replace Infinity with the highest possible page index + + operation.newPagination.page = Math.max(1, Math.min(Infinity, command.page)); + } else if (command.action === 'next') { + operation.newPagination.page = self.getNextPage(); + } else if (command.action === 'prev') { + operation.newPagination.page = self.getPrevPage(); + } else if (command.anchor) { + operation.newPagination.anchor = command.anchor; + } + + if (command.limit > -1) { + operation.newPagination.limit = command.limit; + } + + if (operation.newPagination.limit !== operation.startPagination.limit) { + // A new limit has been sent via the API, calculate total pages + + operation.newTotalPages = operation.newPagination.limit ? + Math.max(Math.ceil(operation.startState.matching.length / operation.newPagination.limit), 1) : + 1; + } + + if (operation.newPagination.limit <= 0 || operation.newPagination.limit === Infinity) { + operation.newPagination.page = 1; + } + }, + + /** + * @private + * @return {number} page + */ + + getNextPage: function() { + var self = this, + page = -1; + + page = self.state.activePagination.page + 1; + + if (page > self.state.totalPages) { + page = self.config.pagination.loop ? 1 : self.state.activePagination.page; + } + + return page; + }, + + /** + * @private + * @return {Number} page + */ + + getPrevPage: function() { + var self = this, + page = -1; + + page = self.state.activePagination.page - 1; + + if (page < 1) { + page = self.config.pagination.loop ? self.state.totalPages : self.state.activePagination.page; + } + + return page; + }, + + /** + * @private + * @param {HTMLElement} pageListEl + * @param {mixitup.Operation} operation + * @return {void} + */ + + renderPageListEl: function(pageListEl, operation) { + var self = this, + activeIndex = -1, + pagerHtml = '', + buttonList = [], + model = null, + renderer = null, + allowedIndices = [], + truncatedBefore = false, + truncatedAfter = false, + disabled = null, + el = null, + html = '', + i = -1; + + if ( + operation.newPagination.limit < 0 || + operation.newPagination.limit === Infinity || + (operation.newTotalPages < 2 && self.config.pagination.hidePageListIfSinglePage) + ) { + // Empty the pager list, and add disabled class + + pageListEl.innerHTML = ''; + + h.addClass(pageListEl, self.classNamesPageList.disabled); + + return; + } + + activeIndex = operation.newPagination.page - 1; + + renderer = typeof (renderer = self.config.render.pager) === 'function' ? renderer : null; + + if (self.config.pagination.maxPagers < Infinity && operation.newTotalPages > self.config.pagination.maxPagers) { + allowedIndices = self.getAllowedIndices(operation); + } + + // Render prev button + + model = new mixitup.ModelPager(); + + model.isPrev = true; + model.classList.push(self.classNamesPager.base, self.classNamesPager.prev); + + // If first and not looping, disable the prev button + + if (operation.newPagination.page === 1 && !self.config.pagination.loop) { + model.classList.push(self.classNamesPager.disabled); + + model.isDisabled = true; + } + + model.classNames = model.classList.join(' '); + + if (renderer) { + pagerHtml = renderer(model); + } else { + pagerHtml = h.template(self.config.templates.pagerPrev)(model); + } + + buttonList.push(pagerHtml); + + // Render per-page pagers + + for (i = 0; i < operation.newTotalPages; i++) { + pagerHtml = self.renderPager(i, operation, allowedIndices); + + if (pagerHtml || (i < activeIndex && truncatedBefore) || i > activeIndex && truncatedAfter) { + if (pagerHtml) { + buttonList.push(pagerHtml); + } + + continue; + } + + // Replace gaps between pagers with a truncation maker, but only once + + model = new mixitup.ModelPager(); + + model.isTruncationMarker = true; + + model.classList.push(self.classNamesPager.base, self.classNamesPager.truncationMarker); + model.classNames = model.classList.join(' '); + + if (renderer) { + pagerHtml = renderer(model); + } else { + pagerHtml = h.template(self.config.templates.pagerTruncationMarker)(model); + } + + buttonList.push(pagerHtml); + + // Prevent multiple truncation markers + + if (i < activeIndex) { + truncatedBefore = true; + } + + if (i > activeIndex) { + truncatedAfter = true; + } + } + + // Render next button + + model = new mixitup.ModelPager(); + + model.isNext = true; + model.classList.push(self.classNamesPager.base, self.classNamesPager.next); + + // If last page and not looping, disable the next button + + if (operation.newPagination.page === operation.newTotalPages && !self.config.pagination.loop) { + model.classList.push(self.classNamesPager.disabled); + } + + model.classNames = model.classList.join(' '); + + if (renderer) { + pagerHtml = renderer(model); + } else { + pagerHtml = h.template(self.config.templates.pagerNext)(model); + } + + buttonList.push(pagerHtml); + + // Replace markup + + html = buttonList.join(' '); + + pageListEl.innerHTML = html; + + // Add disabled attribute to disabled buttons + + disabled = pageListEl.querySelectorAll('.' + self.classNamesPager.disabled); + + for (i = 0; el = disabled[i]; i++) { + if (typeof el.disabled === 'boolean') { + el.disabled = true; + } + } + + if (truncatedBefore || truncatedAfter) { + h.addClass(pageListEl, self.classNamesPageList.truncated); + } else { + h.removeClass(pageListEl, self.classNamesPageList.truncated); + } + + if (operation.newTotalPages > 1) { + h.removeClass(pageListEl, self.classNamesPageList.disabled); + } else { + h.addClass(pageListEl, self.classNamesPageList.disabled); + } + }, + + /** + * An algorithm defining which pagers should be rendered based on their index + * and the current active page, when a `pagination.maxPagers` value is applied. + * + * @private + * @param {mixitup.Operation} operation + * @return {number[]} + */ + + getAllowedIndices: function(operation) { + var self = this, + activeIndex = operation.newPagination.page - 1, + lastIndex = operation.newTotalPages - 1, + indices = [], + paddingRange = -1, + paddingBack = -1, + paddingFront = -1, + paddingRangeStart = -1, + paddingRangeEnd = -1, + paddingRangeOffset = -1, + i = -1; + + // Examples: + + // « 1 2 *3* 4 5 » maxPagers = 5 + // « 1 ... 4 *5* 6 ... 10 » maxPagers = 5 + + // « 1 ... 6 7 *8* 9 10 » maxPagers = 6 + // « 1 ... 3 4 *5* 6 ... 10 » maxPagers = 6 + + // « 1 ... 3 4 *5* 6 7 ... 10 » maxPagers = 7 + // « *1* 2 3 4 5 6 ... 10 » maxPagers = 7 + + // This algorithm ensures that at any time, the active pager + // should be surrounded by as many "padding" pagers as possible to equal the + // value of `pagination.maxPagers`, accounting for the fact the first and last + // pager should also always be rendered. + + // Push in index 0 to represent the first pager + + indices.push(0); + + // Calculate the "padding range" by subtracting 2 from `pagination.maxPagers` + + paddingRange = self.config.pagination.maxPagers - 2; + + // Distribute the padding equally behind and in front of the active pager. + // If the padding range is an even number, we allow an extra pager behind the active pager. + + paddingBack = Math.ceil((paddingRange - 1) / 2); + paddingFront = Math.floor((paddingRange - 1) / 2); + + // Calculate where the range should start and finish based on the active index + + paddingRangeStart = activeIndex - paddingBack; + paddingRangeEnd = activeIndex + paddingFront; + + // Set the offset to 0 + + paddingRangeOffset = 0; + + // If the start of the range has collided with the first pager, positively offset as needed + + if (paddingRangeStart < 1) { + paddingRangeOffset = 1 - paddingRangeStart; + } + + // If the end of the range has collided with the last pager, negatively offset as needed + + if (paddingRangeEnd > lastIndex - 1) { + paddingRangeOffset = (lastIndex - 1) - paddingRangeEnd; + } + + // Calcuate the first index of the range taking into account any offset + + i = paddingRangeStart + paddingRangeOffset; + + // Iteratate through the range, adding the respective indices: + + while (paddingRange) { + indices.push(i); + + i++; + paddingRange--; + } + + indices.push(lastIndex); + + return indices; + }, + + /** + * Renderes individual, per-page pagers. + * + * @private + * @param {number} i + * @param {mixitup.Operation} operation + * @param {number[]} [allowedIndices] + * @return {string} + */ + + renderPager: function(i, operation, allowedIndices) { + var self = this, + renderer = null, + activePage = operation.newPagination.page - 1, + model = new mixitup.ModelPager(), + output = ''; + + if ( + self.config.pagination.maxPagers < Infinity && + allowedIndices.length && + allowedIndices.indexOf(i) < 0 + ) { + // maxPagers is set, and this pager is not in the allowed range + + return ''; + } + + renderer = typeof (renderer = self.config.render.pager) === 'function' ? renderer : null; + + model.isPageLink = true; + + model.classList.push(self.classNamesPager.base); + + if (i === 0) { + model.classList.push(self.classNamesPager.first); + } + + if (i === operation.newTotalPages - 1) { + model.classList.push(self.classNamesPager.last); + } + + if (i === activePage) { + model.classList.push(self.classNamesPager.active); + } + + model.classNames = model.classList.join(' '); + model.pageNumber = i + 1; + + if (renderer) { + output = renderer(model); + } else { + output = h.template(self.config.templates.pager)(model); + } + + return output; + }, + + /** + * @private + * @param {HTMLElement} pageStatsEl + * @param {mixitup.Operation} operation + * @return {void} + */ + + renderPageStatsEl: function(pageStatsEl, operation) { + var self = this, + model = new mixitup.ModelPageStats(), + renderer = null, + output = '', + template = ''; + + if ( + operation.newPagination.limit < 0 || + operation.newPagination.limit === Infinity || + (operation.newTotalPages < 2 && self.config.pagination.hidePageStatsIfSinglePage) + ) { + // Empty the pager list, and add disabled class + + pageStatsEl.innerHTML = ''; + + h.addClass(pageStatsEl, self.classNamesPageStats.disabled); + + return; + } + + renderer = typeof (renderer = self.config.render.pageStats) === 'function' ? renderer : null; + + model.totalTargets = operation.matching.length; + + if (model.totalTargets) { + template = operation.newPagination.limit === 1 ? + self.config.templates.pageStatsSingle : + self.config.templates.pageStats; + } else { + template = self.config.templates.pageStatsFail; + } + + if (model.totalTargets && operation.newPagination.limit > 0) { + model.startPageAt = ((operation.newPagination.page - 1) * operation.newPagination.limit) + 1; + model.endPageAt = Math.min(model.startPageAt + operation.newPagination.limit - 1, model.totalTargets); + } else { + model.startPageAt = model.endPageAt = 0; + } + + if (renderer) { + output = renderer(model); + } else { + output = h.template(template)(model); + } + + pageStatsEl.innerHTML = output; + + if (model.totalTargets) { + h.removeClass(pageStatsEl, self.classNamesPageStats.disabled); + } else { + h.addClass(pageStatsEl, self.classNamesPageStats.disabled); + } + }, + + /** + * @private + * @param {Array<*>} args + * @return {mixitup.UserInstruction} instruction + */ + + parsePaginateArgs: function(args) { + var self = this, + instruction = new mixitup.UserInstruction(), + arg = null, + i = -1; + + instruction.animate = self.config.animation.enable; + instruction.command = new mixitup.CommandPaginate(); + + for (i = 0; i < args.length; i++) { + arg = args[i]; + + if (arg === null) continue; + + if (typeof arg === 'object' && h.isElement(arg, self.dom.document)) { + instruction.command.anchor = arg; + } else if (arg instanceof mixitup.CommandPaginate || typeof arg === 'object') { + h.extend(instruction.command, arg); + } else if (typeof arg === 'number') { + instruction.command.page = arg; + } else if (typeof arg === 'string' && !isNaN(parseInt(arg))) { + // e.g. "4" + + instruction.command.page = parseInt(arg); + } else if (typeof arg === 'string') { + instruction.command.action = arg; + } else if (typeof arg === 'boolean') { + instruction.animate = arg; + } else if (typeof arg === 'function') { + instruction.callback = arg; + } + } + + h.freeze(instruction); + + // NB: Don't freeze command as may need to be sanitized later by other methods + + return instruction; + }, + + /** + * Changes the current page and/or the current page limit. + * + * @example + * + * .paginate(page [, animate] [, callback]) + * + * @example Example 1: Changing the active page + * + * console.log(mixer.getState().activePagination.page); // 1 + * + * mixer.paginate(2) + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 2); // true + * }); + * + * @example Example 2: Progressing to the next page + * + * console.log(mixer.getState().activePagination.page); // 1 + * + * mixer.paginate('next') + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 2); // true + * }); + * + * @example Example 3: Starting a page from an abitrary "anchor" element + * + * var anchorEl = mixer.getState().show[3]; + * + * mixer.paginate(anchorEl) + * .then(function(state) { + * console.log(mixer.getState().activePagination.anchor === anchorEl); // true + * console.log(mixer.getState().show[0] === anchorEl); // true + * }); + * + * @example Example 4: Changing the page limit + * + * var anchorEl = mixer.getState().show[3]; + * + * console.log(mixer.getState().activePagination.limit); // 8 + * + * mixer.paginate({ + * limit: 4 + * }) + * .then(function(state) { + * console.log(mixer.getState().activePagination.limit === 4); // true + * }); + * + * @example Example 5: Changing the active page and page limit + * + * mixer.paginate({ + * limit: 4, + * page: 2 + * }) + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 2); // true + * console.log(mixer.getState().activePagination.limit === 4); // true + * }); + * + * @public + * @instance + * @param {(number|string|object|HTMLElement)} page + * A page number, string (`'next'`, `'prev'`), HTML element reference, or command object. + * @param {boolean} [animate=true] + * An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default. + * @param {function} [callback=null] + * An optional callback function to be invoked after the operation has completed. + * @return {Promise.} + * A promise resolving with the current state object. + */ + + paginate: function() { + var self = this, + instruction = self.parsePaginateArgs(arguments); + + return self.multimix({ + paginate: instruction.command + }, instruction.animate, instruction.callback); + }, + + /** + * A shorthand for `.paginate('next')`. Moves to the next page. + * + * @example + * + * .nextPage() + * + * @example Example: Moving to the next page + * + * console.log(mixer.getState().activePagination.page); // 1 + * + * mixer.nextPage() + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 2); // true + * }); + * + * @public + * @instance + * @return {Promise.} + * A promise resolving with the current state object. + */ + + nextPage: function() { + var self = this, + instruction = self.parsePaginateArgs(arguments); + + return self.multimix({ + paginate: { + action: 'next' + } + }, instruction.animate, instruction.callback); + }, + + /** + * A shorthand for `.paginate('prev')`. Moves to the previous page. + * + * @example + * + * .prevPage() + * + * @example Example: Moving to the previous page + * + * console.log(mixer.getState().activePagination.page); // 5 + * + * mixer.prevPage() + * .then(function(state) { + * console.log(mixer.getState().activePagination.page === 4); // true + * }); + * + * @public + * @instance + * @return {Promise.} + * A promise resolving with the current state object. + */ + + prevPage: function() { + var self = this, + instruction = self.parsePaginateArgs(arguments); + + return self.multimix({ + paginate: { + action: 'prev' + } + }, instruction.animate, instruction.callback); + } +}); \ No newline at end of file diff --git a/src/model-page-stats.js b/src/model-page-stats.js new file mode 100644 index 0000000..a872c8e --- /dev/null +++ b/src/model-page-stats.js @@ -0,0 +1,9 @@ +/* globals mixitup, h */ + +mixitup.ModelPageStats = function() { + this.startPageAt = -1; + this.endPageAt = -1; + this.totalTargets = -1; + + h.seal(this); +}; \ No newline at end of file diff --git a/src/model-pager.js b/src/model-pager.js new file mode 100644 index 0000000..b761fe5 --- /dev/null +++ b/src/model-pager.js @@ -0,0 +1,14 @@ +/* global mixitup, h */ + +mixitup.ModelPager = function() { + this.pageNumber = -1; + this.classNames = ''; + this.classList = []; + this.isDisabled = false; + this.isPrev = false; + this.isNext = false; + this.isPageLink = false; + this.isTruncationMarker = false; + + h.seal(this); +}; \ No newline at end of file diff --git a/src/module-definitions.js b/src/module-definitions.js new file mode 100644 index 0000000..c75dc4e --- /dev/null +++ b/src/module-definitions.js @@ -0,0 +1,13 @@ +/* global mixitupPagination */ + +if (typeof exports === 'object' && typeof module === 'object') { + module.exports = mixitupPagination; +} else if (typeof define === 'function' && define.amd) { + define(function() { + return mixitupPagination; + }); +} else if (window.mixitup && typeof window.mixitup === 'function') { + mixitupPagination(window.mixitup); +} else { + throw new Error('[MixItUp Pagination] MixItUp core not found'); +} \ No newline at end of file diff --git a/src/operation.js b/src/operation.js new file mode 100644 index 0000000..bdd052c --- /dev/null +++ b/src/operation.js @@ -0,0 +1,8 @@ +/* global mixitup */ + +mixitup.Operation.registerAction('afterConstruct', 'pagination', function() { + this.startPagination = null; + this.newPagination = null; + this.startTotalPages = -1; + this.newTotalPages = -1; +}); \ No newline at end of file diff --git a/src/state.js b/src/state.js new file mode 100644 index 0000000..35fe25c --- /dev/null +++ b/src/state.js @@ -0,0 +1,43 @@ +/* global mixitup */ + +/** + * `mixitup.State` objects expose various pieces of data detailing the state of + * a MixItUp instance. They are provided at the start and end of any operation via + * callbacks and events, with the most recent state stored between operations + * for retrieval at any time via the API. + * + * @constructor + * @namespace + * @name State + * @memberof mixitup + * @public + * @since 3.0.0 + */ + +mixitup.State.registerAction('afterConstruct', 'pagination', function() { + + /** + * The currently active pagination command as set by a control click or API call. + * + * @name activePagination + * @memberof mixitup.State + * @instance + * @type {mixitup.CommandPagination} + * @default null + */ + + this.activePagination = null; + + /** + * The total number of pages produced as a combination of the current page + * limit and active filter. + * + * @name totalPages + * @memberof mixitup.State + * @instance + * @type {number} + * @default -1 + */ + + this.totalPages = -1; +}); \ No newline at end of file diff --git a/src/ui-class-names.js b/src/ui-class-names.js new file mode 100644 index 0000000..b2af308 --- /dev/null +++ b/src/ui-class-names.js @@ -0,0 +1,12 @@ +/* global mixitup */ + +mixitup.UiClassNames.registerAction('afterConstruct', 'pagination', function() { + this.first = ''; + this.last = ''; + this.prev = ''; + this.next = ''; + this.first = ''; + this.last = ''; + this.truncated = ''; + this.truncationMarker = ''; +}); \ No newline at end of file diff --git a/src/version-check.js b/src/version-check.js new file mode 100644 index 0000000..31b7734 --- /dev/null +++ b/src/version-check.js @@ -0,0 +1,12 @@ +/* global mixitupPagination, mixitup, h */ +if ( + !mixitup.CORE_VERSION || + !h.compareVersions(mixitupPagination.REQUIRE_CORE_VERSION, mixitup.CORE_VERSION) +) { + throw new Error( + '[MixItUp Pagination] MixItUp Pagination ' + + mixitupPagination.EXTENSION_VERSION + + ' requires at least MixItUp ' + + mixitupPagination.REQUIRE_CORE_VERSION + ); +} \ No newline at end of file diff --git a/src/wrapper.hbs b/src/wrapper.hbs new file mode 100644 index 0000000..e944fe2 --- /dev/null +++ b/src/wrapper.hbs @@ -0,0 +1,60 @@ +{{>banner}} + +(function(window) { + 'use strict'; + + var mixitupPagination = function(mixitup) { + var h = mixitup.h; + + {{>version-check}} + + {{>config-callbacks}} + + {{>config-class-names}} + + {{>config-load}} + + {{>config-pagination}} + + {{>config-render}} + + {{>config-selectors}} + + {{>config-templates}} + + {{>config}} + + {{>model-pager}} + + {{>model-page-stats}} + + {{>ui-class-names}} + + {{>control-definition}} + + {{>control}} + + {{>command-multimix}} + + {{>command-paginate}} + + {{>events}} + + {{>operation}} + + {{>state}} + + {{>mixer-dom}} + + {{>mixer}} + + {{>facade}} + }; + + mixitupPagination.TYPE = 'mixitup-extension'; + mixitupPagination.NAME = '{{name}}'; + mixitupPagination.EXTENSION_VERSION = '{{version}}'; + mixitupPagination.REQUIRE_CORE_VERSION = '{{coreVersion}}'; + + {{>module-definitions}} +})(window); \ No newline at end of file