Initial commit
4
filterizr/v2/.eslintignore
Normal file
@ -0,0 +1,4 @@
|
||||
/dist/*
|
||||
/node_modules/*
|
||||
/coverage/*
|
||||
./webpack.config.js
|
34
filterizr/v2/.eslintrc.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"jest": true,
|
||||
"jquery": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"rules": {
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"always-multiline",
|
||||
{
|
||||
"functions": "never"
|
||||
}
|
||||
],
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"semi": ["error", "always"],
|
||||
"no-console": ["error"],
|
||||
"no-extra-semi": ["error"],
|
||||
"@typescript-eslint/no-var-requires": 0,
|
||||
"@typescript-eslint/member-ordering": 2,
|
||||
"no-unused-vars": ["error"]
|
||||
}
|
||||
}
|
41
filterizr/v2/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Filterizr version**
|
||||
Which version of Filterizr do you have installed in your project?
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
filterizr/v2/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEAT]"
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
14
filterizr/v2/.github/ISSUE_TEMPLATE/help-wanted.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Help wanted
|
||||
about: Describe this issue template's purpose here.
|
||||
title: "[HELP]"
|
||||
labels: help wanted
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Filterizr version**
|
||||
Which version of Filterizr do you have installed in your project?
|
||||
|
||||
**Describe the issue in details**
|
||||
Add any other context about the problem here.
|
6
filterizr/v2/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
**/node_modules
|
||||
**/coverage
|
||||
**/build
|
||||
**/dist/**/*.d.ts
|
||||
.DS_STORE
|
||||
yarn-error.log
|
13
filterizr/v2/.npmignore
Normal file
@ -0,0 +1,13 @@
|
||||
demo/
|
||||
tests/
|
||||
coverage/
|
||||
.babelrc
|
||||
.eslintignore
|
||||
.eslintrc.yml
|
||||
.travis.yml
|
||||
webpack.config.js
|
||||
README.md
|
||||
LICENSE
|
||||
CHANGELOG.md
|
||||
filterizr_logo.png
|
||||
yarn-error.log
|
6
filterizr/v2/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "always"
|
||||
}
|
6
filterizr/v2/.travis.yml
Normal file
@ -0,0 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "8"
|
||||
|
||||
script:
|
||||
- npm run lint
|
135
filterizr/v2/CHANGELOG.md
Normal file
@ -0,0 +1,135 @@
|
||||
## Version 2.2.4
|
||||
|
||||
- Set `display: none` on filtered out items
|
||||
|
||||
## Version 2.2.3
|
||||
|
||||
- Fix bug with circular JSON ref by dememoizing `getFiltered`
|
||||
|
||||
## Version 2.2.2
|
||||
|
||||
- Performance optimization (by use of memoization)
|
||||
- Fix `insertItem` behavior when using `gutterPixels`
|
||||
|
||||
## Version 2.2.1
|
||||
|
||||
- Fix grid behavior when changing `gutterPixels` through `setOptions`
|
||||
- Improve calculation of grid paddings when tweaking `gutterPixels`
|
||||
- Improve behavior of window resize event in reordering the grid
|
||||
|
||||
## Version 2.2.0
|
||||
|
||||
- Add `gutterPixels` options to support margins out of the box
|
||||
- Change column calculation logic to use `Math.floor` instead of `Math.round` (fixes overflowing elements at the grid's end)
|
||||
- Add new public method `removeItem` that removes an item from the grid
|
||||
- Add a built-in loading spinner animation controlled through the `spinner` option in options.
|
||||
- Fix timing of callbacks
|
||||
|
||||
## Version 2.1.1
|
||||
|
||||
- Remove `gridSelector` as it could not be used in any viable use case
|
||||
- Further simplification/optimization of codebase
|
||||
|
||||
## Version 2.1.0
|
||||
|
||||
- Export TS type declarations for package consumers
|
||||
- Add `searchTerm`, `gridSelector` and `gridItemsSelector` to options
|
||||
- Extend constructor of `Filterizr` to accept either a selector or an `HTMLElement` as the first argument
|
||||
- Add `ActiveFilter` class to abstract away some complexity from `toggleFilter`
|
||||
- Add `FilterizrOptions` class to abstract away some complexity from the options
|
||||
- Add `BrowserWindow` class to abstract away window events
|
||||
- Add `FilterItems` class to abstract away the operations on `FilterItem` collections
|
||||
- Delay setting the transition property on `FilterItem` to have
|
||||
the animations playing only after the grid has been initialized.
|
||||
- Integrate `imagesloaded` with `Filterizr` to solve the common issue
|
||||
of overlapping images
|
||||
- Implement `onInit` event fired only once on the first render
|
||||
|
||||
## Version 2.0.1
|
||||
|
||||
- Fix bug where `setupControls` in options was no functional
|
||||
|
||||
## Version 2.0.0
|
||||
|
||||
- Filterizr is now a JavaScript library and has no jQuery dependency, but can still be used as a jQuery plugin
|
||||
- Converted the codebase to TypeScript
|
||||
- Fix a few minor bugs
|
||||
- Improved behavior of shuffle
|
||||
- Rebuilt the website in React
|
||||
|
||||
## Version 1.3.5
|
||||
|
||||
- Add support for Bootstrap v4+ and flex layouts
|
||||
- Fix bug with infinite loop for sameWidth layout when window resizing and cols <= 0
|
||||
- Add polyfill for Array.prototype.includes and String.prototype.includes for IE11
|
||||
- Drop IE10 support, grids now use flex
|
||||
|
||||
## Version 1.3.4
|
||||
|
||||
- Fix bug where filter would be triggered multiple times through the controls
|
||||
- Fix bug where the control event handlers would not be deregistered on `.destroy()` due to problematic selector
|
||||
|
||||
## Version 1.3.3
|
||||
|
||||
- Fix bug where items might overlap when the container is instantiated, by calling `updateFilterItemsDimensions` on instantiating the FilterContainer
|
||||
- Fix all layout related bugs, stemming from inspecting the wrong collection, causing overlaps or incorrect container height
|
||||
|
||||
## Version 1.3.2
|
||||
|
||||
- Fix bug where if a filter was already active the `search` method did not correctly filter out items
|
||||
- Set `jquery@^1.9.0` as `peerDependency` to Filterizr
|
||||
- Set entry point of package.json to `./dist/jquery.filterizr-with-jquery.min.js` where Filterizr is bundled with jQuery and exports the jQuery objects with the Filterizr function registered on it.
|
||||
|
||||
## Version 1.3.1
|
||||
|
||||
- Fix bug where Filterizr would not be instantiated if the .filtr-container had more than one class names
|
||||
|
||||
## Version 1.3.0
|
||||
|
||||
- Gave a corerewrite to the plugin, with ES6 and Babel, maintaining a backwards compatible API.
|
||||
- Added tests for most important methods.
|
||||
- Dropped support for Bower.
|
||||
- Added new API method `.filterizr('insertItem', $node)`, which is used add a new item into the Filterizr grid.
|
||||
- Added new API method `.filterizr('destroy')`, which is used to destroy the Filterizr instance.
|
||||
- Added new option `multifilterLogicalOperator: ('or'|'and')` to support different multifiltering modes.
|
||||
- Added new option `controlsSelector: ''` allowing to easily target multiple controls if many Filterizr instances exist.
|
||||
- Filtered out items will now receive a z-index: -1000 on top of the .filteredOut class making them non-clickable when not visible.
|
||||
|
||||
## Version 1.2.5
|
||||
|
||||
- Fixed the bug caused by selector property which was removed in jQuery version 3.
|
||||
- Removed `selector` from configuration options.
|
||||
|
||||
## Version 1.2.3
|
||||
|
||||
- Added Bower support.
|
||||
- Fixed a bug which made Filterizr incompatible with some other scripts (e.g. Mootools) due to using for..in loops on Arrays.
|
||||
- Fixed a bug where .filteredOut items would be clickable by adding `pointer-events: none` on all filtered out items (for IE10 a `.filteredOut { z-index: 1-; }` rule is still needed in your CSS file).
|
||||
|
||||
## Version 1.2.2
|
||||
|
||||
- Improved UX of `setOptions` method when overriding the callbacks object in options. All undefined callbacks will now be set to an empty function by default.
|
||||
- Fixed a bug in `getCategory` method of filtr items, where a string would be compared with a number, by using `parseInt`.
|
||||
- Fixed a bug in `toggleFilter` method where if the user would resize the window undefined would be added to the toggledOn filters and would cause an error to be thrown if the user switched off the categories.
|
||||
|
||||
## Version 1.2.1
|
||||
|
||||
- Fixed a minor bug which would occur in the absence of a search input field.
|
||||
|
||||
## Version 1.2.0
|
||||
|
||||
- Added new API method `.filterizr('search', text)`, which is used to apply a search filter.
|
||||
- Added new search control in the form of an input text which must have a data-search attribute.
|
||||
- Updated public API methods to account for and apply the search filter over their intended operations.
|
||||
|
||||
## Version 1.1.0
|
||||
|
||||
- Added new API method `.filterizr('toggleFilter', toggledFilter)`, which is used for the new multi-filtering mode.
|
||||
- Added new filtering mode and filtering controls for multi-filtering. The user can now activate filters and display specific portions of the gallery alone or in combination. When all filters are turned of an unfiltered gallery is shown.
|
||||
- All filtered out .filtr-item elements now get a 'filteredOut' class when they are filtered out of the visible elements, so that the user can target them if needed.
|
||||
- Improved error checking, when the value of the `data-category` attribute of .filtr-item elements is not an integer or a string of integers delimited by ', ' a comprehensive error is thrown.
|
||||
|
||||
## Version 1.0.1
|
||||
|
||||
- Improved sorting functionality. Users can now sort based on custom data-attributes. Just add your custom data-attribute
|
||||
(e.g. data-mySortData) and then call `.filterizr('sort', 'mySortData', 'asc')` Remember to omit the "data-" part when passing the attribute name as the parameter.
|
21
filterizr/v2/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yiotis Kaltsikis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
106
filterizr/v2/README.md
Normal file
@ -0,0 +1,106 @@
|
||||
# [<img src="./filterizr_logo.png" width="145" height="30" alt="Filterizr logo" />](http://yiotis.net/filterizr)<br/> [](https://travis-ci.org/giotiskl/Filterizr) <img src="https://img.shields.io/npm/v/filterizr.svg" alt="NPM version" /> <img src="https://img.shields.io/npm/dm/filterizr.svg" alt="NPM monthly downloads" /> <img src="https://img.shields.io/github/license/giotiskl/filterizr.svg" alt="license badge" />
|
||||
|
||||
## Description
|
||||
|
||||
Filterizr is a JavaScript library that sorts, shuffles, searches and applies stunning filters over responsive galleries using CSS3 transitions. Write your very own, custom effects in CSS and watch your gallery come to life!
|
||||
|
||||
It can also be used as a jQuery plugin.
|
||||
|
||||
## Install
|
||||
|
||||
You can install Filterizr by downloading the minified version on its website/repo or through npm/yarn:
|
||||
|
||||
```
|
||||
npm install filterizr
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
yarn add filterizr
|
||||
```
|
||||
|
||||
Three files can be found in the `./dist/` directory:
|
||||
|
||||
- `filterizr.min.js` is the pure Filterizr JavaScript library that can be used using CommonJS (or ES6) imports, it is the main entry point when installing via npm.
|
||||
- `vanilla.filterizr.min.js` is a distribution of Filterizr which can be used directly in a `<script>` tag, as it exposes the Filterizr library as a global.
|
||||
- `jquery.filterizr.min.js` is a distribution of Filterizr as a jQuery plugin. It detects and extends the global jQuery object and also exposes the vanilla Filterizr library as a global.
|
||||
|
||||
## Documentation & Tutorials
|
||||
|
||||
### Basic usage
|
||||
|
||||
If you are building a simple static website and would like to use Filterizr via `<script>` tag make sure to download `/dist/vanilla.filterizr.min.js` and load it.
|
||||
|
||||
If you would like to use the jQuery variation then include `/dist/jquery.filterizr.min.js` in a `<script>` tag. Make sure to import it after jQuery itself.
|
||||
|
||||
If you are importing Filterizr from `/node_modules`, Filterizr's default export provide you with the vanilla JavaScript library:
|
||||
|
||||
```
|
||||
import Filterizr from 'filterizr'
|
||||
```
|
||||
|
||||
If you are using jQuery in that same project, also installed via npm and you would like to use Filterizr as a jQuery plugin then you can call the static method `Filterizr.installAsJQueryPlugin`, passing in the jQuery object as follows:
|
||||
|
||||
```
|
||||
import $ from 'jquery';
|
||||
import Filterizr from 'filterizr';
|
||||
|
||||
// This will extend the $.fn prototype with Filterizr
|
||||
Filterizr.installAsJQueryPlugin($);
|
||||
|
||||
$('.filter-container').filterizr('filter', 5); // or any other Filterizr API call
|
||||
```
|
||||
|
||||
### Tutorials & Docs
|
||||
|
||||
You can find Filterizr's documentation as well as a series of tutorials on how to install and operate Filterizr on [the Filterizr website](http://yiotis.net/filterizr). Here is a list of contents:
|
||||
|
||||
Contents:
|
||||
|
||||
- Documentation
|
||||
- [Vanilla JavaScript Filterizr](https://yiotis.net/filterizr/#/documentation/vanilla/options)
|
||||
- [jQuery Filterizr](https://yiotis.net/filterizr/#/documentation/jquery/options)
|
||||
- Tutorials
|
||||
- [Getting started](https://yiotis.net/filterizr/#/tutorials/quickstart)
|
||||
- [Filtering](https://yiotis.net/filterizr/#/tutorials/filtering)
|
||||
- [Sorting](https://yiotis.net/filterizr/#/tutorials/sorting)
|
||||
- [Searching](https://yiotis.net/filterizr/#/tutorials/searching)
|
||||
- [Delay modes](https://yiotis.net/filterizr/#/tutorials/delay-modes)
|
||||
- [Layouts](https://yiotis.net/filterizr/#/tutorials/layouts)
|
||||
- [Loading spinner](https://yiotis.net/filterizr/#/tutorials/loading-animation)
|
||||
- [Using as jQuery plugin](https://yiotis.net/filterizr/#/tutorials/as-jquery-plugin)
|
||||
- [FAQ & Caveats](https://yiotis.net/filterizr/#/faq)
|
||||
|
||||
## Why Filterizr?
|
||||
|
||||
- Thoroughly documented and easy to use.
|
||||
- Performs great on mobile.
|
||||
- Written in TypeScript.
|
||||
- Has zero dependencies.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you would like to contribute to Filterizr, please make sure to follow the steps described:
|
||||
|
||||
- Create a fork from `master` branch
|
||||
- Add your feature or bug fix
|
||||
- If you're implementing a method, add tests
|
||||
- Run the tests and ESLint to make sure everything is ok
|
||||
- Make your PR and set the base branch to `develop`
|
||||
|
||||
## Browser support
|
||||
|
||||
IE11 and all modern browsers.
|
||||
|
||||
## Donate
|
||||
|
||||
Did you enjoy Filterizr? Filterizr is and will always be 100% free, if you would like to show your support feel free to donate:
|
||||
|
||||
- **BTC**: 1JdpKt3aeNQuKF9CrUKeq3XkPswcqgAFpt
|
||||
- **ETH**: 0xdb259cf059faf286e5834e95c8f3a973438276e8
|
||||
- **Paypal**: https://www.paypal.me/yiotiskl
|
||||
|
||||
## License
|
||||
|
||||
Filterizr is licensed under [the MIT License](https://opensource.org/licenses/MIT). Enjoy!
|
BIN
filterizr/v2/demo/img/city_1.jpg
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
filterizr/v2/demo/img/city_2.jpg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
filterizr/v2/demo/img/city_3.jpg
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
filterizr/v2/demo/img/industrial_1.jpg
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
filterizr/v2/demo/img/industrial_2.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
filterizr/v2/demo/img/industrial_3.jpg
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
filterizr/v2/demo/img/nature_1.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
filterizr/v2/demo/img/nature_2.jpg
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
filterizr/v2/demo/img/nature_3.jpg
Normal file
After Width: | Height: | Size: 79 KiB |
145
filterizr/v2/demo/index.css
Normal file
@ -0,0 +1,145 @@
|
||||
body {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.filtr-item {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 767px) {
|
||||
.filtr-item {
|
||||
width: 33.3%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 991px) {
|
||||
.filtr-item {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
/* Filter controls */
|
||||
.simplefilter,
|
||||
.multifilter,
|
||||
.sortandshuffle,
|
||||
.search-row {
|
||||
padding-left: 0;
|
||||
}
|
||||
.simplefilter li,
|
||||
.multifilter li,
|
||||
.sortandshuffle li {
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
text-transform: uppercase;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
.simplefilter li {
|
||||
background-color: #585858;
|
||||
}
|
||||
.simplefilter li.active {
|
||||
background-color: #212121;
|
||||
}
|
||||
.multifilter li {
|
||||
background-color: #4b9eff;
|
||||
}
|
||||
.multifilter li.active {
|
||||
background-color: #2265b4;
|
||||
}
|
||||
|
||||
/* Shuffle and sort controls */
|
||||
select {
|
||||
padding: 1rem 1rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.sortandshuffle .shuffle-btn {
|
||||
background-color: #dec800;
|
||||
}
|
||||
.sortandshuffle .sort-btn {
|
||||
background-color: #de0000;
|
||||
}
|
||||
.sortandshuffle .sort-btn.active {
|
||||
background-color: #9d0000;
|
||||
}
|
||||
|
||||
/* Search control */
|
||||
.filtr-search {
|
||||
padding: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Colored divs */
|
||||
.block {
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.75) inset;
|
||||
color: white;
|
||||
text-align: center;
|
||||
height: 80px;
|
||||
position: relative;
|
||||
}
|
||||
.block > .text {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.block.xs {
|
||||
height: 40px;
|
||||
}
|
||||
.block.md {
|
||||
height: 120px;
|
||||
}
|
||||
.block.lg {
|
||||
height: 160px;
|
||||
}
|
||||
.block.purple {
|
||||
background-color: purple;
|
||||
}
|
||||
.block.yellow {
|
||||
background-color: #dec800;
|
||||
}
|
||||
.block.red {
|
||||
background-color: crimson;
|
||||
}
|
||||
.block.green {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
.block.blue {
|
||||
background-color: #4b9eff;
|
||||
}
|
||||
|
||||
/* Helpers */
|
||||
.push-down {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.filtr-container {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.filtr-container,
|
||||
.color-container-1,
|
||||
.color-container-2,
|
||||
.color-container-3,
|
||||
.color-container-4 {
|
||||
display: none;
|
||||
transition: height 500ms ease-out;
|
||||
}
|
572
filterizr/v2/demo/index.html
Normal file
@ -0,0 +1,572 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Filterizr Demo - With jQuery in Bundle</title>
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
|
||||
integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link rel="stylesheet" href="./index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Filter Controls - Simple Mode -->
|
||||
<div class="row">
|
||||
<!-- A basic setup of simple mode filter controls, all you have to do is use data-filter="all"
|
||||
for an unfiltered gallery and then the values of your categories to filter between them -->
|
||||
<ul class="simplefilter">
|
||||
Simple filter controls:
|
||||
<li class="fltr-controls active" data-filter="all">All</li>
|
||||
<li class="fltr-controls" data-filter="1">Cityscape</li>
|
||||
<li class="fltr-controls" data-filter="2">Landscape</li>
|
||||
<li class="fltr-controls" data-filter="3">Industrial</li>
|
||||
<li class="fltr-controls" data-filter="4">Daylight</li>
|
||||
<li class="fltr-controls" data-filter="5">Nightscape</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Filter Controls - Multifilter Mode -->
|
||||
<div class="row">
|
||||
<!-- A basic setup of multifilter controls, when the user toggles a category
|
||||
the corresponding items are rendered or hidden -->
|
||||
<ul class="multifilter">
|
||||
Multifilter controls:
|
||||
<li class="fltr-controls" data-multifilter="1">Cityscape</li>
|
||||
<li class="fltr-controls" data-multifilter="2">Landscape</li>
|
||||
<li class="fltr-controls" data-multifilter="3">Industrial</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Shuffle & Sort Controls -->
|
||||
<div class="row">
|
||||
<ul class="sortandshuffle">
|
||||
Sort & Shuffle controls:
|
||||
<!-- Basic shuffle control -->
|
||||
<li class="fltr-controls shuffle-btn" data-shuffle>Shuffle</li>
|
||||
<!-- Basic sort controls consisting of asc/desc button and a select -->
|
||||
<li class="fltr-controls sort-btn active" data-sortAsc>Asc</li>
|
||||
<li class="fltr-controls sort-btn" data-sortDesc>Desc</li>
|
||||
<select class="fltr-controls" data-sortOrder>
|
||||
<option value="index">
|
||||
Position
|
||||
</option>
|
||||
<option value="sortData">
|
||||
Description
|
||||
</option>
|
||||
</select>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Search control -->
|
||||
<div class="row search-row">
|
||||
Search control:
|
||||
<input
|
||||
type="text"
|
||||
class="fltr-controls filtr-search"
|
||||
name="filtr-search"
|
||||
data-search
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="row push-down">
|
||||
<!-- This is the set up of a basic gallery, your items must have the categories they belong to in a data-category
|
||||
attribute, which starts from the value 1 and goes up from there -->
|
||||
<div class="filtr-container">
|
||||
<div class="filtr-item" data-category="1, 5" data-sort="Busy streets">
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/city_1.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">Busy Streets</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item"
|
||||
data-category="2, 5"
|
||||
data-sort="Luminous night"
|
||||
>
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/nature_2.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">Luminous night</span>
|
||||
</div>
|
||||
<div class="filtr-item" data-category="1, 4" data-sort="City wonders">
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/city_3.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">city wonders</span>
|
||||
</div>
|
||||
<div class="filtr-item" data-category="3" data-sort="In production">
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/industrial_1.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">in production</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item"
|
||||
data-category="3, 4"
|
||||
data-sort="Industrial site"
|
||||
>
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/industrial_2.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">industrial site</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item"
|
||||
data-category="2, 4"
|
||||
data-sort="Peaceful lake"
|
||||
>
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/nature_1.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">peaceful lake</span>
|
||||
</div>
|
||||
<div class="filtr-item" data-category="1, 5" data-sort="City lights">
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/city_2.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">city lights</span>
|
||||
</div>
|
||||
<div class="filtr-item" data-category="2, 4" data-sort="Dreamhouse">
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/nature_3.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">dreamhouse</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item"
|
||||
data-category="3"
|
||||
data-sort="Restless machines"
|
||||
>
|
||||
<img
|
||||
class="img-responsive"
|
||||
src="./img/industrial_3.jpg"
|
||||
alt="sample image"
|
||||
/>
|
||||
<span class="item-desc">restless machines</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Same size layout -->
|
||||
<div class="row push-down">
|
||||
<h3>Same size layout</h3>
|
||||
<div class="color-controls-1">
|
||||
<ul class="simplefilter">
|
||||
<li class="color-controls-1 active" data-filter="all">All</li>
|
||||
<li class="color-controls-1" data-filter="red">Red</li>
|
||||
<li class="color-controls-1" data-filter="yellow">Yellow</li>
|
||||
<li class="color-controls-1" data-filter="green">Green</li>
|
||||
<li class="color-controls-1" data-filter="purple">Purple</li>
|
||||
<li class="color-controls-1" data-filter="blue">Blue</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="color-container-1 col-xs-12">
|
||||
<div class="filtr-item block red col-sm-3" data-category="red">
|
||||
<span class="text">01</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-3" data-category="blue">
|
||||
<span class="text">02</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-3" data-category="green">
|
||||
<span class="text">03</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-3" data-category="purple">
|
||||
<span class="text">04</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-3" data-category="purple">
|
||||
<span class="text">05</span>
|
||||
</div>
|
||||
<div class="filtr-item block yellow col-sm-3" data-category="yellow">
|
||||
<span class="text">06</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-3" data-category="green">
|
||||
<span class="text">07</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-3" data-category="green">
|
||||
<span class="text">08</span>
|
||||
</div>
|
||||
<div class="filtr-item block yellow col-sm-3" data-category="yellow">
|
||||
<span class="text">09</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-3" data-category="purple">
|
||||
<span class="text">10</span>
|
||||
</div>
|
||||
<div class="filtr-item block red col-sm-3" data-category="red">
|
||||
<span class="text">11</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-3" data-category="blue">
|
||||
<span class="text">12</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Same width layout -->
|
||||
<div class="row push-down">
|
||||
<h3>Same width layout</h3>
|
||||
<div class="color-controls-2">
|
||||
<ul class="simplefilter">
|
||||
<li class="color-controls-2 active" data-filter="all">All</li>
|
||||
<li class="color-controls-2" data-filter="red">Red</li>
|
||||
<li class="color-controls-2" data-filter="yellow">Yellow</li>
|
||||
<li class="color-controls-2" data-filter="green">Green</li>
|
||||
<li class="color-controls-2" data-filter="purple">Purple</li>
|
||||
<li class="color-controls-2" data-filter="blue">Blue</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="color-container-2 col-xs-12">
|
||||
<div class="filtr-item block green xs col-sm-4" data-category="green">
|
||||
<span class="text">01</span>
|
||||
</div>
|
||||
<div class="filtr-item block red lg col-sm-4" data-category="red">
|
||||
<span class="text">02</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue md col-sm-4" data-category="blue">
|
||||
<span class="text">03</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-4" data-category="green">
|
||||
<span class="text">04</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">05</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item block purple xs col-sm-4"
|
||||
data-category="purple"
|
||||
>
|
||||
<span class="text">06</span>
|
||||
</div>
|
||||
<div class="filtr-item block red lg col-sm-4" data-category="red">
|
||||
<span class="text">07</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item block yellow md col-sm-4"
|
||||
data-category="yellow"
|
||||
>
|
||||
<span class="text">08</span>
|
||||
</div>
|
||||
<div class="filtr-item block red md col-sm-4" data-category="red">
|
||||
<span class="text">09</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-4" data-category="green">
|
||||
<span class="text">10</span>
|
||||
</div>
|
||||
<div class="filtr-item block green lg col-sm-4" data-category="green">
|
||||
<span class="text">11</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item block yellow xs col-sm-4"
|
||||
data-category="yellow"
|
||||
>
|
||||
<span class="text">12</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">13</span>
|
||||
</div>
|
||||
<div class="filtr-item block red col-sm-4" data-category="red">
|
||||
<span class="text">14</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue lg col-sm-4" data-category="blue">
|
||||
<span class="text">15</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Same height layout -->
|
||||
<div class="row push-down">
|
||||
<h3>Same height layout</h3>
|
||||
<div class="color-controls-3">
|
||||
<ul class="simplefilter">
|
||||
<li class="color-controls-3 active" data-filter="all">All</li>
|
||||
<li class="color-controls-3" data-filter="red">Red</li>
|
||||
<li class="color-controls-3" data-filter="yellow">Yellow</li>
|
||||
<li class="color-controls-3" data-filter="green">Green</li>
|
||||
<li class="color-controls-3" data-filter="purple">Purple</li>
|
||||
<li class="color-controls-3" data-filter="blue">Blue</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="color-container-3 col-xs-12">
|
||||
<div class="filtr-item block red col-sm-8" data-category="red">
|
||||
<span class="text">01</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-4" data-category="blue">
|
||||
<span class="text">02</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-2" data-category="green">
|
||||
<span class="text">03</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">04</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">05</span>
|
||||
</div>
|
||||
<div class="filtr-item block yellow col-sm-2" data-category="yellow">
|
||||
<span class="text">06</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-12" data-category="green">
|
||||
<span class="text">07</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-4" data-category="green">
|
||||
<span class="text">08</span>
|
||||
</div>
|
||||
<div class="filtr-item block yellow col-sm-2" data-category="yellow">
|
||||
<span class="text">09</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">10</span>
|
||||
</div>
|
||||
<div class="filtr-item block red col-sm-4" data-category="red">
|
||||
<span class="text">11</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-8" data-category="blue">
|
||||
<span class="text">12</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Packed layout -->
|
||||
<div class="row push-down">
|
||||
<h3>Packed layout</h3>
|
||||
<div class="color-controls-4">
|
||||
<ul class="simplefilter">
|
||||
<li class="color-controls-4 active" data-filter="all">All</li>
|
||||
<li class="color-controls-4" data-filter="red">Red</li>
|
||||
<li class="color-controls-4" data-filter="yellow">Yellow</li>
|
||||
<li class="color-controls-4" data-filter="green">Green</li>
|
||||
<li class="color-controls-4" data-filter="purple">Purple</li>
|
||||
<li class="color-controls-4" data-filter="blue">Blue</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="color-container-4 col-xs-12">
|
||||
<div class="filtr-item block red xs col-sm-8" data-category="red">
|
||||
<span class="text">01</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-4" data-category="blue">
|
||||
<span class="text">02</span>
|
||||
</div>
|
||||
<div class="filtr-item block green md col-sm-2" data-category="green">
|
||||
<span class="text">03</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">04</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">05</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item block yellow md col-sm-2"
|
||||
data-category="yellow"
|
||||
>
|
||||
<span class="text">06</span>
|
||||
</div>
|
||||
<div class="filtr-item block green md col-sm-6" data-category="green">
|
||||
<span class="text">07</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-4" data-category="green">
|
||||
<span class="text">08</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item block yellow lg col-sm-2"
|
||||
data-category="yellow"
|
||||
>
|
||||
<span class="text">09</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">10</span>
|
||||
</div>
|
||||
<div class="filtr-item block red xs col-sm-4" data-category="red">
|
||||
<span class="text">11</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-8" data-category="blue">
|
||||
<span class="text">12</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Horizontal layout -->
|
||||
<div class="row push-down">
|
||||
<h3>Horizontal layout</h3>
|
||||
<div class="color-controls-5">
|
||||
<ul class="simplefilter">
|
||||
<li class="color-controls-5 active" data-filter="all">All</li>
|
||||
<li class="color-controls-5" data-filter="red">Red</li>
|
||||
<li class="color-controls-5" data-filter="yellow">Yellow</li>
|
||||
<li class="color-controls-5" data-filter="green">Green</li>
|
||||
<li class="color-controls-5" data-filter="purple">Purple</li>
|
||||
<li class="color-controls-5" data-filter="blue">Blue</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="color-container-5 col-xs-12">
|
||||
<div class="filtr-item block red xs col-sm-2" data-category="red">
|
||||
<span class="text">01</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-4" data-category="blue">
|
||||
<span class="text">02</span>
|
||||
</div>
|
||||
<div class="filtr-item block green md col-sm-2" data-category="green">
|
||||
<span class="text">03</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">04</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vertical layout -->
|
||||
<div class="row push-down">
|
||||
<h3>Vertical layout</h3>
|
||||
<div class="color-controls-6">
|
||||
<ul class="simplefilter">
|
||||
<li class="color-controls-6 active" data-filter="all">All</li>
|
||||
<li class="color-controls-6" data-filter="red">Red</li>
|
||||
<li class="color-controls-6" data-filter="yellow">Yellow</li>
|
||||
<li class="color-controls-6" data-filter="green">Green</li>
|
||||
<li class="color-controls-6" data-filter="purple">Purple</li>
|
||||
<li class="color-controls-6" data-filter="blue">Blue</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="color-container-6 col-xs-12">
|
||||
<div class="filtr-item block red xs col-sm-8" data-category="red">
|
||||
<span class="text">01</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-4" data-category="blue">
|
||||
<span class="text">02</span>
|
||||
</div>
|
||||
<div class="filtr-item block green md col-sm-2" data-category="green">
|
||||
<span class="text">03</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">04</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">05</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item block yellow md col-sm-2"
|
||||
data-category="yellow"
|
||||
>
|
||||
<span class="text">06</span>
|
||||
</div>
|
||||
<div class="filtr-item block green md col-sm-6" data-category="green">
|
||||
<span class="text">07</span>
|
||||
</div>
|
||||
<div class="filtr-item block green col-sm-4" data-category="green">
|
||||
<span class="text">08</span>
|
||||
</div>
|
||||
<div
|
||||
class="filtr-item block yellow lg col-sm-2"
|
||||
data-category="yellow"
|
||||
>
|
||||
<span class="text">09</span>
|
||||
</div>
|
||||
<div class="filtr-item block purple col-sm-4" data-category="purple">
|
||||
<span class="text">10</span>
|
||||
</div>
|
||||
<div class="filtr-item block red xs col-sm-4" data-category="red">
|
||||
<span class="text">11</span>
|
||||
</div>
|
||||
<div class="filtr-item block blue col-sm-8" data-category="blue">
|
||||
<span class="text">12</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.4.1.min.js"
|
||||
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<script type="text/javascript" src="filterizr.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
const simpleFilters = document.querySelectorAll('.simplefilter li');
|
||||
Array.from(simpleFilters).forEach((node) =>
|
||||
node.addEventListener('click', function() {
|
||||
simpleFilters.forEach((filter) => filter.classList.remove('active'));
|
||||
node.classList.add('active');
|
||||
})
|
||||
);
|
||||
|
||||
const multiFilters = document.querySelectorAll('.multifilter li');
|
||||
Array.from(multiFilters).forEach((node) =>
|
||||
node.addEventListener('click', function() {
|
||||
node.classList.toggle('active');
|
||||
})
|
||||
);
|
||||
|
||||
const sortControls = document.querySelectorAll('.sort-btn');
|
||||
Array.from(sortControls).forEach((node) =>
|
||||
node.addEventListener('click', function() {
|
||||
sortControls.forEach((control) => control.classList.remove('active'));
|
||||
node.classList.add('active');
|
||||
})
|
||||
);
|
||||
|
||||
const shuffleControl = document.querySelector('.shuffle-btn');
|
||||
shuffleControl.addEventListener('click', function() {
|
||||
sortControls.forEach((control) => control.classList.remove('active'));
|
||||
});
|
||||
|
||||
// Expose this filterizr as a global for debugging
|
||||
window.filterizr = new window.Filterizr('.filtr-container', {
|
||||
controlsSelector: '.fltr-controls',
|
||||
gutterPixels: 15,
|
||||
spinner: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
new window.Filterizr('.color-container-1', {
|
||||
controlsSelector: '.color-controls-1',
|
||||
gutterPixels: 10,
|
||||
});
|
||||
new window.Filterizr('.color-container-2', {
|
||||
controlsSelector: '.color-controls-2',
|
||||
layout: 'sameWidth',
|
||||
gutterPixels: 10,
|
||||
});
|
||||
new window.Filterizr('.color-container-3', {
|
||||
controlsSelector: '.color-controls-3',
|
||||
layout: 'sameHeight',
|
||||
gutterPixels: 10,
|
||||
});
|
||||
new window.Filterizr('.color-container-4', {
|
||||
controlsSelector: '.color-controls-4',
|
||||
layout: 'packed',
|
||||
gutterPixels: 10,
|
||||
});
|
||||
new window.Filterizr('.color-container-5', {
|
||||
controlsSelector: '.color-controls-5',
|
||||
layout: 'horizontal',
|
||||
gutterPixels: 10,
|
||||
});
|
||||
new window.Filterizr('.color-container-6', {
|
||||
controlsSelector: '.color-controls-6',
|
||||
layout: 'vertical',
|
||||
gutterPixels: 10,
|
||||
});
|
||||
</script>
|
||||
</html>
|
12
filterizr/v2/dist/filterizr.min.js
vendored
Normal file
29
filterizr/v2/dist/jquery.filterizr.min.js
vendored
Normal file
12
filterizr/v2/dist/vanilla.filterizr.min.js
vendored
Normal file
BIN
filterizr/v2/filterizr_logo.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
7
filterizr/v2/jest.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
/* global module */
|
||||
module.exports = {
|
||||
roots: ['<rootDir>/tests', '<rootDir>/src'],
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
},
|
||||
};
|
72
filterizr/v2/package.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "filterizr",
|
||||
"version": "2.2.4",
|
||||
"description": "Filterizr is a jQuery plugin that sorts, shuffles, searches and applies stunning filters over responsive galleries using CSS3 transitions and custom CSS effects",
|
||||
"main": "dist/filterizr.min.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": "Yiotis Kaltsikis <yiotiskal@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/giotiskl/filterizr/issues"
|
||||
},
|
||||
"homepage": "https://yiotis.net/filterizr",
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --env.env=development --mode development",
|
||||
"start-with-jquery": "webpack-dev-server --progress --inline --hot --env.env=development --env.withjquery=false",
|
||||
"build": "webpack -p --progress --env.env=production && webpack -p --progress --env.env=production --env.buildTarget=var",
|
||||
"build-commonjs": "webpack -p --progress --env.env=production",
|
||||
"build-var": "webpack -p --progress --env.env=production --env.buildTarget=var",
|
||||
"test": "jest",
|
||||
"coverage": "jest --coverage",
|
||||
"showcoverage": "xdg-open ./coverage/lcov-report/index.html",
|
||||
"lint": "tsc --noEmit && eslint '*/**/*.{js,ts,tsx}'",
|
||||
"preversion": "yarn test",
|
||||
"version": "yarn build",
|
||||
"postversion": "git push && git push --tags",
|
||||
"watch": "webpack --watch"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/giotiskl/Filterizr.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"fast-memoize": "^2.5.1",
|
||||
"imagesloaded": "^4.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.15",
|
||||
"@types/jquery": "^3.3.30",
|
||||
"@typescript-eslint/eslint-plugin": "^1.12.0",
|
||||
"@typescript-eslint/parser": "^1.12.0",
|
||||
"eslint": "^6.0.1",
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"jest": "^24.8.0",
|
||||
"jquery": "^3.4.1",
|
||||
"prettier": "^1.18.2",
|
||||
"ts-jest": "^24.0.2",
|
||||
"ts-loader": "^6.0.4",
|
||||
"typescript": "^3.5.3",
|
||||
"webpack": "^4.35.3",
|
||||
"webpack-cli": "^3.3.6",
|
||||
"webpack-dev-server": "^3.7.2"
|
||||
},
|
||||
"keywords": [
|
||||
"filter",
|
||||
"shuffle",
|
||||
"search",
|
||||
"sort",
|
||||
"filterizr",
|
||||
"gallery",
|
||||
"responsive",
|
||||
"css3",
|
||||
"css",
|
||||
"lightweight",
|
||||
"customizable",
|
||||
"Yiotis",
|
||||
"javascript",
|
||||
"library",
|
||||
"jquery-plugin",
|
||||
"ecosystem:jquery"
|
||||
]
|
||||
}
|
54
filterizr/v2/src/ActiveFilter.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Filter } from './types';
|
||||
|
||||
/**
|
||||
* ActiveFilter represents the currently active filter over
|
||||
* the grid.
|
||||
*
|
||||
* It can be a plain string value or an array of strings.
|
||||
*/
|
||||
export default class ActiveFilter {
|
||||
private filter: Filter;
|
||||
|
||||
public constructor(filter: Filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public get(): Filter {
|
||||
return this.filter;
|
||||
}
|
||||
|
||||
public set(targetFilter: Filter): void {
|
||||
this.filter = targetFilter;
|
||||
}
|
||||
|
||||
public toggle(targetFilter: string): void {
|
||||
this.filter = this.toggleFilter(this.filter, targetFilter);
|
||||
}
|
||||
|
||||
private toggleFilter(
|
||||
activeFilter: Filter,
|
||||
targetFilter: string
|
||||
): string | string[] {
|
||||
if (activeFilter === 'all') {
|
||||
return targetFilter;
|
||||
}
|
||||
|
||||
if (Array.isArray(activeFilter)) {
|
||||
if (activeFilter.includes(targetFilter)) {
|
||||
const newActiveFilter = activeFilter.filter(
|
||||
(filter): boolean => filter !== targetFilter
|
||||
);
|
||||
return newActiveFilter.length === 1
|
||||
? newActiveFilter[0]
|
||||
: newActiveFilter;
|
||||
}
|
||||
return [...activeFilter, targetFilter];
|
||||
}
|
||||
|
||||
if (activeFilter === targetFilter) {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
return [activeFilter, targetFilter];
|
||||
}
|
||||
}
|
71
filterizr/v2/src/EventReceiver.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { Destructible, Dictionary } from './types/interfaces';
|
||||
|
||||
type Receiver = NodeListOf<Element> | Element | Window;
|
||||
|
||||
export default class EventReceiver implements Destructible {
|
||||
private receiver: Receiver;
|
||||
private eventDictionary: Dictionary;
|
||||
|
||||
public constructor(receiver: Receiver) {
|
||||
this.receiver = receiver;
|
||||
this.eventDictionary = {};
|
||||
}
|
||||
|
||||
public on(eventType: string, eventHandler: EventListener): void {
|
||||
const { receiver } = this;
|
||||
const receiversAreMany = receiver instanceof NodeList;
|
||||
|
||||
const eventExists = !!this.eventDictionary[eventType];
|
||||
if (eventExists) {
|
||||
delete this.eventDictionary[eventType];
|
||||
}
|
||||
|
||||
if (receiversAreMany && !!(receiver as NodeList).length) {
|
||||
this.eventDictionary[eventType] = eventHandler;
|
||||
Array.from(receiver as NodeList).forEach((node: Element): void => {
|
||||
node.addEventListener(eventType, eventHandler);
|
||||
});
|
||||
} else if (!receiversAreMany && !!receiver) {
|
||||
this.eventDictionary[eventType] = eventHandler;
|
||||
(receiver as Element | Window).addEventListener(eventType, eventHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public off(eventType: string): void {
|
||||
const { receiver } = this;
|
||||
const eventHandler = this.eventDictionary[eventType];
|
||||
const receiversAreMany = receiver instanceof NodeList;
|
||||
|
||||
if (receiversAreMany && !!(receiver as NodeList).length) {
|
||||
Array.from(receiver as NodeList).forEach((node: Element): void => {
|
||||
node.removeEventListener(eventType, eventHandler);
|
||||
});
|
||||
} else if (!receiversAreMany && !!receiver) {
|
||||
(receiver as Element | Window).removeEventListener(
|
||||
eventType,
|
||||
eventHandler
|
||||
);
|
||||
}
|
||||
delete this.eventDictionary[eventType];
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
const { receiver } = this;
|
||||
const receiversAreMany = receiver instanceof NodeList;
|
||||
|
||||
if (receiversAreMany && !!(receiver as NodeList).length) {
|
||||
Array.from(receiver as NodeList).forEach((node: Element): void =>
|
||||
this.removeAllEvents(node)
|
||||
);
|
||||
} else if (!receiversAreMany && !!receiver) {
|
||||
this.removeAllEvents(receiver as Element | Window);
|
||||
}
|
||||
}
|
||||
|
||||
private removeAllEvents(node: Element | Window): void {
|
||||
Object.keys(this.eventDictionary).forEach((key: string): void => {
|
||||
node.removeEventListener(key, this.eventDictionary[key]);
|
||||
delete this.eventDictionary[key];
|
||||
});
|
||||
}
|
||||
}
|
153
filterizr/v2/src/FilterContainer/FilterContainer.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { FILTERIZR_STATE } from '../config';
|
||||
import { FilterizrState } from '../types';
|
||||
import { debounce } from '../utils';
|
||||
import FilterizrOptions from '../FilterizrOptions';
|
||||
import FilterItem from '../FilterItem';
|
||||
import FilterItems from '../FilterItems';
|
||||
import FilterizrElement from '../FilterizrElement';
|
||||
import StyledFilterContainer from './StyledFilterContainer';
|
||||
|
||||
/**
|
||||
* Resembles the grid of items within Filterizr.
|
||||
*/
|
||||
export default class FilterContainer extends FilterizrElement {
|
||||
public filterItems: FilterItems;
|
||||
|
||||
protected styledNode: StyledFilterContainer;
|
||||
private _filterizrState: FilterizrState;
|
||||
|
||||
public constructor(node: Element, options: FilterizrOptions) {
|
||||
if (!node) {
|
||||
throw new Error(
|
||||
'Filterizr: could not initialize container, check the selector or node you passed to the constructor exists.'
|
||||
);
|
||||
}
|
||||
super(node, options);
|
||||
this.styledNode = new StyledFilterContainer(node as HTMLElement, options);
|
||||
this._filterizrState = FILTERIZR_STATE.IDLE;
|
||||
this.styles.initialize();
|
||||
this.filterItems = this.makeFilterItems(this.options);
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
public get styles(): StyledFilterContainer {
|
||||
return this.styledNode;
|
||||
}
|
||||
|
||||
public set filterizrState(filterizrState: FilterizrState) {
|
||||
this._filterizrState = filterizrState;
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
super.destroy();
|
||||
this.unbindEvents();
|
||||
this.filterItems.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn the HTML elements in the grid to FilterItem
|
||||
* instances and return a collection of them.
|
||||
* @throws when no filter items are found in the grid.
|
||||
*/
|
||||
public makeFilterItems(options: FilterizrOptions): FilterItems {
|
||||
const filterItemNodes = Array.from(
|
||||
this.node.querySelectorAll(options.get().gridItemsSelector)
|
||||
);
|
||||
const filterItemsArray = filterItemNodes.map(
|
||||
(node, index): FilterItem => new FilterItem(node, index, options)
|
||||
);
|
||||
|
||||
const filterItems = new FilterItems(filterItemsArray, options);
|
||||
if (!filterItems.length) {
|
||||
throw new Error(
|
||||
"Filterizr: cannot initialize empty container. Make sure the gridItemsSelector option corresponds to the selector of your grid's items"
|
||||
);
|
||||
}
|
||||
|
||||
filterItems.styles.updateWidth();
|
||||
|
||||
return filterItems;
|
||||
}
|
||||
|
||||
public insertItem(node: HTMLElement): void {
|
||||
const nodeModified = node.cloneNode(true) as Element;
|
||||
nodeModified.removeAttribute('style');
|
||||
this.node.appendChild(nodeModified);
|
||||
const filterItem = new FilterItem(
|
||||
nodeModified,
|
||||
this.filterItems.length,
|
||||
this.options
|
||||
);
|
||||
filterItem.styles.enableTransitions();
|
||||
filterItem.styles.updateWidth();
|
||||
this.filterItems.push(filterItem);
|
||||
}
|
||||
|
||||
public removeItem(node: HTMLElement): void {
|
||||
this.filterItems.remove(node);
|
||||
this.node.removeChild(node);
|
||||
}
|
||||
|
||||
public setHeight(newHeight: number): void {
|
||||
this.dimensions.height = newHeight;
|
||||
this.styles.setHeight(newHeight);
|
||||
}
|
||||
|
||||
public bindEvents(): void {
|
||||
const {
|
||||
animationDuration,
|
||||
callbacks,
|
||||
delay,
|
||||
delayMode,
|
||||
gridItemsSelector,
|
||||
} = this.options.get();
|
||||
const animationDelay =
|
||||
delayMode === 'progressive' ? delay * this.filterItems.length : delay;
|
||||
this.eventReceiver.on('transitionend', debounce(
|
||||
(event: Event): void => {
|
||||
const targetIsFilterItem = Array.from(
|
||||
(event.target as HTMLElement).classList
|
||||
).reduce(
|
||||
(acc: boolean, val: string): boolean =>
|
||||
acc || gridItemsSelector.includes(val),
|
||||
false
|
||||
);
|
||||
if (targetIsFilterItem) {
|
||||
switch (this._filterizrState) {
|
||||
case FILTERIZR_STATE.FILTERING:
|
||||
this.trigger('filteringEnd');
|
||||
break;
|
||||
case FILTERIZR_STATE.SORTING:
|
||||
this.trigger('sortingEnd');
|
||||
break;
|
||||
case FILTERIZR_STATE.SHUFFLING:
|
||||
this.trigger('shufflingEnd');
|
||||
break;
|
||||
}
|
||||
this.filterizrState = FILTERIZR_STATE.IDLE;
|
||||
}
|
||||
},
|
||||
animationDuration * 100 + animationDelay,
|
||||
false
|
||||
) as EventListener);
|
||||
// Public Filterizr events
|
||||
this.eventReceiver.on('init', callbacks.onInit);
|
||||
this.eventReceiver.on('filteringStart', callbacks.onFilteringStart);
|
||||
this.eventReceiver.on('filteringEnd', callbacks.onFilteringEnd);
|
||||
this.eventReceiver.on('shufflingStart', callbacks.onShufflingStart);
|
||||
this.eventReceiver.on('shufflingEnd', callbacks.onShufflingEnd);
|
||||
this.eventReceiver.on('sortingStart', callbacks.onSortingStart);
|
||||
this.eventReceiver.on('sortingEnd', callbacks.onSortingEnd);
|
||||
}
|
||||
|
||||
public unbindEvents(): void {
|
||||
this.eventReceiver.off('transitionend');
|
||||
this.eventReceiver.off('init');
|
||||
this.eventReceiver.off('filteringStart');
|
||||
this.eventReceiver.off('filteringEnd');
|
||||
this.eventReceiver.off('shufflingStart');
|
||||
this.eventReceiver.off('shufflingEnd');
|
||||
this.eventReceiver.off('sortingStart');
|
||||
this.eventReceiver.off('sortingEnd');
|
||||
}
|
||||
}
|
20
filterizr/v2/src/FilterContainer/StyledFilterContainer.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {
|
||||
makeInitialStyles,
|
||||
makePaddingStyles,
|
||||
makeHeightStyles,
|
||||
} from './styles';
|
||||
import StyledFilterizrElement from '../StyledFilterizrElement';
|
||||
|
||||
export default class StyledFilterContainer extends StyledFilterizrElement {
|
||||
public initialize(): void {
|
||||
this.set(makeInitialStyles(this.options));
|
||||
}
|
||||
|
||||
public updatePaddings(): void {
|
||||
this.set(makePaddingStyles(this.options));
|
||||
}
|
||||
|
||||
public setHeight(newHeight: number): void {
|
||||
this.set(makeHeightStyles(newHeight));
|
||||
}
|
||||
}
|
1
filterizr/v2/src/FilterContainer/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './FilterContainer';
|
18
filterizr/v2/src/FilterContainer/styles.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import FilterizrOptions from '../FilterizrOptions';
|
||||
|
||||
export const makePaddingStyles = (options: FilterizrOptions): object => ({
|
||||
padding: `${options.get().gutterPixels}px`,
|
||||
});
|
||||
|
||||
export const makeInitialStyles = (options: FilterizrOptions): object => ({
|
||||
...makePaddingStyles(options),
|
||||
position: 'relative',
|
||||
// Needed for flex displays
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
});
|
||||
|
||||
export const makeHeightStyles = (height: number): object => ({
|
||||
height: `${height}px`,
|
||||
});
|
98
filterizr/v2/src/FilterControls.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import Filterizr from './Filterizr';
|
||||
import { debounce } from './utils';
|
||||
import EventReceiver from './EventReceiver';
|
||||
import { Destructible } from './types/interfaces';
|
||||
|
||||
export default class FilterControls implements Destructible {
|
||||
private filterControls: EventReceiver;
|
||||
private filterizr: Filterizr;
|
||||
private multiFilterControls: EventReceiver;
|
||||
private searchControls: EventReceiver;
|
||||
private selector: string;
|
||||
private shuffleControls: EventReceiver;
|
||||
private sortAscControls: EventReceiver;
|
||||
private sortDescControls: EventReceiver;
|
||||
|
||||
/**
|
||||
* @param filterizr keep a ref to the Filterizr object to control actions
|
||||
* @param selector selector of controls in case of multiple Filterizr instances
|
||||
*/
|
||||
public constructor(filterizr: Filterizr, selector: string = '') {
|
||||
this.filterizr = filterizr;
|
||||
this.selector = selector;
|
||||
|
||||
this.filterControls = new EventReceiver(
|
||||
document.querySelectorAll(`${selector}[data-filter]`)
|
||||
);
|
||||
this.multiFilterControls = new EventReceiver(
|
||||
document.querySelectorAll(`${selector}[data-multifilter]`)
|
||||
);
|
||||
this.shuffleControls = new EventReceiver(
|
||||
document.querySelectorAll(`${selector}[data-shuffle]`)
|
||||
);
|
||||
this.searchControls = new EventReceiver(
|
||||
document.querySelectorAll(`${selector}[data-search]`)
|
||||
);
|
||||
this.sortAscControls = new EventReceiver(
|
||||
document.querySelectorAll(`${selector}[data-sortAsc]`)
|
||||
);
|
||||
this.sortDescControls = new EventReceiver(
|
||||
document.querySelectorAll(`${selector}[data-sortDesc]`)
|
||||
);
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.filterControls.destroy();
|
||||
this.multiFilterControls.destroy();
|
||||
this.shuffleControls.destroy();
|
||||
this.searchControls.destroy();
|
||||
this.sortAscControls.destroy();
|
||||
this.sortDescControls.destroy();
|
||||
}
|
||||
|
||||
private initialize(): void {
|
||||
const { filterizr, selector } = this;
|
||||
|
||||
// Filter EventReceiver
|
||||
this.filterControls.on('click', (evt): void => {
|
||||
const ctrl: Element = evt.currentTarget as Element;
|
||||
const targetFilter: string = ctrl.getAttribute('data-filter');
|
||||
filterizr.filter(targetFilter);
|
||||
});
|
||||
this.multiFilterControls.on('click', (evt): void => {
|
||||
const ctrl: Element = evt.target as Element;
|
||||
const targetFilter = ctrl.getAttribute('data-multifilter');
|
||||
filterizr.toggleFilter(targetFilter);
|
||||
});
|
||||
|
||||
// Shuffle EventReceiver
|
||||
this.shuffleControls.on('click', filterizr.shuffle.bind(filterizr));
|
||||
|
||||
// Search EventReceiver
|
||||
this.searchControls.on('keyup', debounce(
|
||||
(evt: Event): void => {
|
||||
const textfield: HTMLInputElement = evt.target as HTMLInputElement;
|
||||
const searchTerm = textfield.value;
|
||||
filterizr.search(searchTerm);
|
||||
},
|
||||
250,
|
||||
false
|
||||
) as EventListener);
|
||||
|
||||
// Sort EventReceiver
|
||||
this.sortAscControls.on('click', (): void => {
|
||||
const sortAttr: string = (document.querySelector(
|
||||
`${selector}[data-sortOrder]`
|
||||
) as HTMLInputElement).value;
|
||||
filterizr.sort(sortAttr, 'asc');
|
||||
});
|
||||
this.sortDescControls.on('click', (): void => {
|
||||
const sortAttr = (document.querySelector(
|
||||
`${selector}[data-sortOrder]`
|
||||
) as HTMLInputElement).value;
|
||||
filterizr.sort(sortAttr, 'desc');
|
||||
});
|
||||
}
|
||||
}
|
106
filterizr/v2/src/FilterItem/FilterItem.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { getDataAttributesOfHTMLNode } from '../utils';
|
||||
import { Dictionary, Position } from '../types/interfaces';
|
||||
import FilterizrOptions from '../FilterizrOptions';
|
||||
import FilterizrElement from '../FilterizrElement';
|
||||
import StyledFilterItem from './StyledFilterItem';
|
||||
|
||||
/**
|
||||
* Resembles an item in the grid of Filterizr.
|
||||
*/
|
||||
export default class FilterItem extends FilterizrElement {
|
||||
protected styledNode: StyledFilterItem;
|
||||
|
||||
private filteredOut: boolean;
|
||||
private lastPosition: Position;
|
||||
private sortData: Dictionary;
|
||||
|
||||
public constructor(node: Element, index: number, options: FilterizrOptions) {
|
||||
super(node, options);
|
||||
this.filteredOut = false;
|
||||
this.lastPosition = { left: 0, top: 0 };
|
||||
this.sortData = {
|
||||
...getDataAttributesOfHTMLNode(node),
|
||||
index,
|
||||
sortData: node.getAttribute('data-sort'),
|
||||
};
|
||||
this.styledNode = new StyledFilterItem(node as HTMLElement, index, options);
|
||||
this.styles.initialize();
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
public get styles(): StyledFilterItem {
|
||||
return this.styledNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the FilterItem instance
|
||||
*/
|
||||
public destroy(): void {
|
||||
super.destroy();
|
||||
this.unbindEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters in a specific FilterItem out of the grid.
|
||||
*/
|
||||
public filterIn(targetPosition: Position): void {
|
||||
const { filterInCss } = this.options.get();
|
||||
this.styles.setFilteredStyles(targetPosition, filterInCss);
|
||||
this.lastPosition = targetPosition;
|
||||
this.filteredOut = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out a specific FilterItem out of the grid.
|
||||
*/
|
||||
public filterOut(): void {
|
||||
const { filterOutCss } = this.options.get();
|
||||
this.styles.setFilteredStyles(this.lastPosition, filterOutCss);
|
||||
this.filteredOut = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the text contents of the FilterItem match the search term
|
||||
* @param searchTerm to look up
|
||||
*/
|
||||
public contentsMatchSearch(searchTerm: string): boolean {
|
||||
return this.node.textContent.toLowerCase().includes(searchTerm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all categories of the grid items data-category attribute
|
||||
* with a regexp regarding all whitespace.
|
||||
*/
|
||||
public getCategories(): string[] {
|
||||
return this.node.getAttribute('data-category').split(/\s*,\s*/g);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the sort attribute
|
||||
* @param sortAttribute "index", "sortData" or custom user data-attribute by which to sort
|
||||
*/
|
||||
public getSortAttribute(sortAttribute: string): string | number {
|
||||
return this.sortData[sortAttribute];
|
||||
}
|
||||
|
||||
protected bindEvents(): void {
|
||||
this.eventReceiver.on('transitionend', (): void => {
|
||||
// On transition end determines if the item is filtered out or not.
|
||||
// It adds a .filteredOut class so that user can target these items
|
||||
// via css if needed. It sets the z-index to -1000 to prevent mouse
|
||||
// events from being triggered.
|
||||
if (this.filteredOut) {
|
||||
this.node.classList.add('filteredOut');
|
||||
this.styles.setZIndex(-1000);
|
||||
this.styles.setHidden();
|
||||
} else {
|
||||
this.node.classList.remove('filteredOut');
|
||||
this.styles.removeZIndex();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected unbindEvents(): void {
|
||||
this.eventReceiver.off('transitionend');
|
||||
}
|
||||
}
|
102
filterizr/v2/src/FilterItem/StyledFilterItem.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import {
|
||||
makeInitialStyles,
|
||||
makeFilteringStyles,
|
||||
makeTransitionStyles,
|
||||
} from './styles';
|
||||
import { Position } from './../types/interfaces';
|
||||
import StyledFilterizrElement from '../StyledFilterizrElement';
|
||||
import FilterizrOptions from '../FilterizrOptions';
|
||||
|
||||
const imagesLoaded = require('imagesloaded');
|
||||
|
||||
export default class StyledFilterItem extends StyledFilterizrElement {
|
||||
private _index: number;
|
||||
|
||||
public constructor(
|
||||
node: HTMLElement,
|
||||
index: number,
|
||||
options: FilterizrOptions
|
||||
) {
|
||||
super(node, options);
|
||||
this._index = index;
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this.set(makeInitialStyles(this.options));
|
||||
}
|
||||
|
||||
public setFilteredStyles(position: Position, cssOptions: object): void {
|
||||
this.set(makeFilteringStyles(position, cssOptions));
|
||||
}
|
||||
|
||||
public updateTransitionStyle(): void {
|
||||
this.set(makeTransitionStyles(this._index, this.options));
|
||||
}
|
||||
|
||||
public updateWidth(): void {
|
||||
const { gutterPixels } = this.options.get();
|
||||
const containerWidth = this.node.parentElement.clientWidth;
|
||||
const itemWidth = this.node.clientWidth;
|
||||
const timesItFitsContainer = Math.floor(containerWidth / itemWidth);
|
||||
const gutterRatio = 1 / timesItFitsContainer + 1;
|
||||
const width = `${itemWidth - gutterPixels * gutterRatio}px`;
|
||||
this.set({ width });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transition css property as an inline style on the FilterItem.
|
||||
*
|
||||
* The idea here is that during the very first render items should assume
|
||||
* their positions directly.
|
||||
*
|
||||
* Following renders should actually trigger the transitions, which is why
|
||||
* we need to delay setting the transition property.
|
||||
*
|
||||
* Unfortunately, JavaScript code executes on the same thread as the
|
||||
* browser's rendering. Everything that needs to be drawn waits for
|
||||
* JavaScript execution to complete. Thus, we need to use a setTimeout
|
||||
* here to defer setting the transition style at the first rendering cycle.
|
||||
*/
|
||||
public async enableTransitions(): Promise<void> {
|
||||
return new Promise((resolve): void => {
|
||||
const hasImage = !!this.node.querySelectorAll('img').length;
|
||||
if (hasImage) {
|
||||
imagesLoaded(this.node, (): void => {
|
||||
setTimeout((): void => {
|
||||
this.updateTransitionStyle();
|
||||
resolve();
|
||||
}, 10);
|
||||
});
|
||||
} else {
|
||||
setTimeout((): void => {
|
||||
this.updateTransitionStyle();
|
||||
resolve();
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public disableTransitions(): void {
|
||||
this.remove('transition');
|
||||
}
|
||||
|
||||
public setZIndex(zIndex: number): void {
|
||||
this.set({ 'z-index': zIndex });
|
||||
}
|
||||
|
||||
public removeZIndex(): void {
|
||||
this.remove('z-index');
|
||||
}
|
||||
|
||||
public removeWidth(): void {
|
||||
this.remove('width');
|
||||
}
|
||||
|
||||
public setHidden(): void {
|
||||
this.set({ display: 'none' });
|
||||
}
|
||||
|
||||
public setVisible(): void {
|
||||
this.remove('display');
|
||||
}
|
||||
}
|
1
filterizr/v2/src/FilterItem/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './FilterItem';
|
45
filterizr/v2/src/FilterItem/styles.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import FilterizrOptions from '../FilterizrOptions';
|
||||
import { Dictionary, Position } from '../types/interfaces';
|
||||
|
||||
function getTransitionDelay(index: number, options: FilterizrOptions): number {
|
||||
const { delay, delayMode } = options.get();
|
||||
if (delayMode === 'progressive') {
|
||||
return delay * index;
|
||||
}
|
||||
if (index % 2 === 0) {
|
||||
return delay;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export const makeInitialStyles = (options: FilterizrOptions): object =>
|
||||
Object.assign({}, options.get().filterOutCss, {
|
||||
'-webkit-backface-visibility': 'hidden',
|
||||
perspective: '1000px',
|
||||
'-webkit-perspective': '1000px',
|
||||
'-webkit-transform-style': 'preserve-3d',
|
||||
position: 'absolute',
|
||||
});
|
||||
|
||||
export const makeFilteringStyles = (
|
||||
targetPosition: Position,
|
||||
cssOptions: Dictionary
|
||||
): object =>
|
||||
Object.assign({}, cssOptions, {
|
||||
transform: `${cssOptions.transform || ''} translate3d(${
|
||||
targetPosition.left
|
||||
}px, ${targetPosition.top}px, 0)`,
|
||||
});
|
||||
|
||||
export const makeTransitionStyles = (
|
||||
index: number,
|
||||
options: FilterizrOptions
|
||||
): object => {
|
||||
const { animationDuration, easing } = options.get();
|
||||
return {
|
||||
transition: `all ${animationDuration}s ${easing} ${getTransitionDelay(
|
||||
index,
|
||||
options
|
||||
)}ms, width 1ms`,
|
||||
};
|
||||
};
|
139
filterizr/v2/src/FilterItems/FilterItems.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import StyledFilterItems from './StyledFilterItems';
|
||||
import { Filter } from '../types';
|
||||
import FilterItem from '../FilterItem';
|
||||
import FilterizrOptions from '../FilterizrOptions/FilterizrOptions';
|
||||
import {
|
||||
allStringsOfArray1InArray2,
|
||||
filterItemArraysHaveSameSorting,
|
||||
intersection,
|
||||
shuffle,
|
||||
sortBy,
|
||||
} from '../utils';
|
||||
import { Destructible, Styleable } from '../types/interfaces';
|
||||
|
||||
export default class FilterItems implements Destructible, Styleable {
|
||||
private filterItems: FilterItem[];
|
||||
private styledFilterItems: StyledFilterItems;
|
||||
private options: FilterizrOptions;
|
||||
|
||||
public constructor(filterItems: FilterItem[], options: FilterizrOptions) {
|
||||
this.filterItems = filterItems;
|
||||
this.styledFilterItems = new StyledFilterItems(filterItems);
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public get styles(): StyledFilterItems {
|
||||
return this.styledFilterItems;
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this.filterItems.length;
|
||||
}
|
||||
|
||||
public getItem(index: number): FilterItem {
|
||||
return this.filterItems[index];
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.filterItems.forEach((filterItem): void => filterItem.destroy());
|
||||
}
|
||||
|
||||
public push(filterItem: FilterItem): number {
|
||||
return this.filterItems.push(filterItem);
|
||||
}
|
||||
|
||||
public remove(node: HTMLElement): void {
|
||||
this.filterItems = this.filterItems.filter(
|
||||
({ node: filterItemNode }): boolean => filterItemNode !== node
|
||||
);
|
||||
}
|
||||
|
||||
public getFiltered(filter: Filter): FilterItem[] {
|
||||
const { searchTerm } = this.options;
|
||||
const searchedFilterItems = this.search(this.filterItems, searchTerm);
|
||||
if (filter === 'all') {
|
||||
return searchedFilterItems;
|
||||
}
|
||||
return searchedFilterItems.filter((filterItem): boolean =>
|
||||
this.shouldBeFiltered(filterItem.getCategories(), filter)
|
||||
);
|
||||
}
|
||||
|
||||
public getFilteredOut(filter: Filter): FilterItem[] {
|
||||
const { searchTerm } = this.options;
|
||||
return this.filterItems.filter((filterItem: FilterItem): boolean => {
|
||||
const categories = filterItem.getCategories();
|
||||
const shouldBeFiltered = this.shouldBeFiltered(categories, filter);
|
||||
const contentsMatchSearch = filterItem.contentsMatchSearch(searchTerm);
|
||||
return !shouldBeFiltered || !contentsMatchSearch;
|
||||
});
|
||||
}
|
||||
|
||||
public sort(
|
||||
sortAttr: string = 'index',
|
||||
sortOrder: 'asc' | 'desc' = 'asc'
|
||||
): void {
|
||||
const sortedItems = sortBy(this.filterItems, (filterItem: FilterItem):
|
||||
| string
|
||||
| number => filterItem.getSortAttribute(sortAttr));
|
||||
|
||||
const orderedSortedItems =
|
||||
sortOrder === 'asc' ? sortedItems : sortedItems.reverse();
|
||||
|
||||
this.filterItems = orderedSortedItems;
|
||||
}
|
||||
|
||||
public shuffle(): void {
|
||||
const filteredItems = this.getFiltered(this.options.filter);
|
||||
|
||||
if (filteredItems.length > 1) {
|
||||
const indicesBeforeShuffling = this.getFiltered(this.options.filter)
|
||||
.map((filterItem): number => this.filterItems.indexOf(filterItem))
|
||||
.slice();
|
||||
|
||||
// Shuffle filtered items (until they have a new order)
|
||||
let shuffledItems;
|
||||
do {
|
||||
shuffledItems = shuffle(filteredItems);
|
||||
} while (filterItemArraysHaveSameSorting(filteredItems, shuffledItems));
|
||||
{
|
||||
shuffledItems = shuffle(filteredItems);
|
||||
}
|
||||
|
||||
// Update filterItems to have them in the new shuffled order
|
||||
shuffledItems.forEach((filterItem, index): void => {
|
||||
const newIndex = indicesBeforeShuffling[index];
|
||||
this.filterItems = Object.assign([], this.filterItems, {
|
||||
[newIndex]: filterItem,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private search(
|
||||
filteredItems: FilterItem[],
|
||||
searchTerm: string
|
||||
): FilterItem[] {
|
||||
if (!searchTerm) {
|
||||
return filteredItems;
|
||||
}
|
||||
return filteredItems.filter((filterItem: FilterItem): boolean =>
|
||||
filterItem.contentsMatchSearch(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
private shouldBeFiltered(categories: string[], filter: Filter): boolean {
|
||||
const { multifilterLogicalOperator } = this.options.getRaw();
|
||||
const isMultifilteringEnabled = Array.isArray(filter);
|
||||
|
||||
if (!isMultifilteringEnabled) {
|
||||
return categories.includes(filter as string);
|
||||
}
|
||||
|
||||
if (multifilterLogicalOperator === 'or') {
|
||||
return !!intersection(categories, filter as string[]).length;
|
||||
}
|
||||
|
||||
return allStringsOfArray1InArray2(filter as string[], categories);
|
||||
}
|
||||
}
|
49
filterizr/v2/src/FilterItems/StyledFilterItems.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import StyledFilterItem from '../FilterItem/StyledFilterItem';
|
||||
import StyledFilterizrElements from '../StyledFilterizrElements';
|
||||
import FilterItem from '../FilterItem/FilterItem';
|
||||
|
||||
export default class StyledFilterItems extends StyledFilterizrElements {
|
||||
private _filterItems: StyledFilterItem[];
|
||||
|
||||
public constructor(elements: FilterItem[]) {
|
||||
super();
|
||||
this._filterItems = elements.map(({ styles }): StyledFilterItem => styles);
|
||||
}
|
||||
|
||||
public resetDisplay(): void {
|
||||
this._filterItems.forEach((filterItem): void => filterItem.setVisible());
|
||||
}
|
||||
|
||||
public removeWidth(): void {
|
||||
this._filterItems.forEach((filterItem): void => filterItem.removeWidth());
|
||||
}
|
||||
|
||||
public updateWidth(): void {
|
||||
this._filterItems.forEach((filterItem): void => filterItem.updateWidth());
|
||||
}
|
||||
|
||||
public updateTransitionStyle(): void {
|
||||
this._filterItems.forEach((filterItem): void =>
|
||||
filterItem.updateTransitionStyle()
|
||||
);
|
||||
}
|
||||
|
||||
public disableTransitions(): void {
|
||||
this._filterItems.forEach((filterItem): void =>
|
||||
filterItem.disableTransitions()
|
||||
);
|
||||
}
|
||||
|
||||
public async enableTransitions(): Promise<void> {
|
||||
this._filterItems.forEach(
|
||||
async (filterItem): Promise<void> => await filterItem.enableTransitions()
|
||||
);
|
||||
}
|
||||
|
||||
public updateWidthWithTransitionsDisabled(): void {
|
||||
this.disableTransitions();
|
||||
this.removeWidth();
|
||||
this.updateWidth();
|
||||
this.enableTransitions();
|
||||
}
|
||||
}
|
1
filterizr/v2/src/FilterItems/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './FilterItems';
|
274
filterizr/v2/src/Filterizr/Filterizr.ts
Normal file
@ -0,0 +1,274 @@
|
||||
import { FILTERIZR_STATE } from '../config';
|
||||
import { Filter } from '../types';
|
||||
import { RawOptions, Destructible, Dimensions } from '../types/interfaces';
|
||||
import { getHTMLElement, debounce } from '../utils';
|
||||
import EventReceiver from '../EventReceiver';
|
||||
import FilterizrOptions, { defaultOptions } from '../FilterizrOptions';
|
||||
import FilterControls from '../FilterControls';
|
||||
import FilterContainer from '../FilterContainer';
|
||||
import FilterItems from '../FilterItems';
|
||||
import FilterItem from '../FilterItem';
|
||||
import Spinner from '../Spinner';
|
||||
import makeLayoutPositions from '../makeLayoutPositions';
|
||||
import installAsJQueryPlugin from './installAsJQueryPlugin';
|
||||
|
||||
const imagesLoaded = require('imagesloaded');
|
||||
|
||||
export default class Filterizr implements Destructible {
|
||||
/**
|
||||
* Main Filterizr classes exported as static members
|
||||
*/
|
||||
public static FilterContainer = FilterContainer;
|
||||
public static FilterItem = FilterItem;
|
||||
public static defaultOptions = defaultOptions;
|
||||
/**
|
||||
* Static method that receives the jQuery object and extends
|
||||
* its prototype with a .filterizr method.
|
||||
*/
|
||||
public static installAsJQueryPlugin: Function = installAsJQueryPlugin;
|
||||
|
||||
public options: FilterizrOptions;
|
||||
|
||||
private windowEventReceiver: EventReceiver;
|
||||
private filterContainer: FilterContainer;
|
||||
private filterControls?: FilterControls;
|
||||
private imagesHaveLoaded: boolean;
|
||||
private spinner?: Spinner;
|
||||
|
||||
public constructor(
|
||||
selectorOrNode: string | HTMLElement = '.filtr-container',
|
||||
userOptions: RawOptions = {}
|
||||
) {
|
||||
this.options = new FilterizrOptions(userOptions);
|
||||
|
||||
const {
|
||||
areControlsEnabled,
|
||||
controlsSelector,
|
||||
isSpinnerEnabled,
|
||||
} = this.options;
|
||||
|
||||
this.windowEventReceiver = new EventReceiver(window);
|
||||
this.filterContainer = new FilterContainer(
|
||||
getHTMLElement(selectorOrNode),
|
||||
this.options
|
||||
);
|
||||
this.imagesHaveLoaded = !this.filterContainer.node.querySelectorAll('img')
|
||||
.length;
|
||||
|
||||
if (areControlsEnabled) {
|
||||
this.filterControls = new FilterControls(this, controlsSelector);
|
||||
}
|
||||
if (isSpinnerEnabled) {
|
||||
this.spinner = new Spinner(this.filterContainer, this.options);
|
||||
}
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
private get filterItems(): FilterItems {
|
||||
return this.filterContainer.filterItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the items in the grid by a category
|
||||
* @param category by which to filter
|
||||
*/
|
||||
public filter(category: Filter): void {
|
||||
const { filterContainer } = this;
|
||||
|
||||
filterContainer.trigger('filteringStart');
|
||||
filterContainer.filterizrState = FILTERIZR_STATE.FILTERING;
|
||||
|
||||
category = Array.isArray(category)
|
||||
? category.map((c): string => c.toString())
|
||||
: category.toString();
|
||||
|
||||
this.options.filter = category;
|
||||
this.render();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
const { windowEventReceiver, filterControls, filterContainer } = this;
|
||||
filterContainer.destroy();
|
||||
windowEventReceiver.destroy();
|
||||
if (this.options.areControlsEnabled && filterControls) {
|
||||
filterControls.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new FilterItem into the grid
|
||||
*/
|
||||
public async insertItem(node: HTMLElement): Promise<void> {
|
||||
const { filterContainer } = this;
|
||||
filterContainer.insertItem(node);
|
||||
await this.waitForImagesToLoad();
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a FilterItem from the grid
|
||||
*/
|
||||
public removeItem(node: HTMLElement): void {
|
||||
const { filterContainer } = this;
|
||||
filterContainer.removeItem(node);
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the FilterItems in the grid
|
||||
* @param sortAttr the attribute by which to perform the sort
|
||||
* @param sortOrder ascending or descending
|
||||
*/
|
||||
public sort(
|
||||
sortAttr: string = 'index',
|
||||
sortOrder: 'asc' | 'desc' = 'asc'
|
||||
): void {
|
||||
const { filterContainer, filterItems } = this;
|
||||
filterContainer.trigger('sortingStart');
|
||||
filterContainer.filterizrState = FILTERIZR_STATE.SORTING;
|
||||
filterItems.sort(sortAttr, sortOrder);
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through the FilterItems for a given string and adds an additional filter layer.
|
||||
*/
|
||||
public search(searchTerm: string = this.options.get().searchTerm): void {
|
||||
this.options.searchTerm = searchTerm.toLowerCase();
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffles the FilterItems in the grid, making sure their positions have changed.
|
||||
*/
|
||||
public shuffle(): void {
|
||||
const { filterContainer, filterItems } = this;
|
||||
filterContainer.trigger('shufflingStart');
|
||||
filterContainer.filterizrState = FILTERIZR_STATE.SHUFFLING;
|
||||
filterItems.shuffle();
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the perferences of the users for rendering the Filterizr grid,
|
||||
* additionally performs error checking on the new options passed.
|
||||
* @param newOptions to override the defaults.
|
||||
*/
|
||||
public setOptions(newOptions: RawOptions): void {
|
||||
const { filterContainer, filterItems } = this;
|
||||
const animationPropIsSet =
|
||||
'animationDuration' in newOptions ||
|
||||
'delay' in newOptions ||
|
||||
'delayMode' in newOptions;
|
||||
|
||||
if (newOptions.callbacks || animationPropIsSet) {
|
||||
// Remove old callbacks before setting the new ones in the options
|
||||
filterContainer.unbindEvents();
|
||||
}
|
||||
|
||||
this.options.set(newOptions);
|
||||
|
||||
if (newOptions.easing || animationPropIsSet) {
|
||||
filterItems.styles.updateTransitionStyle();
|
||||
}
|
||||
|
||||
if (newOptions.callbacks || animationPropIsSet) {
|
||||
filterContainer.bindEvents();
|
||||
}
|
||||
|
||||
if ('searchTerm' in newOptions) {
|
||||
this.search(newOptions.searchTerm);
|
||||
}
|
||||
|
||||
if (
|
||||
'filter' in newOptions ||
|
||||
'multifilterLomultifilterLogicalOperator' in newOptions
|
||||
) {
|
||||
this.filter(this.options.filter);
|
||||
}
|
||||
|
||||
if ('gutterPixels' in newOptions) {
|
||||
this.filterContainer.styles.updatePaddings();
|
||||
this.filterItems.styles.updateWidthWithTransitionsDisabled();
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs multifiltering with AND/OR logic.
|
||||
* @param toggledFilter the filter to toggle
|
||||
*/
|
||||
public toggleFilter(toggledFilter: string): void {
|
||||
this.options.toggleFilter(toggledFilter);
|
||||
this.filter(this.options.filter);
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
const { filterContainer, filterItems, options } = this;
|
||||
const itemsToFilterIn = filterItems.getFiltered(options.filter);
|
||||
|
||||
filterItems.styles.resetDisplay();
|
||||
|
||||
filterItems.getFilteredOut(options.filter).forEach((filterItem): void => {
|
||||
filterItem.filterOut();
|
||||
});
|
||||
|
||||
const { containerHeight, itemsPositions } = makeLayoutPositions(
|
||||
filterContainer.dimensions.width,
|
||||
itemsToFilterIn.map(({ dimensions }): Dimensions => dimensions),
|
||||
this.options.get()
|
||||
);
|
||||
filterContainer.setHeight(containerHeight);
|
||||
|
||||
itemsToFilterIn.forEach((filterItem, index): void => {
|
||||
filterItem.filterIn(itemsPositions[index]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization sequence of Filterizr when the grid is first loaded
|
||||
*/
|
||||
private async initialize(): Promise<void> {
|
||||
const { filterContainer, filterItems, spinner } = this;
|
||||
this.bindEvents();
|
||||
await this.waitForImagesToLoad();
|
||||
if (this.options.isSpinnerEnabled) {
|
||||
// The spinner will first fade out (opacity: 0) before being removed
|
||||
await spinner.destroy();
|
||||
}
|
||||
// Enable animations after the initial render, to let
|
||||
// the items assume their positions before animating
|
||||
this.render();
|
||||
await filterItems.styles.enableTransitions();
|
||||
filterContainer.trigger('init');
|
||||
}
|
||||
|
||||
private bindEvents(): void {
|
||||
const { filterItems, windowEventReceiver } = this;
|
||||
windowEventReceiver.on('resize', debounce(
|
||||
(): void => {
|
||||
filterItems.styles.updateWidthWithTransitionsDisabled();
|
||||
this.render();
|
||||
},
|
||||
50,
|
||||
false
|
||||
) as EventListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves when the images of the grid have finished loading into the DOM
|
||||
*/
|
||||
private async waitForImagesToLoad(): Promise<void> {
|
||||
const { imagesHaveLoaded, filterContainer } = this;
|
||||
if (imagesHaveLoaded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise((resolve): void => {
|
||||
imagesLoaded(filterContainer.node, (): void => {
|
||||
this.imagesHaveLoaded = true;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
1
filterizr/v2/src/Filterizr/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Filterizr';
|
67
filterizr/v2/src/Filterizr/installAsJQueryPlugin.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import Filterizr from './Filterizr';
|
||||
import { defaultOptions } from '../FilterizrOptions';
|
||||
|
||||
export default function installAsJQueryPlugin($: any): void {
|
||||
if (!$)
|
||||
throw new Error(
|
||||
'Filterizr as a jQuery plugin, requires jQuery to work. If you would prefer to use the vanilla JS version, please use the correct bundle file.'
|
||||
);
|
||||
|
||||
// Add filterizr method on jQuery prototype
|
||||
$.fn.filterizr = function(): any {
|
||||
const selector = `.${$.trim(this.get(0).className).replace(/\s+/g, '.')}`;
|
||||
const args = arguments;
|
||||
|
||||
// user is instantiating Filterizr
|
||||
if (
|
||||
(!this._fltr && args.length === 0) ||
|
||||
(args.length === 1 && typeof args[0] === 'object')
|
||||
) {
|
||||
const options = args.length > 0 ? args[0] : defaultOptions;
|
||||
this._fltr = new Filterizr(selector, options);
|
||||
}
|
||||
// otherwise call the method called
|
||||
else if (args.length >= 1 && typeof args[0] === 'string') {
|
||||
const method = args[0];
|
||||
const methodArgs = Array.prototype.slice.call(args, 1);
|
||||
const filterizr = this._fltr;
|
||||
switch (method) {
|
||||
case 'filter':
|
||||
filterizr.filter(...methodArgs);
|
||||
return this;
|
||||
case 'insertItem':
|
||||
filterizr.insertItem(...methodArgs);
|
||||
return this;
|
||||
case 'removeItem':
|
||||
filterizr.removeItem(...methodArgs);
|
||||
return this;
|
||||
case 'toggleFilter':
|
||||
filterizr.toggleFilter(...methodArgs);
|
||||
return this;
|
||||
case 'sort':
|
||||
filterizr.sort(...methodArgs);
|
||||
return this;
|
||||
case 'shuffle':
|
||||
filterizr.shuffle(...methodArgs);
|
||||
return this;
|
||||
case 'search':
|
||||
filterizr.search(...methodArgs);
|
||||
return this;
|
||||
case 'setOptions':
|
||||
filterizr.setOptions(...methodArgs);
|
||||
return this;
|
||||
case 'destroy':
|
||||
filterizr.destroy(...methodArgs);
|
||||
// Kill internal reference to Filterizr instance
|
||||
delete this._fltr;
|
||||
return this;
|
||||
default:
|
||||
throw new Error(
|
||||
`Filterizr: ${method} is not part of the Filterizr API. Please refer to the docs for more information.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
}
|
44
filterizr/v2/src/FilterizrElement.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {
|
||||
Destructible,
|
||||
Dimensions,
|
||||
Resizable,
|
||||
Styleable,
|
||||
} from './types/interfaces';
|
||||
import FilterizrOptions from './FilterizrOptions';
|
||||
import EventReceiver from './EventReceiver';
|
||||
import StyledFilterizrElement from './StyledFilterizrElement';
|
||||
|
||||
export default abstract class FilterizrElement
|
||||
implements Destructible, Resizable, Styleable {
|
||||
public node: Element;
|
||||
public options: FilterizrOptions;
|
||||
protected eventReceiver: EventReceiver;
|
||||
|
||||
public constructor(node: Element, options: FilterizrOptions) {
|
||||
this.node = node;
|
||||
this.options = options;
|
||||
this.eventReceiver = new EventReceiver(this.node);
|
||||
}
|
||||
|
||||
public get dimensions(): Dimensions {
|
||||
return {
|
||||
width: this.node.clientWidth,
|
||||
height: this.node.clientHeight,
|
||||
};
|
||||
}
|
||||
|
||||
public destroy(): void | Promise<void> {
|
||||
this.styles.destroy();
|
||||
}
|
||||
|
||||
public trigger(eventType: string): void {
|
||||
const event = new Event(eventType);
|
||||
this.node.dispatchEvent(event);
|
||||
}
|
||||
|
||||
public abstract get styles(): StyledFilterizrElement;
|
||||
|
||||
protected abstract bindEvents(): void;
|
||||
|
||||
protected abstract unbindEvents(): void;
|
||||
}
|
133
filterizr/v2/src/FilterizrOptions/FilterizrOptions.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import { cssEasingValuesRegexp, LAYOUT } from '../config';
|
||||
import { BaseOptions, RawOptions } from './../types/interfaces';
|
||||
import { defaultOptions } from '.';
|
||||
import { checkOptionForErrors, merge } from '../utils';
|
||||
import ActiveFilter from '../ActiveFilter';
|
||||
import { Filter } from '../types';
|
||||
|
||||
export interface Options extends BaseOptions {
|
||||
filter: ActiveFilter;
|
||||
}
|
||||
|
||||
export default class FilterizrOptions {
|
||||
private options: Options;
|
||||
|
||||
public constructor(userOptions: RawOptions) {
|
||||
const options = merge({}, defaultOptions, this.validate(userOptions));
|
||||
this.options = this.convertToFilterizrOptions(options);
|
||||
}
|
||||
|
||||
public get isSpinnerEnabled(): boolean {
|
||||
return this.options.spinner.enabled;
|
||||
}
|
||||
|
||||
public get areControlsEnabled(): boolean {
|
||||
return this.options.setupControls;
|
||||
}
|
||||
|
||||
public get controlsSelector(): string {
|
||||
return this.options.controlsSelector;
|
||||
}
|
||||
|
||||
public get filter(): Filter {
|
||||
return this.options.filter.get();
|
||||
}
|
||||
|
||||
public set filter(filter: Filter) {
|
||||
this.options.filter.set(filter);
|
||||
}
|
||||
|
||||
public toggleFilter(filter: string): void {
|
||||
this.options.filter.toggle(filter);
|
||||
}
|
||||
|
||||
public get searchTerm(): string {
|
||||
return this.options.searchTerm;
|
||||
}
|
||||
|
||||
public set searchTerm(searchTerm: string) {
|
||||
this.options.searchTerm = searchTerm;
|
||||
}
|
||||
|
||||
public get(): Options {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public getRaw(): RawOptions {
|
||||
return this.convertToOptions(this.options);
|
||||
}
|
||||
|
||||
public set(newUserOptions: RawOptions): void {
|
||||
const options = merge(
|
||||
{},
|
||||
this.convertToOptions(this.options),
|
||||
this.validate(newUserOptions)
|
||||
);
|
||||
this.options = this.convertToFilterizrOptions(options);
|
||||
}
|
||||
|
||||
private convertToFilterizrOptions(userOptions: RawOptions): Options {
|
||||
return {
|
||||
...userOptions,
|
||||
filter: new ActiveFilter(userOptions.filter),
|
||||
};
|
||||
}
|
||||
|
||||
private convertToOptions(filterizrOptions: Options): RawOptions {
|
||||
return {
|
||||
...filterizrOptions,
|
||||
filter: filterizrOptions.filter.get(),
|
||||
};
|
||||
}
|
||||
|
||||
private validate(options: RawOptions): RawOptions {
|
||||
checkOptionForErrors(
|
||||
'animationDuration',
|
||||
options.animationDuration,
|
||||
'number'
|
||||
);
|
||||
checkOptionForErrors('callbacks', options.callbacks, 'object');
|
||||
checkOptionForErrors(
|
||||
'controlsSelector',
|
||||
options.controlsSelector,
|
||||
'string'
|
||||
);
|
||||
checkOptionForErrors('delay', options.delay, 'number');
|
||||
checkOptionForErrors(
|
||||
'easing',
|
||||
options.easing,
|
||||
'string',
|
||||
cssEasingValuesRegexp,
|
||||
'https://www.w3schools.com/cssref/css3_pr_transition-timing-function.asp'
|
||||
);
|
||||
checkOptionForErrors('delayMode', options.delayMode, 'string', [
|
||||
'progressive',
|
||||
'alternate',
|
||||
]);
|
||||
checkOptionForErrors('filter', options.filter, 'string|number|array');
|
||||
checkOptionForErrors('filterOutCss', options.filterOutCss, 'object');
|
||||
checkOptionForErrors('filterInCss', options.filterOutCss, 'object');
|
||||
checkOptionForErrors(
|
||||
'gridItemsSelector',
|
||||
options.gridItemsSelector,
|
||||
'string'
|
||||
);
|
||||
checkOptionForErrors('gutterPixels', options.gutterPixels, 'number');
|
||||
checkOptionForErrors(
|
||||
'layout',
|
||||
options.layout,
|
||||
'string',
|
||||
Object.values(LAYOUT)
|
||||
);
|
||||
checkOptionForErrors(
|
||||
'multifilterLogicalOperator',
|
||||
options.multifilterLogicalOperator,
|
||||
'string',
|
||||
['and', 'or']
|
||||
);
|
||||
checkOptionForErrors('searchTerm', options.searchTerm, 'string');
|
||||
checkOptionForErrors('setupControls', options.setupControls, 'boolean');
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
47
filterizr/v2/src/FilterizrOptions/defaultOptions.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { LAYOUT } from './../config';
|
||||
import { RawOptions } from '../types/interfaces';
|
||||
import { noop } from '../utils';
|
||||
|
||||
const defaultOptions: RawOptions = {
|
||||
animationDuration: 0.5,
|
||||
callbacks: {
|
||||
onInit: noop,
|
||||
onFilteringStart: noop,
|
||||
onFilteringEnd: noop,
|
||||
onShufflingStart: noop,
|
||||
onShufflingEnd: noop,
|
||||
onSortingStart: noop,
|
||||
onSortingEnd: noop,
|
||||
},
|
||||
controlsSelector: '',
|
||||
delay: 0,
|
||||
delayMode: 'progressive',
|
||||
easing: 'ease-out',
|
||||
filter: 'all',
|
||||
filterOutCss: {
|
||||
opacity: 0,
|
||||
transform: 'scale(0.5)',
|
||||
},
|
||||
filterInCss: {
|
||||
opacity: 1,
|
||||
transform: 'scale(1)',
|
||||
},
|
||||
gridItemsSelector: '.filtr-item',
|
||||
gutterPixels: 0,
|
||||
layout: LAYOUT.SAME_SIZE,
|
||||
multifilterLogicalOperator: 'or',
|
||||
searchTerm: '',
|
||||
setupControls: true,
|
||||
spinner: {
|
||||
enabled: false,
|
||||
fillColor: '#2184D0',
|
||||
styles: {
|
||||
height: '75px',
|
||||
margin: '0 auto',
|
||||
width: '75px',
|
||||
'z-index': 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default defaultOptions;
|
2
filterizr/v2/src/FilterizrOptions/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as defaultOptions } from './defaultOptions';
|
||||
export { default } from './FilterizrOptions';
|
35
filterizr/v2/src/Spinner/Spinner.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Destructible, Styleable } from '../types/interfaces';
|
||||
import { makeSpinner } from './makeSpinner';
|
||||
import FilterizrOptions from '../FilterizrOptions';
|
||||
import FilterContainer from '../FilterContainer';
|
||||
import StyledSpinner from './StyledSpinner';
|
||||
|
||||
export default class Spinner implements Destructible, Styleable {
|
||||
private node: HTMLElement;
|
||||
private styledNode: StyledSpinner;
|
||||
private filterContainer: FilterContainer;
|
||||
|
||||
public constructor(
|
||||
filterContainer: FilterContainer,
|
||||
options: FilterizrOptions
|
||||
) {
|
||||
this.filterContainer = filterContainer;
|
||||
this.node = makeSpinner(options.get().spinner);
|
||||
this.styledNode = new StyledSpinner(this.node, options);
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
public get styles(): StyledSpinner {
|
||||
return this.styledNode;
|
||||
}
|
||||
|
||||
public async destroy(): Promise<void> {
|
||||
await this.styles.fadeOut();
|
||||
this.filterContainer.node.removeChild(this.node);
|
||||
}
|
||||
|
||||
private initialize(): void {
|
||||
this.styles.initialize();
|
||||
this.filterContainer.node.appendChild(this.node);
|
||||
}
|
||||
}
|
16
filterizr/v2/src/Spinner/StyledSpinner.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import StyledFilterizrElement from '../StyledFilterizrElement';
|
||||
|
||||
export default class StyledSpinner extends StyledFilterizrElement {
|
||||
public initialize(): void {
|
||||
const { styles } = this.options.get().spinner;
|
||||
this.set({
|
||||
...styles,
|
||||
opacity: 1,
|
||||
transition: 'all ease-out 500ms',
|
||||
});
|
||||
}
|
||||
|
||||
public async fadeOut(): Promise<void> {
|
||||
await this.animate({ opacity: 0 });
|
||||
}
|
||||
}
|
1
filterizr/v2/src/Spinner/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './Spinner';
|
12
filterizr/v2/src/Spinner/makeSpinner.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { SpinnerOptions } from '../types/interfaces';
|
||||
|
||||
export function makeSpinner({ fillColor }: SpinnerOptions): HTMLElement {
|
||||
const svg = `<?xml version="1.0" encoding="UTF-8"?><svg stroke="${fillColor}" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" stroke-width="2"><circle cx="22" cy="22" r="1"><animate attributeName="r" begin="0s" calcMode="spline" dur="1.8s" keySplines="0.165, 0.84, 0.44, 1" keyTimes="0; 1" repeatCount="indefinite" values="1; 20"/><animate attributeName="stroke-opacity" begin="0s" calcMode="spline" dur="1.8s" keySplines="0.3, 0.61, 0.355, 1" keyTimes="0; 1" repeatCount="indefinite" values="1; 0"/></circle><circle cx="22" cy="22" r="1"><animate attributeName="r" begin="-0.9s" calcMode="spline" dur="1.8s" keySplines="0.165, 0.84, 0.44, 1" keyTimes="0; 1" repeatCount="indefinite" values="1; 20"/><animate attributeName="stroke-opacity" begin="-0.9s" calcMode="spline" dur="1.8s" keySplines="0.3, 0.61, 0.355, 1" keyTimes="0; 1" repeatCount="indefinite" values="1; 0"/></circle></g></svg>`;
|
||||
|
||||
const svgImg = document.createElement('img');
|
||||
svgImg.classList.add('Filterizr__spinner');
|
||||
svgImg.src = `data:image/svg+xml;base64,${window.btoa(svg)}`;
|
||||
svgImg.alt = 'Spinner';
|
||||
|
||||
return svgImg;
|
||||
}
|
32
filterizr/v2/src/StyledFilterizrElement.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Destructible } from './types/interfaces/Destructible';
|
||||
import animate from './animate';
|
||||
import { setStyles } from './utils';
|
||||
import FilterizrOptions from './FilterizrOptions';
|
||||
|
||||
export default abstract class StyledFilterizrElement implements Destructible {
|
||||
protected options: FilterizrOptions;
|
||||
protected node: HTMLElement;
|
||||
|
||||
public constructor(node: HTMLElement, options: FilterizrOptions) {
|
||||
this.node = node;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.node.removeAttribute('style');
|
||||
}
|
||||
|
||||
protected async animate(targetStyles: object): Promise<void> {
|
||||
animate(this.node, targetStyles);
|
||||
}
|
||||
|
||||
protected set(targetStyles: object): void {
|
||||
setStyles(this.node, targetStyles);
|
||||
}
|
||||
|
||||
protected remove(propertyName: string): void {
|
||||
this.node.style.removeProperty(propertyName);
|
||||
}
|
||||
|
||||
public abstract initialize(): void | Promise<void>;
|
||||
}
|
1
filterizr/v2/src/StyledFilterizrElements.ts
Normal file
@ -0,0 +1 @@
|
||||
export default abstract class StyledFilterizrElements {}
|
36
filterizr/v2/src/animate.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import EventReceiver from './EventReceiver';
|
||||
import { setStyles } from './utils';
|
||||
|
||||
interface AnimatorTransition {
|
||||
node: HTMLElement;
|
||||
targetStyles: object;
|
||||
eventReceiver: EventReceiver;
|
||||
}
|
||||
|
||||
class Animator {
|
||||
public static async animate(
|
||||
node: HTMLElement,
|
||||
targetStyles: object
|
||||
): Promise<void> {
|
||||
await Animator.process({
|
||||
node,
|
||||
targetStyles,
|
||||
eventReceiver: new EventReceiver(node),
|
||||
});
|
||||
}
|
||||
|
||||
private static async process(transition: AnimatorTransition): Promise<void> {
|
||||
return new Promise((resolve): void => {
|
||||
transition.eventReceiver.on('transitionend', (): void => {
|
||||
transition.eventReceiver.destroy();
|
||||
resolve();
|
||||
});
|
||||
|
||||
setTimeout((): void => {
|
||||
setStyles(transition.node, transition.targetStyles);
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Animator.animate;
|
4
filterizr/v2/src/config/cssEasingValuesRegexp.ts
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* A Regexp to validate potential values for the CSS easing property of transitions.
|
||||
*/
|
||||
export const cssEasingValuesRegexp = /(^linear$)|(^ease-in-out$)|(^ease-in$)|(^ease-out$)|(^ease$)|(^step-start$)|(^step-end$)|(^steps\(\d\s*,\s*(end|start)\))$|(^cubic-bezier\((\d*\.*\d+)\s*,\s*(\d*\.*\d+)\s*,\s*(\d*\.*\d+)\s*,\s*(\d*\.*\d+)\))$/;
|
16
filterizr/v2/src/config/filterizrState.ts
Normal file
@ -0,0 +1,16 @@
|
||||
interface FilterizrState {
|
||||
IDLE: 'IDLE';
|
||||
FILTERING: 'FILTERING';
|
||||
SORTING: 'SORTING';
|
||||
SHUFFLING: 'SHUFFLING';
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible animation states for Filterizr
|
||||
*/
|
||||
export const FILTERIZR_STATE: FilterizrState = {
|
||||
IDLE: 'IDLE',
|
||||
FILTERING: 'FILTERING',
|
||||
SORTING: 'SORTING',
|
||||
SHUFFLING: 'SHUFFLING',
|
||||
};
|
3
filterizr/v2/src/config/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { FILTERIZR_STATE } from './filterizrState';
|
||||
export { LAYOUT } from './layout';
|
||||
export { cssEasingValuesRegexp } from './cssEasingValuesRegexp';
|
20
filterizr/v2/src/config/layout.ts
Normal file
@ -0,0 +1,20 @@
|
||||
interface Layout {
|
||||
SAME_SIZE: 'sameSize';
|
||||
SAME_HEIGHT: 'sameHeight';
|
||||
SAME_WIDTH: 'sameWidth';
|
||||
PACKED: 'packed';
|
||||
HORIZONTAL: 'horizontal';
|
||||
VERTICAL: 'vertical';
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible grid layout modes
|
||||
*/
|
||||
export const LAYOUT: Layout = {
|
||||
SAME_SIZE: 'sameSize',
|
||||
SAME_HEIGHT: 'sameHeight',
|
||||
SAME_WIDTH: 'sameWidth',
|
||||
PACKED: 'packed',
|
||||
HORIZONTAL: 'horizontal',
|
||||
VERTICAL: 'vertical',
|
||||
};
|
16
filterizr/v2/src/index.jquery.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Filterizr is a JavaScript library that sorts, shuffles and applies stunning filters over
|
||||
* responsive galleries using CSS3 transitions and custom CSS effects.
|
||||
*
|
||||
* @author Yiotis Kaltsikis
|
||||
* @see {@link http://yiotis.net/filterizr}
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import Filterizr from './Filterizr';
|
||||
|
||||
(function($: any): void {
|
||||
Filterizr.installAsJQueryPlugin($);
|
||||
})((window as any).jQuery);
|
||||
|
||||
export default Filterizr;
|
10
filterizr/v2/src/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Filterizr is a JavaScript library that sorts, shuffles and applies stunning
|
||||
* filters over responsive galleries using CSS3 transitions and
|
||||
* custom CSS effects.
|
||||
* @author Yiotis Kaltsikis
|
||||
* @see {@link http://yiotis.net/filterizr}
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export { default } from './Filterizr';
|
79
filterizr/v2/src/makeLayoutPositions/Packer.ts
Normal file
@ -0,0 +1,79 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Modified version of Jake Gordon's Bin Packing algorithm used for Filterizr's 'packed' layout
|
||||
* @see {@link https://github.com/jakesgordon/bin-packing}
|
||||
*/
|
||||
interface PackerRoot {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h?: number;
|
||||
used?: boolean;
|
||||
down?: PackerRoot;
|
||||
right?: PackerRoot;
|
||||
}
|
||||
|
||||
interface PackerBlock {
|
||||
x?: number;
|
||||
y?: number;
|
||||
w?: number;
|
||||
h?: number;
|
||||
fit?: PackerRoot | void;
|
||||
}
|
||||
|
||||
export default class Packer {
|
||||
root: PackerRoot;
|
||||
|
||||
constructor(w: number) {
|
||||
this.init(w);
|
||||
}
|
||||
|
||||
init(w: number) {
|
||||
this.root = { x: 0, y: 0, w: w };
|
||||
}
|
||||
|
||||
fit(blocks: PackerBlock[]) {
|
||||
let n,
|
||||
node,
|
||||
block,
|
||||
len = blocks.length;
|
||||
let h = len > 0 ? blocks[0].h : 0;
|
||||
this.root.h = h;
|
||||
for (n = 0; n < len; n++) {
|
||||
block = blocks[n];
|
||||
if ((node = this.findNode(this.root, block.w, block.h)))
|
||||
block.fit = this.splitNode(node, block.w, block.h);
|
||||
else block.fit = this.growDown(block.w, block.h);
|
||||
}
|
||||
}
|
||||
|
||||
findNode(root: PackerRoot, w: number, h: number): PackerRoot | void {
|
||||
if (root.used)
|
||||
return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
|
||||
else if (w <= root.w && h <= root.h) return root;
|
||||
else return null;
|
||||
}
|
||||
|
||||
splitNode(node: PackerRoot, w: number, h: number): PackerRoot {
|
||||
node.used = true;
|
||||
node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };
|
||||
node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };
|
||||
return node;
|
||||
}
|
||||
|
||||
growDown(w: number, h: number): PackerRoot | void {
|
||||
let node;
|
||||
this.root = {
|
||||
used: true,
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: this.root.w,
|
||||
h: this.root.h + h,
|
||||
down: { x: 0, y: this.root.h, w: this.root.w, h: h },
|
||||
right: this.root,
|
||||
};
|
||||
if ((node = this.findNode(this.root, w, h)))
|
||||
return this.splitNode(node, w, h);
|
||||
else return null;
|
||||
}
|
||||
}
|
1
filterizr/v2/src/makeLayoutPositions/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './makeLayoutPositions';
|
@ -0,0 +1,32 @@
|
||||
import { ContainerLayout, Dimensions, Position } from '../types/interfaces';
|
||||
|
||||
function calculateWidthSumWithGutters(
|
||||
itemsDimensions: Dimensions[],
|
||||
gutterPixels: number
|
||||
): number {
|
||||
return itemsDimensions.reduce(
|
||||
(acc, { width }): number => acc + width + gutterPixels,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal layout algorithm that arranges all FilterItems in one row. Their width may vary.
|
||||
*/
|
||||
export default (
|
||||
itemsDimensions: Dimensions[],
|
||||
gutterPixels: number
|
||||
): ContainerLayout => ({
|
||||
containerHeight:
|
||||
Math.max(...itemsDimensions.map(({ height }): number => height)) +
|
||||
gutterPixels * 2,
|
||||
itemsPositions: itemsDimensions.map(
|
||||
({}, index: number): Position => ({
|
||||
left: calculateWidthSumWithGutters(
|
||||
itemsDimensions.slice(0, index),
|
||||
gutterPixels
|
||||
),
|
||||
top: 0,
|
||||
})
|
||||
),
|
||||
});
|
60
filterizr/v2/src/makeLayoutPositions/makeLayoutPositions.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { LAYOUT } from './../config';
|
||||
import { ContainerLayout, Dimensions, Options } from '../types/interfaces';
|
||||
import memoize from 'fast-memoize';
|
||||
import makeHorizontalLayoutPositions from './makeHorizontalLayoutPositions';
|
||||
import makeVerticalLayoutPositions from './makeVerticalLayoutPositions';
|
||||
import makeSameHeightLayoutPositions from './makeSameHeightLayoutPositions';
|
||||
import makeSameWidthLayoutPositions from './makeSameWidthLayoutPositions';
|
||||
import makeSameSizeLayoutPosition from './makeSameSizeLayoutPosition';
|
||||
import makePackedLayoutPositions from './makePackedLayoutPositions';
|
||||
|
||||
/**
|
||||
* Creates the specifications of the dimensions of the
|
||||
* container and items for the next render of Filterizr.
|
||||
*/
|
||||
export default memoize(
|
||||
(
|
||||
containerWidth: number,
|
||||
itemsDimensions: Dimensions[],
|
||||
{ gutterPixels, layout }: Options
|
||||
): ContainerLayout => {
|
||||
if (!itemsDimensions.length) {
|
||||
return {
|
||||
containerHeight: 0,
|
||||
itemsPositions: [],
|
||||
};
|
||||
}
|
||||
|
||||
switch (layout) {
|
||||
case LAYOUT.HORIZONTAL:
|
||||
return makeHorizontalLayoutPositions(itemsDimensions, gutterPixels);
|
||||
case LAYOUT.VERTICAL:
|
||||
return makeVerticalLayoutPositions(itemsDimensions, gutterPixels);
|
||||
case LAYOUT.SAME_HEIGHT:
|
||||
return makeSameHeightLayoutPositions(
|
||||
containerWidth,
|
||||
itemsDimensions,
|
||||
gutterPixels
|
||||
);
|
||||
case LAYOUT.SAME_WIDTH:
|
||||
return makeSameWidthLayoutPositions(
|
||||
containerWidth,
|
||||
itemsDimensions,
|
||||
gutterPixels
|
||||
);
|
||||
case LAYOUT.PACKED:
|
||||
return makePackedLayoutPositions(
|
||||
containerWidth,
|
||||
itemsDimensions,
|
||||
gutterPixels
|
||||
);
|
||||
case LAYOUT.SAME_SIZE:
|
||||
default:
|
||||
return makeSameSizeLayoutPosition(
|
||||
containerWidth,
|
||||
itemsDimensions,
|
||||
gutterPixels
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
@ -0,0 +1,35 @@
|
||||
import { ContainerLayout, Position, Dimensions } from '../types/interfaces';
|
||||
import Packer from './Packer';
|
||||
|
||||
/**
|
||||
* Packed layout for items that can have varying width as well as varying height.
|
||||
*/
|
||||
export default (
|
||||
containerWidth: number,
|
||||
itemsDimensions: Dimensions[],
|
||||
gutterPixels: number
|
||||
): ContainerLayout => {
|
||||
//Instantiate new Packer, set up grid
|
||||
const packer = new Packer(containerWidth);
|
||||
const itemsDimensionsWithGutter = itemsDimensions.map(
|
||||
({ width, height }): object => ({
|
||||
w: width + gutterPixels,
|
||||
h: height + gutterPixels,
|
||||
})
|
||||
);
|
||||
|
||||
// Use packer to convert dimensions into coordinates
|
||||
packer.fit(itemsDimensionsWithGutter);
|
||||
|
||||
const itemsPositions = itemsDimensionsWithGutter.map(
|
||||
({ fit }: { fit: { x: number; y: number } }): Position => ({
|
||||
left: fit.x,
|
||||
top: fit.y,
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
containerHeight: packer.root.h + gutterPixels,
|
||||
itemsPositions,
|
||||
};
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
import {
|
||||
ContainerLayout,
|
||||
Position,
|
||||
Dimensions,
|
||||
Dictionary,
|
||||
} from '../types/interfaces';
|
||||
|
||||
/**
|
||||
* Same height layout for items that have the same height, but can have varying width
|
||||
*/
|
||||
export default (
|
||||
containerWidth: number,
|
||||
itemsDimensions: Dimensions[],
|
||||
gutterPixels: number
|
||||
): ContainerLayout => {
|
||||
const endWidths = itemsDimensions.map(({ width }, index): number => {
|
||||
const prevWidth = itemsDimensions
|
||||
.slice(0, index)
|
||||
.reduce((acc, { width }): number => acc + width + gutterPixels * 2, 0);
|
||||
return prevWidth + width + gutterPixels;
|
||||
});
|
||||
|
||||
const rowStartIndexes: Dictionary = endWidths.reduce(
|
||||
(acc: Dictionary, width, index): object => {
|
||||
const accLength = Object.keys(acc).length;
|
||||
const rowMustBreak = width > containerWidth * accLength;
|
||||
return {
|
||||
...acc,
|
||||
...(rowMustBreak && { [accLength]: index }),
|
||||
};
|
||||
},
|
||||
{ 0: 0 }
|
||||
);
|
||||
|
||||
const itemsPositions = itemsDimensions.map(
|
||||
({ height }, index): Position => {
|
||||
const row = Math.floor(endWidths[index] / containerWidth);
|
||||
return {
|
||||
left: itemsDimensions
|
||||
.slice(rowStartIndexes[row], index)
|
||||
.reduce((acc, { width }): number => acc + width + gutterPixels, 0),
|
||||
top: (height + gutterPixels) * row,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const totalRows = Object.keys(rowStartIndexes).length;
|
||||
const totalItemHeight = itemsDimensions[0].height + gutterPixels;
|
||||
const containerHeight = totalRows * totalItemHeight + gutterPixels;
|
||||
|
||||
return {
|
||||
containerHeight,
|
||||
itemsPositions,
|
||||
};
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
import { ContainerLayout, Position, Dimensions } from '../types/interfaces';
|
||||
|
||||
/**
|
||||
* Same size layout for items that have the same width/height
|
||||
*/
|
||||
export default (
|
||||
containerWidth: number,
|
||||
itemsDimensions: Dimensions[],
|
||||
gutterPixels: number
|
||||
): ContainerLayout => {
|
||||
const columns = Math.floor(
|
||||
containerWidth / (itemsDimensions[0].width + gutterPixels)
|
||||
);
|
||||
|
||||
const itemsPositions = itemsDimensions.map(
|
||||
({ width, height }, index): Position => {
|
||||
const row = Math.floor(index / columns);
|
||||
const col = index - columns * row;
|
||||
return {
|
||||
left: col * (width + gutterPixels),
|
||||
top: row * (height + gutterPixels),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const totalRows = Math.ceil(itemsDimensions.length / columns);
|
||||
const firstItemHeight = itemsDimensions[0].height + gutterPixels;
|
||||
const containerHeight = totalRows * firstItemHeight + gutterPixels;
|
||||
|
||||
return {
|
||||
containerHeight,
|
||||
itemsPositions,
|
||||
};
|
||||
};
|
@ -0,0 +1,47 @@
|
||||
import { ContainerLayout, Dimensions, Position } from '../types/interfaces';
|
||||
|
||||
/**
|
||||
* Same width layout for items that have the same width and varying height
|
||||
*/
|
||||
export default (
|
||||
containerWidth: number,
|
||||
itemsDimensions: Dimensions[],
|
||||
gutterPixels: number
|
||||
): ContainerLayout => {
|
||||
const columns = Math.floor(
|
||||
containerWidth / (itemsDimensions[0].width + gutterPixels)
|
||||
);
|
||||
|
||||
const itemsPositions = itemsDimensions.map(
|
||||
({ width }, index): Position => {
|
||||
const row = Math.floor(index / columns);
|
||||
const col = index - columns * row;
|
||||
return {
|
||||
left: col * (width + gutterPixels),
|
||||
// The top position of every item for this layout
|
||||
// is the sum of the items positioned above it
|
||||
top: itemsDimensions
|
||||
.slice(0, index)
|
||||
.filter((_, subIndex): boolean => (index - subIndex) % columns === 0)
|
||||
.reduce((sum, { height }): number => sum + height + gutterPixels, 0),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// The height of the container for this layout is
|
||||
// going to be the same as that of the longest column.
|
||||
const columnsHeights = itemsDimensions.reduce(
|
||||
(acc, { height }, index): number[] => {
|
||||
const row = Math.floor(index / columns);
|
||||
const col = index - columns * row;
|
||||
acc[col] += height + gutterPixels;
|
||||
return acc;
|
||||
},
|
||||
Array.apply(null, Array(columns)).map(Number.prototype.valueOf, 0)
|
||||
);
|
||||
|
||||
return {
|
||||
containerHeight: Math.max(...columnsHeights) + gutterPixels,
|
||||
itemsPositions,
|
||||
};
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
import { ContainerLayout, Dimensions, Position } from '../types/interfaces';
|
||||
|
||||
function calculateHeightSumWithGutters(
|
||||
itemsDimensions: Dimensions[],
|
||||
gutterPixels: number
|
||||
): number {
|
||||
if (!itemsDimensions.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return itemsDimensions.reduce(
|
||||
(acc, { height }): number => acc + height + gutterPixels,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical layout algorithm that arranges all FilterItems in one column. Their height may vary.
|
||||
*/
|
||||
export default (
|
||||
itemsDimensions: Dimensions[],
|
||||
gutterPixels: number
|
||||
): ContainerLayout => ({
|
||||
containerHeight:
|
||||
calculateHeightSumWithGutters(itemsDimensions, gutterPixels) + gutterPixels,
|
||||
itemsPositions: itemsDimensions.map(
|
||||
(_, index): Position => ({
|
||||
left: 0,
|
||||
top: calculateHeightSumWithGutters(
|
||||
itemsDimensions.slice(0, index),
|
||||
gutterPixels
|
||||
),
|
||||
})
|
||||
),
|
||||
});
|
9
filterizr/v2/src/types/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export type Filter = string | string[];
|
||||
export type FilterizrState = 'IDLE' | 'FILTERING' | 'SORTING' | 'SHUFFLING';
|
||||
export type Layout =
|
||||
| 'horizontal'
|
||||
| 'vertical'
|
||||
| 'sameHeight'
|
||||
| 'sameWidth'
|
||||
| 'sameSize'
|
||||
| 'packed';
|
21
filterizr/v2/src/types/interfaces/BaseOptions.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { SpinnerOptions } from './SpinnerOptions';
|
||||
import { RawOptionsCallbacks } from './RawOptionsCallbacks';
|
||||
import { Layout } from '..';
|
||||
|
||||
export interface BaseOptions {
|
||||
animationDuration?: number;
|
||||
callbacks?: RawOptionsCallbacks;
|
||||
controlsSelector?: string;
|
||||
delay?: number;
|
||||
delayMode?: 'alternate' | 'progressive';
|
||||
easing?: string;
|
||||
filterOutCss?: object;
|
||||
filterInCss?: object;
|
||||
gridItemsSelector?: string;
|
||||
gutterPixels?: number;
|
||||
layout?: Layout;
|
||||
multifilterLogicalOperator?: 'or' | 'and';
|
||||
searchTerm?: string;
|
||||
setupControls?: boolean;
|
||||
spinner?: SpinnerOptions;
|
||||
}
|
6
filterizr/v2/src/types/interfaces/ContainerLayout.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Position } from './Position';
|
||||
|
||||
export interface ContainerLayout {
|
||||
containerHeight: number;
|
||||
itemsPositions: Position[];
|
||||
}
|
3
filterizr/v2/src/types/interfaces/Destructible.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface Destructible {
|
||||
destroy(): void | Promise<void>;
|
||||
}
|
3
filterizr/v2/src/types/interfaces/Dictionary.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface Dictionary {
|
||||
[key: string]: any;
|
||||
}
|
4
filterizr/v2/src/types/interfaces/Dimensions.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Dimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
6
filterizr/v2/src/types/interfaces/Options.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { BaseOptions } from './BaseOptions';
|
||||
import ActiveFilter from '../../ActiveFilter';
|
||||
|
||||
export interface Options extends BaseOptions {
|
||||
filter: ActiveFilter;
|
||||
}
|
4
filterizr/v2/src/types/interfaces/Position.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Position {
|
||||
left: number;
|
||||
top: number;
|
||||
}
|
5
filterizr/v2/src/types/interfaces/RawOptions.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { BaseOptions } from './BaseOptions';
|
||||
|
||||
export interface RawOptions extends BaseOptions {
|
||||
filter?: string | string[];
|
||||
}
|
10
filterizr/v2/src/types/interfaces/RawOptionsCallbacks.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface RawOptionsCallbacks {
|
||||
onInit?: EventListener;
|
||||
onFilteringStart?: EventListener;
|
||||
onFilteringEnd?: EventListener;
|
||||
onShufflingStart?: EventListener;
|
||||
onShufflingEnd?: EventListener;
|
||||
onSortingStart?: EventListener;
|
||||
onSortingEnd?: EventListener;
|
||||
onTransitionEnd?: EventListener;
|
||||
}
|
6
filterizr/v2/src/types/interfaces/Resizable.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface Resizable {
|
||||
readonly dimensions: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
7
filterizr/v2/src/types/interfaces/SpinnerOptions.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Dictionary } from './Dictionary';
|
||||
|
||||
export interface SpinnerOptions {
|
||||
enabled?: boolean;
|
||||
fillColor?: string;
|
||||
styles?: Dictionary;
|
||||
}
|
6
filterizr/v2/src/types/interfaces/Styleable.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import StyledFilterizrElement from '../../StyledFilterizrElement';
|
||||
import StyledFilterizrElements from '../../StyledFilterizrElements';
|
||||
|
||||
export interface Styleable {
|
||||
readonly styles: StyledFilterizrElement | StyledFilterizrElements;
|
||||
}
|
12
filterizr/v2/src/types/interfaces/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export { BaseOptions } from './BaseOptions';
|
||||
export { ContainerLayout } from './ContainerLayout';
|
||||
export { Destructible } from './Destructible';
|
||||
export { Dictionary } from './Dictionary';
|
||||
export { Dimensions } from './Dimensions';
|
||||
export { Options } from './Options';
|
||||
export { Position } from './Position';
|
||||
export { RawOptions } from './RawOptions';
|
||||
export { RawOptionsCallbacks } from './RawOptionsCallbacks';
|
||||
export { Resizable } from './Resizable';
|
||||
export { SpinnerOptions } from './SpinnerOptions';
|
||||
export { Styleable } from './Styleable';
|
5
filterizr/v2/src/utils/allStringsOfArray1InArray2.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const allStringsOfArray1InArray2 = (
|
||||
arr1: string[],
|
||||
arr2: string[]
|
||||
): boolean =>
|
||||
arr1.reduce((acc, item): boolean => acc && arr2.includes(item), true);
|
68
filterizr/v2/src/utils/checkOptionForErrors.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Error checking method to restrict a prop to some allowed values
|
||||
* @param {String} name of the option key in the options object
|
||||
* @param {String|Number|Object|Function|Array|Boolean} value of the option
|
||||
* @param {String} type of the property
|
||||
* @param {Array} allowed accepted values for option
|
||||
* @param {String} furtherHelpLink a link to docs for further help
|
||||
*/
|
||||
export const checkOptionForErrors = (
|
||||
name: string,
|
||||
value: string | number | object | Function | any[] | boolean,
|
||||
type?: string,
|
||||
allowed?: any[] | RegExp,
|
||||
furtherHelpLink?: string
|
||||
): void => {
|
||||
if (typeof value === 'undefined') return; // exit case, missing from options
|
||||
|
||||
// Define exception for type error
|
||||
const typeError = new Error(
|
||||
`Filterizr: expected type of option "${name}" to be "${type}", but its type is: "${typeof value}"`
|
||||
);
|
||||
let matchesOtherTypes = false;
|
||||
let isArray = false;
|
||||
const couldBeArray = type.includes('array');
|
||||
// Perform type checking
|
||||
if ((typeof value).match(type)) {
|
||||
matchesOtherTypes = true;
|
||||
} else if (!matchesOtherTypes && couldBeArray) {
|
||||
isArray = Array.isArray(value);
|
||||
}
|
||||
// Throw the errors for invalid types
|
||||
if (!matchesOtherTypes && !couldBeArray) {
|
||||
throw typeError;
|
||||
} else if (!matchesOtherTypes && couldBeArray && !isArray) {
|
||||
throw typeError;
|
||||
}
|
||||
|
||||
// Make sure that the value of the option is within the accepted values
|
||||
const referTo = (link?: string): string => {
|
||||
if (link) {
|
||||
return ` For further help read here: ${link}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
if (Array.isArray(allowed)) {
|
||||
let validValue = false;
|
||||
allowed.forEach((el): void => {
|
||||
if (el === value) validValue = true;
|
||||
});
|
||||
if (!validValue) {
|
||||
throw new Error(
|
||||
`Filterizr: allowed values for option "${name}" are: ${allowed
|
||||
.map((el): string => `"${el}"`)
|
||||
.join(', ')}. Value received: "${value}".${referTo(furtherHelpLink)}`
|
||||
);
|
||||
}
|
||||
} else if (typeof value === 'string' && allowed instanceof RegExp) {
|
||||
const isValid = value.match(allowed);
|
||||
if (!isValid) {
|
||||
throw new Error(
|
||||
`Filterizr: invalid value "${value}" for option "${name}" received.${referTo(
|
||||
furtherHelpLink
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
20
filterizr/v2/src/utils/debounce.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Debounce of Underscore.js
|
||||
*/
|
||||
export const debounce = function(
|
||||
func: Function,
|
||||
wait: number,
|
||||
immediate: boolean
|
||||
): Function {
|
||||
let timeout: number;
|
||||
return function(): void {
|
||||
const context = this;
|
||||
const args = arguments;
|
||||
clearTimeout(timeout);
|
||||
timeout = window.setTimeout((): void => {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
}, wait);
|
||||
if (immediate && !timeout) func.apply(context, args);
|
||||
};
|
||||
};
|
23
filterizr/v2/src/utils/filterItemArraysHaveSameSorting.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import FilterItem from '../FilterItem';
|
||||
|
||||
/**
|
||||
* Simple method to check if two arrays of FilterItems
|
||||
* are sorted in the same manner or not.
|
||||
* @param {Array} arr1 the first array of FilterItems
|
||||
* @param {Array} arr2 the second array of FilterItems
|
||||
* @return {Boolean} equality
|
||||
*/
|
||||
export const filterItemArraysHaveSameSorting = (
|
||||
filterItemsA: FilterItem[],
|
||||
filterItemsB: FilterItem[]
|
||||
): boolean => {
|
||||
if (filterItemsA.length !== filterItemsB.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filterItemsA.reduce((acc, filterItemA, index): boolean => {
|
||||
const indexA = filterItemA.getSortAttribute('index');
|
||||
const indexB = filterItemsB[index].getSortAttribute('index');
|
||||
return acc && indexA === indexB;
|
||||
}, true);
|
||||
};
|
17
filterizr/v2/src/utils/getDataAttributesOfHTMLNode.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Dictionary } from '../types/interfaces/Dictionary';
|
||||
|
||||
export function getDataAttributesOfHTMLNode(node: Element) {
|
||||
const data: Dictionary = {
|
||||
category: '',
|
||||
sort: '',
|
||||
};
|
||||
for (let i = 0, atts = node.attributes, n = atts.length; i < n; i++) {
|
||||
const { nodeName, nodeValue } = atts[i];
|
||||
if (nodeName.includes('data')) {
|
||||
data[nodeName.slice(5, nodeName.length)] = nodeValue;
|
||||
}
|
||||
}
|
||||
delete data.category;
|
||||
delete data.sort;
|
||||
return data;
|
||||
}
|
13
filterizr/v2/src/utils/getHTMLElement.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Wrapper around document.querySelector, will function as
|
||||
* an identity function if an HTML element is passed in
|
||||
* @param {HTMLElement|string} nodeOrSelector
|
||||
*/
|
||||
export const getHTMLElement = (
|
||||
selectorOrNode: HTMLElement | string
|
||||
): HTMLElement => {
|
||||
if (typeof selectorOrNode === 'string') {
|
||||
return document.querySelector(selectorOrNode);
|
||||
}
|
||||
return selectorOrNode;
|
||||
};
|
14
filterizr/v2/src/utils/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export { allStringsOfArray1InArray2 } from './allStringsOfArray1InArray2';
|
||||
export { checkOptionForErrors } from './checkOptionForErrors';
|
||||
export { debounce } from './debounce';
|
||||
export {
|
||||
filterItemArraysHaveSameSorting,
|
||||
} from './filterItemArraysHaveSameSorting';
|
||||
export { getDataAttributesOfHTMLNode } from './getDataAttributesOfHTMLNode';
|
||||
export { getHTMLElement } from './getHTMLElement';
|
||||
export { intersection } from './intersection';
|
||||
export { merge } from './merge';
|
||||
export { noop } from './noop';
|
||||
export { setStyles } from './setStyles';
|
||||
export { shuffle } from './shuffle';
|
||||
export { sortBy } from './sortBy';
|
8
filterizr/v2/src/utils/intersection.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* A function get the intersection of two arrays. IE9+.
|
||||
*/
|
||||
export const intersection = (arr1: any[], arr2: any[]): any[] => {
|
||||
return Array.prototype.filter.call(arr1, (n: any): boolean =>
|
||||
arr2.includes(n)
|
||||
);
|
||||
};
|
32
filterizr/v2/src/utils/merge.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Dictionary } from '../types/interfaces/Dictionary';
|
||||
|
||||
/**
|
||||
* Simple object check.
|
||||
*/
|
||||
function isObject(item: any) {
|
||||
return item && typeof item === 'object' && !Array.isArray(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep merge two objects.
|
||||
*/
|
||||
export function merge(
|
||||
target: Dictionary,
|
||||
...sources: Dictionary[]
|
||||
): Dictionary {
|
||||
if (!sources.length) return target;
|
||||
const source = sources.shift();
|
||||
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (isObject(source[key])) {
|
||||
if (!target[key]) Object.assign(target, { [key]: {} });
|
||||
merge(target[key], source[key]);
|
||||
} else {
|
||||
Object.assign(target, { [key]: source[key] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return merge(target, ...sources);
|
||||
}
|
4
filterizr/v2/src/utils/noop.ts
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* A no-operation function
|
||||
*/
|
||||
export const noop = (): void => {};
|
11
filterizr/v2/src/utils/setStyles.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Set inline styles on an HTML node
|
||||
* @param {HTMLElement} node - HTML node
|
||||
* @param {Object} styles - object with styles
|
||||
* @returns {undefined}
|
||||
*/
|
||||
export function setStyles(node: Element, styles: any): void {
|
||||
Object.entries(styles).forEach(([key, value]): void => {
|
||||
((node as HTMLElement).style as any)[key] = value;
|
||||
});
|
||||
}
|
18
filterizr/v2/src/utils/shuffle.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Fisher-Yates shuffle ES6 non-mutating implementation.
|
||||
* @param {Array} array the array to shuffle
|
||||
* @return {Array} shuffled array without mutating the initial array.
|
||||
*/
|
||||
export const shuffle = (array: any[]): any[] => {
|
||||
// perform deep clone on array to mutate
|
||||
let cloned = array.slice(0);
|
||||
// array to return
|
||||
let randomizedArray = [];
|
||||
// perform shuffle
|
||||
while (cloned.length !== 0) {
|
||||
let rIndex = Math.floor(cloned.length * Math.random());
|
||||
randomizedArray.push(cloned[rIndex]);
|
||||
cloned.splice(rIndex, 1);
|
||||
}
|
||||
return randomizedArray;
|
||||
};
|
25
filterizr/v2/src/utils/sortBy.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Simple non-mutating sorting function for arrays of objects by a property
|
||||
* @param {Array} array to sort
|
||||
* @param {Function} propFn fetches the property by which to sort
|
||||
* @return {Array} a new sorted array
|
||||
*/
|
||||
export const sortBy = (array: any[], propFn: Function): any[] => {
|
||||
let cloned = array.slice(0); // perform deep copy of array
|
||||
|
||||
const comparator = (propFn: Function) => {
|
||||
return (a: any, b: any): number => {
|
||||
const propA = propFn(a);
|
||||
const propB = propFn(b);
|
||||
if (propA < propB) {
|
||||
return -1;
|
||||
} else if (propA > propB) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return cloned.sort(comparator(propFn));
|
||||
};
|
45
filterizr/v2/tests/ActiveFilter.test.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import ActiveFilter from '../src/ActiveFilter';
|
||||
|
||||
describe('ActiveFilter', () => {
|
||||
describe('#constructor', () => {
|
||||
it('should create an instance with default filter set to "all"', () => {
|
||||
expect(new ActiveFilter('all').get()).toEqual('all');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get', () => {
|
||||
it('returns the value of the filter', () => {
|
||||
const activeFilter = new ActiveFilter('all');
|
||||
expect(activeFilter.get()).toEqual('all');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#set', () => {
|
||||
it('set the value of the filter to passed value', () => {
|
||||
const activeFilter = new ActiveFilter('all');
|
||||
activeFilter.set('5');
|
||||
expect(activeFilter.get()).toEqual('5');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#toggle', () => {
|
||||
it('toggle the value of the filter passed in', () => {
|
||||
const activeFilter1 = new ActiveFilter('5');
|
||||
activeFilter1.toggle('5');
|
||||
|
||||
const activeFilter2 = new ActiveFilter(['4', '8']);
|
||||
activeFilter2.toggle('8');
|
||||
|
||||
const activeFilter3 = new ActiveFilter(['4', '8']);
|
||||
activeFilter3.toggle('9');
|
||||
|
||||
const activeFilter4 = new ActiveFilter('all');
|
||||
activeFilter4.toggle('2');
|
||||
|
||||
expect(activeFilter1.get()).toEqual('all');
|
||||
expect(activeFilter2.get()).toEqual('4');
|
||||
expect(activeFilter3.get()).toEqual(['4', '8', '9']);
|
||||
expect(activeFilter4.get()).toEqual('2');
|
||||
});
|
||||
});
|
||||
});
|