├── bin ├── lint-json.js ├── .eslintrc.js ├── version.js └── bundle-js.js ├── .gitignore ├── .jshintrc ├── test ├── unit │ ├── helpers.js │ ├── masonry-stamp.js │ ├── fit-rows.js │ ├── masonry-measure-columns.js │ ├── get-segment-size.js │ ├── filtering.js │ ├── sorting.js │ ├── arrange-complete.js │ └── sort-data.js ├── .jshintrc ├── tests.css └── index.html ├── .github ├── issue_template.md └── contributing.md ├── .eslintrc.js ├── bower.json ├── sandbox ├── require-js.html ├── browserify │ ├── jq-main.js │ ├── main.js │ └── browserify.html ├── basic.html ├── js │ └── require-js.js ├── transition-bug.html ├── fluid.html ├── sandbox.css ├── stamps.html ├── insert.html ├── right-to-left.html ├── bottom-up.html ├── fitrows.html ├── sorting.html ├── masonry.html ├── horizontal-layout-modes.html ├── filter-sort.html ├── cells-by-row.html ├── jquery.html ├── combination-filters-inclusive.html ├── combination-filters.html ├── masonry-horizontal.html └── v3-release-gif2.html ├── js ├── layout-modes │ ├── vertical.js │ ├── fit-rows.js │ └── masonry.js ├── item.js ├── layout-mode.js └── isotope.js ├── package.json ├── README.md └── dist └── isotope.pkgd.min.js /bin/lint-json.js: -------------------------------------------------------------------------------- 1 | require('../package.json'); 2 | require('../bower.json'); 3 | -------------------------------------------------------------------------------- /bin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 'metafizzy' ], 3 | extends: 'plugin:metafizzy/node', 4 | }; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | isotope-site.zip 3 | components/ 4 | bower_components/ 5 | node_modules/ 6 | sandbox/**/bundle.js 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "devel": false, 4 | "strict": true, 5 | "undef": true, 6 | "unused": true 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/helpers.js: -------------------------------------------------------------------------------- 1 | ( function() { 2 | 3 | 'use strict'; 4 | 5 | // ----- default layout mode ----- // 6 | Isotope.defaults.layoutMode = 'fitRows'; 7 | 8 | } )(); 9 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "devel": false, 4 | "strict": true, 5 | "undef": true, 6 | "unused": true, 7 | "predef": { 8 | "Isotope": false, 9 | "QUnit": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Test case:** https://codepen.io/desandro/pen/mEinp 4 | -------------------------------------------------------------------------------- /bin/version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const version = require('../package.json').version; 3 | 4 | const file = 'js/isotope.js'; 5 | let src = fs.readFileSync( file, 'utf8' ); 6 | src = src.replace( /Isotope v\d+\.\d+\.\d+/, `Isotope v${version}` ); 7 | fs.writeFileSync( file, src, 'utf8' ); 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | plugins: [ 'metafizzy' ], 5 | extends: 'plugin:metafizzy/browser', 6 | env: { 7 | browser: true, 8 | commonjs: true, 9 | }, 10 | parserOptions: { 11 | ecmaVersion: 5, 12 | }, 13 | globals: { 14 | Isotope: 'readonly', 15 | QUnit: 'readonly', 16 | define: 'readonly', 17 | }, 18 | rules: { 19 | 'no-var': 'off', 20 | 'prefer-spread': 'off', 21 | strict: 'off', 22 | }, 23 | ignorePatterns: [ 24 | 'sandbox/browserify/bundle.js', 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /test/unit/masonry-stamp.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'Masonry stamp', function( assert ) { 2 | 'use strict'; 3 | 4 | var iso = new Isotope( '#masonry-stamp', { 5 | layoutMode: 'masonry', 6 | itemSelector: '.item', 7 | stamp: '.stamp', 8 | } ); 9 | 10 | function checkPosition( item, x, y ) { 11 | var elem = item.element; 12 | var left = parseInt( elem.style.left, 10 ); 13 | var top = parseInt( elem.style.top, 10 ); 14 | assert.deepEqual( [ left, top ], [ x, y ], 'item position ' + x + ', ' + y ); 15 | } 16 | 17 | checkPosition( iso.items[0], 0, 0 ); 18 | checkPosition( iso.items[1], 0, 30 ); 19 | checkPosition( iso.items[2], 60, 45 ); 20 | checkPosition( iso.items[3], 120, 45 ); 21 | 22 | } ); 23 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isotope-layout", 3 | "description": "Filter and sort magical layouts", 4 | "main": "js/isotope.js", 5 | "dependencies": { 6 | "desandro-matches-selector": "^2.0.0", 7 | "fizzy-ui-utils": "^2.0.4", 8 | "get-size": "^2.0.0", 9 | "masonry-layout": "^4.1.0", 10 | "outlayer": "^2.1.0" 11 | }, 12 | "devDependencies": { 13 | "jquery": "^3.3.1", 14 | "jquery-bridget": "^2", 15 | "qunit": "^2.6.0" 16 | }, 17 | "ignore": [ 18 | "test/", 19 | "sandbox/", 20 | "**/.*", 21 | "package.json", 22 | "notes.md", 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ], 28 | "homepage": "https://isotope.metafizzy.co", 29 | "authors": [ 30 | "David DeSandro" 31 | ], 32 | "moduleType": [ 33 | "amd", 34 | "globals", 35 | "node" 36 | ], 37 | "keywords": [ 38 | "filter", 39 | "sort", 40 | "masonry", 41 | "jquery-plugin" 42 | ], 43 | "license": "GPL-3.0" 44 | } 45 | -------------------------------------------------------------------------------- /test/unit/fit-rows.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'fitRows', function( assert ) { 2 | 'use strict'; 3 | 4 | var iso = new Isotope( '#fitrows-gutter', { 5 | layoutMode: 'fitRows', 6 | itemSelector: '.item', 7 | transitionDuration: 0, 8 | } ); 9 | 10 | function checkPosition( item, x, y ) { 11 | var elem = item.element; 12 | var left = parseInt( elem.style.left, 10 ); 13 | var top = parseInt( elem.style.top, 10 ); 14 | assert.deepEqual( [ left, top ], [ x, y ], 'item position ' + x + ', ' + y ); 15 | } 16 | 17 | checkPosition( iso.items[0], 0, 0 ); 18 | checkPosition( iso.items[1], 60, 0 ); 19 | 20 | // check gutter 21 | iso.options.fitRows = { 22 | gutter: 10, 23 | }; 24 | iso.layout(); 25 | 26 | checkPosition( iso.items[0], 0, 0 ); 27 | checkPosition( iso.items[1], 70, 0 ); 28 | 29 | // check gutter, with element sizing 30 | iso.options.fitRows = { 31 | gutter: '.gutter-sizer', 32 | }; 33 | iso.layout(); 34 | 35 | checkPosition( iso.items[0], 0, 0 ); 36 | checkPosition( iso.items[1], 78, 0 ); 37 | 38 | } ); 39 | -------------------------------------------------------------------------------- /sandbox/require-js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | require js 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

require js

15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /js/layout-modes/vertical.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vertical layout mode 3 | */ 4 | 5 | ( function( window, factory ) { 6 | // universal module definition 7 | if ( typeof define == 'function' && define.amd ) { 8 | // AMD 9 | define( [ 10 | '../layout-mode', 11 | ], 12 | factory ); 13 | } else if ( typeof module == 'object' && module.exports ) { 14 | // CommonJS 15 | module.exports = factory( 16 | require('../layout-mode') 17 | ); 18 | } else { 19 | // browser global 20 | factory( 21 | window.Isotope.LayoutMode 22 | ); 23 | } 24 | 25 | }( window, function factory( LayoutMode ) { 26 | 'use strict'; 27 | 28 | var Vertical = LayoutMode.create( 'vertical', { 29 | horizontalAlignment: 0, 30 | } ); 31 | 32 | var proto = Vertical.prototype; 33 | 34 | proto._resetLayout = function() { 35 | this.y = 0; 36 | }; 37 | 38 | proto._getItemLayoutPosition = function( item ) { 39 | item.getSize(); 40 | var x = ( this.isotope.size.innerWidth - item.size.outerWidth ) * 41 | this.options.horizontalAlignment; 42 | var y = this.y; 43 | this.y += item.size.outerHeight; 44 | return { x: x, y: y }; 45 | }; 46 | 47 | proto._getContainerSize = function() { 48 | return { height: this.y }; 49 | }; 50 | 51 | return Vertical; 52 | 53 | } ) ); 54 | -------------------------------------------------------------------------------- /test/unit/masonry-measure-columns.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'Masonry.measureColumns', function( assert ) { 2 | 'use strict'; 3 | 4 | var iso = new Isotope( '#masonry-measure-columns', { 5 | itemSelector: '.item', 6 | layoutMode: 'masonry', 7 | transitionDuration: 0, 8 | } ); 9 | 10 | var msnryMode = iso.modes.masonry; 11 | assert.equal( msnryMode.columnWidth, 60, 'after layout, measured first element' ); 12 | 13 | iso.modes.masonry._getMeasurement( 'columnWidth', 'outerWidth' ); 14 | assert.equal( msnryMode.columnWidth, 0, '_getMeasurement, no option' ); 15 | 16 | iso.modes.masonry.measureColumns(); 17 | assert.equal( msnryMode.columnWidth, 60, 'measureColumns, no option' ); 18 | 19 | iso.arrange({ filter: '.c' }); 20 | 21 | iso.modes.masonry.measureColumns(); 22 | assert.equal( msnryMode.columnWidth, 60, 23 | 'measureColumns after filter first item, no option' ); 24 | 25 | iso.arrange({ 26 | masonry: { columnWidth: 80 }, 27 | }); 28 | assert.equal( msnryMode.columnWidth, 80, 29 | '.arrange() masonry.columnWidth option set number' ); 30 | 31 | iso.arrange({ 32 | masonry: { columnWidth: '.grid-sizer' }, 33 | }); 34 | assert.equal( msnryMode.columnWidth, 70, 35 | '.arrange() masonry.columnWidth option set selector string' ); 36 | 37 | } ); 38 | -------------------------------------------------------------------------------- /bin/bundle-js.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const requirejs = require('requirejs'); 3 | 4 | // get banner 5 | let isotopeJsSrc = fs.readFileSync( 'js/isotope.js', 'utf8' ); 6 | let banner = isotopeJsSrc.split(' */')[0] + ' */\n\n'; 7 | banner = banner.replace( 'Isotope', 'Isotope PACKAGED' ); 8 | 9 | let options = { 10 | out: 'dist/isotope.pkgd.js', 11 | baseUrl: 'bower_components', 12 | optimize: 'none', 13 | include: [ 14 | 'jquery-bridget/jquery-bridget', 15 | 'isotope-layout/js/isotope', 16 | ], 17 | paths: { 18 | 'isotope-layout': '../', 19 | jquery: 'empty:', 20 | }, 21 | }; 22 | 23 | requirejs.optimize( 24 | options, 25 | function() { 26 | let pkgdSrc = fs.readFileSync( options.out, 'utf8' ); 27 | let definitionRE = /define\(\s*'isotope-layout\/js\/isotope'(.|\n)+\],/; 28 | // remove named module 29 | pkgdSrc.replace( definitionRE, function( definition ) { 30 | // remove named module 31 | return definition.replace( "'isotope-layout/js/isotope',", '' ) 32 | // use explicit file paths, './item' -> 'isotope-layout/js/item' 33 | .replace( /'.\//g, "'isotope-layout/js/" ); 34 | } ); 35 | pkgdSrc = banner + pkgdSrc; 36 | fs.writeFileSync( options.out, pkgdSrc ); 37 | }, 38 | function( err ) { 39 | throw new Error( err ); 40 | }, 41 | ); 42 | -------------------------------------------------------------------------------- /sandbox/browserify/jq-main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable id-length */ 2 | 3 | var Isotope = window.Isotope = require('../../js/isotope'); 4 | var $ = require('jquery'); 5 | require('jquery-bridget'); 6 | 7 | // enable $().isotope() plugin 8 | $.bridget( 'isotope', Isotope ); 9 | 10 | var $container = $('#container').isotope({ 11 | layoutMode: 'fitRows', 12 | transitionDuration: '0.8s', 13 | cellsByRow: { 14 | columnWidth: 130, 15 | rowHeight: 140, 16 | }, 17 | getSortData: { 18 | number: '.number parseInt', 19 | symbol: '.symbol', 20 | name: '.name', 21 | category: '[data-category]', 22 | weight: function( itemElem ) { 23 | // remove parenthesis 24 | return parseFloat( $( itemElem ).find('.weight') 25 | .text() 26 | .replace( /[()]/g, '' ) ); 27 | }, 28 | }, 29 | }); 30 | 31 | $('#options').on( 'click', 'button', function( event ) { 32 | var $target = $( event.target ); 33 | var key = $target.parent().attr('data-isotope-key'); 34 | var value = $target.attr('data-isotope-value'); 35 | 36 | if ( key === 'filter' && value === 'number-greater-than-50' ) { 37 | value = function( elem ) { 38 | var numberText = $( elem ).find('.number') 39 | .text(); 40 | return parseInt( numberText, 10 ) > 40; 41 | }; 42 | } 43 | console.log( key, value ); 44 | var opts = {}; 45 | opts[ key ] = value; 46 | $container.isotope( opts ); 47 | } ); 48 | -------------------------------------------------------------------------------- /test/unit/get-segment-size.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'LayoutMode.getSegmentSize', function( assert ) { 2 | 'use strict'; 3 | 4 | var CellsByRow = Isotope.LayoutMode.create('cellsByRow'); 5 | 6 | CellsByRow.prototype._resetLayout = function() { 7 | this.getColumnWidth(); 8 | this.getRowHeight(); 9 | }; 10 | 11 | var iso = new Isotope( '#get-segment-size', { 12 | layoutMode: 'cellsByRow', 13 | itemSelector: '.item', 14 | cellsByRow: { 15 | columnWidth: 17, 16 | rowHeight: 23, 17 | }, 18 | } ); 19 | 20 | var cellsByRow = iso.modes.cellsByRow; 21 | assert.equal( cellsByRow.columnWidth, 17, 'explicit columnWidth option set' ); 22 | assert.equal( cellsByRow.rowHeight, 23, 'explicit rowHeight option set' ); 23 | 24 | // set element sizing 25 | iso.options.cellsByRow.columnWidth = '.grid-sizer'; 26 | iso.options.cellsByRow.rowHeight = '.grid-sizer'; 27 | cellsByRow.getColumnWidth(); 28 | cellsByRow.getRowHeight(); 29 | assert.equal( cellsByRow.columnWidth, 57, 'element sizing columnWidth' ); 30 | assert.equal( cellsByRow.rowHeight, 47, 'element sizing rowHeight' ); 31 | 32 | // default to first item 33 | delete iso.options.cellsByRow.columnWidth; 34 | delete iso.options.cellsByRow.rowHeight; 35 | cellsByRow.getColumnWidth(); 36 | cellsByRow.getRowHeight(); 37 | assert.equal( cellsByRow.columnWidth, 60, 'first item columnWidth' ); 38 | assert.equal( cellsByRow.rowHeight, 30, 'first item rowHeight' ); 39 | 40 | } ); 41 | -------------------------------------------------------------------------------- /test/tests.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: sans-serif; 9 | } 10 | 11 | .container { 12 | background: #EEE; 13 | width: 180px; 14 | margin-bottom: 20px; 15 | position: relative; 16 | } 17 | 18 | .container:after { 19 | content: ''; 20 | display: block; 21 | clear: both; 22 | } 23 | 24 | .item { 25 | width: 60px; 26 | height: 30px; 27 | float: left; 28 | border: 1px solid; 29 | background: #3BF; 30 | } 31 | 32 | .item.w2 { width: 120px; } 33 | .item.w3 { width: 180px; } 34 | 35 | .item.h2 { height: 50px; } 36 | .item.h3 { height: 70px; } 37 | .item.h4 { height: 90px; } 38 | .item.h5 { height: 110px; } 39 | 40 | /* ---- stamp ---- */ 41 | 42 | 43 | .stamp { 44 | background: red; 45 | opacity: 0.75; 46 | position: absolute; 47 | border: 1px solid; 48 | } 49 | 50 | .has-stamp { 51 | position: relative; 52 | } 53 | 54 | /* ---- get segment size ---- */ 55 | 56 | #get-segment-size .grid-sizer { 57 | /* 57 outer width */ 58 | width: 52px; 59 | margin-right: 5px; 60 | /* 47 outer height */ 61 | height: 40px; 62 | margin-bottom: 7px; 63 | } 64 | 65 | #masonry-measure-columns .grid-sizer { 66 | width: 70px; 67 | } 68 | 69 | /* ---- masonry stamp ---- */ 70 | 71 | #masonry-stamp .stamp1 { 72 | width: 80px; 73 | height: 30px; 74 | right: 25px; 75 | top: 15px; 76 | } 77 | 78 | /* ---- fit rows ---- */ 79 | 80 | #fitrows-gutter .gutter-sizer { 81 | width: 10%; 82 | } 83 | -------------------------------------------------------------------------------- /js/layout-modes/fit-rows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fitRows layout mode 3 | */ 4 | 5 | ( function( window, factory ) { 6 | // universal module definition 7 | if ( typeof define == 'function' && define.amd ) { 8 | // AMD 9 | define( [ 10 | '../layout-mode', 11 | ], 12 | factory ); 13 | } else if ( typeof exports == 'object' ) { 14 | // CommonJS 15 | module.exports = factory( 16 | require('../layout-mode') 17 | ); 18 | } else { 19 | // browser global 20 | factory( 21 | window.Isotope.LayoutMode 22 | ); 23 | } 24 | 25 | }( window, function factory( LayoutMode ) { 26 | 'use strict'; 27 | 28 | var FitRows = LayoutMode.create('fitRows'); 29 | 30 | var proto = FitRows.prototype; 31 | 32 | proto._resetLayout = function() { 33 | this.x = 0; 34 | this.y = 0; 35 | this.maxY = 0; 36 | this._getMeasurement( 'gutter', 'outerWidth' ); 37 | }; 38 | 39 | proto._getItemLayoutPosition = function( item ) { 40 | item.getSize(); 41 | 42 | var itemWidth = item.size.outerWidth + this.gutter; 43 | // if this element cannot fit in the current row 44 | var containerWidth = this.isotope.size.innerWidth + this.gutter; 45 | if ( this.x !== 0 && itemWidth + this.x > containerWidth ) { 46 | this.x = 0; 47 | this.y = this.maxY; 48 | } 49 | 50 | var position = { 51 | x: this.x, 52 | y: this.y, 53 | }; 54 | 55 | this.maxY = Math.max( this.maxY, this.y + item.size.outerHeight ); 56 | this.x += itemWidth; 57 | 58 | return position; 59 | }; 60 | 61 | proto._getContainerSize = function() { 62 | return { height: this.maxY }; 63 | }; 64 | 65 | return FitRows; 66 | 67 | } ) ); 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isotope-layout", 3 | "version": "3.0.6", 4 | "description": "Filter and sort magical layouts", 5 | "main": "js/isotope.js", 6 | "files": [ 7 | "js", 8 | "dist" 9 | ], 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "lint": "node bin/lint-json.js && npx eslint .", 13 | "lintFix": "npx eslint . --fix", 14 | "dist": "npm run bundleJs && npm run uglify", 15 | "bundleJs": "node bin/bundle-js.js", 16 | "uglify": "npx uglifyjs dist/isotope.pkgd.js -o dist/isotope.pkgd.min.js --mangle --comments /^!/", 17 | "version": "node bin/version.js && npm run dist && git add -A css js dist" 18 | }, 19 | "dependencies": { 20 | "desandro-matches-selector": "^2.0.0", 21 | "fizzy-ui-utils": "^2.0.4", 22 | "get-size": "^2.0.0", 23 | "masonry-layout": "^4.1.0", 24 | "outlayer": "^2.1.0" 25 | }, 26 | "devDependencies": { 27 | "eslint": "^6.8.0", 28 | "eslint-plugin-metafizzy": "^1.0.0", 29 | "jquery": "^3.3.1", 30 | "jquery-bridget": "^2", 31 | "jshint": "^2.11.0", 32 | "qunitjs": "^1.15", 33 | "requirejs": "^2.3.6", 34 | "uglifyjs": "^2.4.11" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/metafizzy/isotope.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/metafizzy/isotope/issues" 42 | }, 43 | "homepage": "https://isotope.metafizzy.co", 44 | "directories": { 45 | "test": "test" 46 | }, 47 | "keywords": [ 48 | "DOM", 49 | "browser", 50 | "masonry", 51 | "layout", 52 | "filter", 53 | "sort", 54 | "jquery-plugin" 55 | ], 56 | "author": "Metafizzy", 57 | "license": "GPL-3.0" 58 | } 59 | -------------------------------------------------------------------------------- /sandbox/browserify/main.js: -------------------------------------------------------------------------------- 1 | var Isotope = window.Isotope = require('../../js/isotope'); 2 | var eventie = require('eventie'); 3 | var matchesSelector = require('desandro-matches-selector'); 4 | 5 | // require('isotope-fit-columns'); 6 | // require('isotope-cells-by-column'); 7 | // require('isotope-horizontal'); 8 | // require('isotope-masonry-horizontal'); 9 | 10 | function getText( elem ) { 11 | return elem.textContent || elem.innerText; 12 | } 13 | 14 | var iso = window.iso = new Isotope( '#container', { 15 | layoutMode: 'fitRows', 16 | transitionDuration: '0.8s', 17 | cellsByRow: { 18 | columnWidth: 130, 19 | rowHeight: 140, 20 | }, 21 | getSortData: { 22 | number: '.number parseInt', 23 | symbol: '.symbol', 24 | name: '.name', 25 | category: '[data-category]', 26 | weight: function( itemElem ) { 27 | // remove parenthesis 28 | var weight = itemElem.querySelector('.weight').textContent; 29 | return parseFloat( weight.replace( /[()]/g, '' ) ); 30 | }, 31 | }, 32 | } ); 33 | 34 | var options = document.querySelector('#options'); 35 | 36 | eventie.bind( options, 'click', function( event ) { 37 | if ( !matchesSelector( event.target, 'button' ) ) { 38 | return; 39 | } 40 | 41 | var key = event.target.parentNode.getAttribute('data-isotope-key'); 42 | var value = event.target.getAttribute('data-isotope-value'); 43 | 44 | if ( key === 'filter' && value === 'number-greater-than-50' ) { 45 | value = function( elem ) { 46 | var numberText = getText( elem.querySelector('.number') ); 47 | return parseInt( numberText, 10 ) > 40; 48 | }; 49 | } 50 | console.log( key, value ); 51 | iso.options[ key ] = value; 52 | iso.arrange(); 53 | } ); 54 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | ## Submitting issues 2 | 3 | ### Reduced test case required 4 | 5 | All bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/). Create one by forking any one of the [CodePen examples](https://codepen.io/desandro/tag/isotope-docs) from [the docs](https://isotope.metafizzy.co). 6 | 7 | **CodePens** 8 | 9 | + [Filtering](https://codepen.io/desandro/pen/Ehgij) 10 | + [Sorting](https://codepen.io/desandro/pen/lzCqe) 11 | + [Filtering and sorting](https://codepen.io/desandro/pen/nFrte) 12 | + [Masonry layout](https://codepen.io/desandro/pen/mEinp) 13 | + [Fluid Masonry layout](https://codepen.io/desandro/pen/mIkhq) 14 | 15 | **Test cases** 16 | 17 | + A reduced test case clearly demonstrates the bug or issue. 18 | + It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug. 19 | + A link to your production site is **not** a reduced test case. 20 | 21 | Providing a reduced test case is the best way to get your issue addressed. Without a reduced test case, your issue may be closed. 22 | 23 | ## Pull requests 24 | 25 | Contributions are welcome! 26 | 27 | Your code may be used as part of a commercial product if merged. Be clear about what license applies to your patch. [The MIT license](https://choosealicense.com/licenses/mit/) or [public domain unlicense](https://choosealicense.com/licenses/unlicense/) are permissive, and allow integration of your patch into Isotope as part of a commercial product. 28 | 29 | Do not edit `dist/` files. I'll update these after your PR has been merged. 30 | 31 | ### Development setup / Sandbox 32 | 33 | After a fresh git clone, to run the examples in sandbox, you will first need to run (from the root directory): 34 | 35 | ```shell 36 | $ npm install bower 37 | $ bower install 38 | ``` 39 | -------------------------------------------------------------------------------- /sandbox/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | basic 7 | 8 | 9 | 10 | 11 | 12 | 13 |

basic

14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /sandbox/js/require-js.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable id-length */ 2 | /* globals requirejs */ 3 | 4 | // -------------------------- bower -------------------------- // 5 | 6 | /* 7 | // with bower components 8 | requirejs.config({ 9 | baseUrl: '../bower_components' 10 | }); 11 | 12 | requirejs( [ '../js/isotope' ], function( Isotope ) { 13 | new Isotope( '#basic', { 14 | masonry: { 15 | columnWidth: 60 16 | } 17 | }); 18 | }); 19 | // */ 20 | 21 | // -------------------------- pkgd -------------------------- // 22 | 23 | /* 24 | requirejs( [ '../dist/isotope.pkgd.js' ], function( Isotope ) { 25 | new Isotope( '#basic', { 26 | layoutMode: 'masonry', 27 | masonry: { 28 | columnWidth: 60 29 | } 30 | }); 31 | }); 32 | // */ 33 | 34 | // -------------------------- bower & jQuery -------------------------- // 35 | 36 | /* 37 | requirejs.config({ 38 | baseUrl: '../bower_components', 39 | paths: { 40 | jquery: 'jquery/dist/jquery' 41 | } 42 | }) 43 | 44 | requirejs( [ 45 | 'jquery', 46 | 'isotope/js/isotope', 47 | 'jquery-bridget/jquery-bridget' 48 | ], 49 | function( $, Isotope ) { 50 | $.bridget( 'isotope', Isotope ); 51 | $('#basic').isotope({ 52 | masonry: { 53 | columnWidth: 60 54 | } 55 | }); 56 | }); 57 | 58 | // */ 59 | 60 | // -------------------------- pkgd & jQuery -------------------------- // 61 | 62 | // /* 63 | requirejs.config({ 64 | paths: { 65 | jquery: '../../bower_components/jquery/dist/jquery', 66 | }, 67 | }); 68 | 69 | requirejs( [ 'require', 'jquery', '../dist/isotope.pkgd.js' ], 70 | function( require, $, Isotope ) { 71 | require( [ 72 | 'jquery-bridget/jquery-bridget', 73 | ], 74 | function() { 75 | $.bridget( 'isotope', Isotope ); 76 | $('#basic').isotope({ 77 | masonry: { 78 | columnWidth: 60, 79 | }, 80 | }); 81 | } ); 82 | } ); 83 | 84 | // */ 85 | 86 | -------------------------------------------------------------------------------- /js/item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Isotope Item 3 | **/ 4 | 5 | ( function( window, factory ) { 6 | // universal module definition 7 | if ( typeof define == 'function' && define.amd ) { 8 | // AMD 9 | define( [ 10 | 'outlayer/outlayer', 11 | ], 12 | factory ); 13 | } else if ( typeof module == 'object' && module.exports ) { 14 | // CommonJS 15 | module.exports = factory( 16 | require('outlayer') 17 | ); 18 | } else { 19 | // browser global 20 | window.Isotope = window.Isotope || {}; 21 | window.Isotope.Item = factory( 22 | window.Outlayer 23 | ); 24 | } 25 | 26 | }( window, function factory( Outlayer ) { 27 | 'use strict'; 28 | 29 | // -------------------------- Item -------------------------- // 30 | 31 | // sub-class Outlayer Item 32 | function Item() { 33 | Outlayer.Item.apply( this, arguments ); 34 | } 35 | 36 | var proto = Item.prototype = Object.create( Outlayer.Item.prototype ); 37 | 38 | var _create = proto._create; 39 | proto._create = function() { 40 | // assign id, used for original-order sorting 41 | this.id = this.layout.itemGUID++; 42 | _create.call( this ); 43 | this.sortData = {}; 44 | }; 45 | 46 | proto.updateSortData = function() { 47 | if ( this.isIgnored ) { 48 | return; 49 | } 50 | // default sorters 51 | this.sortData.id = this.id; 52 | // for backward compatibility 53 | this.sortData['original-order'] = this.id; 54 | this.sortData.random = Math.random(); 55 | // go thru getSortData obj and apply the sorters 56 | var getSortData = this.layout.options.getSortData; 57 | var sorters = this.layout._sorters; 58 | for ( var key in getSortData ) { 59 | var sorter = sorters[ key ]; 60 | this.sortData[ key ] = sorter( this.element, this ); 61 | } 62 | }; 63 | 64 | var _destroy = proto.destroy; 65 | proto.destroy = function() { 66 | // call super 67 | _destroy.apply( this, arguments ); 68 | // reset display, #741 69 | this.css({ 70 | display: '', 71 | }); 72 | }; 73 | 74 | return Item; 75 | 76 | } ) ); 77 | -------------------------------------------------------------------------------- /js/layout-modes/masonry.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Masonry layout mode 3 | * sub-classes Masonry 4 | * https://masonry.desandro.com 5 | */ 6 | 7 | ( function( window, factory ) { 8 | // universal module definition 9 | if ( typeof define == 'function' && define.amd ) { 10 | // AMD 11 | define( [ 12 | '../layout-mode', 13 | 'masonry-layout/masonry', 14 | ], 15 | factory ); 16 | } else if ( typeof module == 'object' && module.exports ) { 17 | // CommonJS 18 | module.exports = factory( 19 | require('../layout-mode'), 20 | require('masonry-layout') 21 | ); 22 | } else { 23 | // browser global 24 | factory( 25 | window.Isotope.LayoutMode, 26 | window.Masonry 27 | ); 28 | } 29 | 30 | }( window, function factory( LayoutMode, Masonry ) { 31 | 'use strict'; 32 | 33 | // -------------------------- masonryDefinition -------------------------- // 34 | 35 | // create an Outlayer layout class 36 | var MasonryMode = LayoutMode.create('masonry'); 37 | 38 | var proto = MasonryMode.prototype; 39 | 40 | var keepModeMethods = { 41 | _getElementOffset: true, 42 | layout: true, 43 | _getMeasurement: true, 44 | }; 45 | 46 | // inherit Masonry prototype 47 | for ( var method in Masonry.prototype ) { 48 | // do not inherit mode methods 49 | if ( !keepModeMethods[ method ] ) { 50 | proto[ method ] = Masonry.prototype[ method ]; 51 | } 52 | } 53 | 54 | var measureColumns = proto.measureColumns; 55 | proto.measureColumns = function() { 56 | // set items, used if measuring first item 57 | this.items = this.isotope.filteredItems; 58 | measureColumns.call( this ); 59 | }; 60 | 61 | // point to mode options for fitWidth 62 | var _getOption = proto._getOption; 63 | proto._getOption = function( option ) { 64 | if ( option == 'fitWidth' ) { 65 | return this.options.isFitWidth !== undefined ? 66 | this.options.isFitWidth : this.options.fitWidth; 67 | } 68 | return _getOption.apply( this.isotope, arguments ); 69 | }; 70 | 71 | return MasonryMode; 72 | 73 | } ) ); 74 | -------------------------------------------------------------------------------- /sandbox/transition-bug.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | transition bug 7 | 8 | 9 | 12 | 13 | 14 | 15 |

transition bug

16 | 17 |
18 | 19 |
20 | 21 |
22 | 23 | 29 | 30 |
31 |

51

32 |

Sb

33 |

Antimony

34 |

121.76

35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /test/unit/filtering.js: -------------------------------------------------------------------------------- 1 | /* globals jQuery */ 2 | 3 | QUnit.test( 'filtering', function( assert ) { 4 | 'use strict'; 5 | 6 | var iso = new Isotope( '#filtering', { 7 | isJQueryFiltering: false, 8 | transitionDuration: 0, 9 | } ); 10 | 11 | var ids = getFilteredItemIDs( iso ); 12 | assert.equal( ids, '1,2,3,4,5,6,7', 'all items there by default' ); 13 | 14 | function checkFilter( filter, expectedIDs, message ) { 15 | iso.arrange({ filter: filter }); 16 | ids = getFilteredItemIDs( iso ); 17 | assert.equal( ids, expectedIDs, message || filter ); 18 | } 19 | 20 | checkFilter( '.orange', '1,3,6,7' ); 21 | checkFilter( '.tall', '3,4,7' ); 22 | checkFilter( '.tall.orange', '3,7' ); 23 | 24 | iso.arrange({ 25 | filter: function( elem ) { 26 | var num = parseInt( elem.textContent, 10 ); 27 | return num > 5; 28 | }, 29 | }); 30 | ids = getFilteredItemIDs( iso ); 31 | assert.equal( ids, '4,5,7', 'function, text is greater than 5' ); 32 | 33 | // filter with jQuery 34 | iso.options.isJQueryFiltering = true; 35 | 36 | checkFilter( '.orange', '1,3,6,7', '.orange with jQuery' ); 37 | checkFilter( '.tall', '3,4,7', '.orange with jQuery' ); 38 | checkFilter( '.tall.orange', '3,7', '.tall.orange with jQuery' ); 39 | 40 | checkFilter( ':not(.orange)', '2,4,5' ); 41 | checkFilter( '.orange:not(.tall)', '1,6' ); 42 | 43 | iso.arrange({ 44 | filter: function() { 45 | var num = parseInt( jQuery( this ).text(), 10 ); 46 | return num > 5; 47 | }, 48 | }); 49 | ids = getFilteredItemIDs( iso ); 50 | assert.equal( ids, '4,5,7', 'function, text is greater than 5, with jQuery' ); 51 | 52 | // ----- helper ----- // 53 | 54 | /* 55 |
5
56 |
3
57 |
2
58 |
9
59 |
7
60 |
1
61 |
8
62 | */ 63 | 64 | // return a string of item ids 65 | function getFilteredItemIDs( isotope ) { 66 | return isotope.filteredItems.map( function( item ) { 67 | return item.element.getAttribute('data-item-id'); 68 | } ); 69 | } 70 | 71 | } ); 72 | -------------------------------------------------------------------------------- /test/unit/sorting.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'sorting', function( assert ) { 2 | 3 | 'use strict'; 4 | 5 | // sorting with history 6 | ( function() { 7 | var iso = new Isotope( '#sorting1', { 8 | layoutMode: 'fitRows', 9 | transitionDuration: 0, 10 | getSortData: { 11 | letter: 'b', 12 | number: 'i', 13 | }, 14 | sortBy: 'number', 15 | } ); 16 | 17 | iso.arrange({ sortBy: 'letter' }); 18 | 19 | var texts = getItemsText( iso ); 20 | 21 | assert.equal( texts, 'A1,A2,A3,A4,B1,B2,B4', 22 | 'items sorted by letter, then number, via history' ); 23 | 24 | iso.destroy(); 25 | } )(); 26 | 27 | // sorting with array 28 | ( function() { 29 | var iso = new Isotope( '#sorting1', { 30 | layoutMode: 'fitRows', 31 | transitionDuration: 0, 32 | getSortData: { 33 | letter: 'b', 34 | number: 'i', 35 | }, 36 | sortBy: [ 'letter', 'number' ], 37 | } ); 38 | 39 | assert.equal( getItemsText( iso ), 'A1,A2,A3,A4,B1,B2,B4', 'sortBy array' ); 40 | 41 | iso.arrange({ 42 | sortAscending: false, 43 | }); 44 | assert.equal( getItemsText( iso ), 'B4,B2,B1,A4,A3,A2,A1', 'sortAscending false' ); 45 | 46 | iso.arrange({ 47 | sortAscending: { 48 | letter: true, 49 | number: false, 50 | }, 51 | }); 52 | assert.equal( getItemsText( iso ), 53 | 'A4,A3,A2,A1,B4,B2,B1', 'sortAscending with object' ); 54 | 55 | iso.destroy(); 56 | } )(); 57 | 58 | ( function() { 59 | var iso = new Isotope( '#sorting2', { 60 | layoutMode: 'fitRows', 61 | transitionDuration: 0, 62 | getSortData: { 63 | letter: 'b', 64 | number: 'i', 65 | axis: 'span', 66 | }, 67 | sortBy: [ 'axis' ], 68 | } ); 69 | 70 | iso.arrange({ sortBy: 'number' }); 71 | assert.equal( getItemsText( iso ), 'B1X,A1X,B1Y,A1Y,B2X,A2X,B2Y,A2Y', 72 | 'sort history 1' ); 73 | 74 | iso.arrange({ sortBy: 'letter' }); 75 | assert.equal( getItemsText( iso ), 'A1X,A1Y,A2X,A2Y,B1X,B1Y,B2X,B2Y', 76 | 'sort history 2' ); 77 | 78 | } )(); 79 | 80 | function getItemsText( iso ) { 81 | var texts = iso.filteredItems.map( function( item ) { 82 | return item.element.textContent; 83 | } ); 84 | return texts.join(','); 85 | } 86 | 87 | } ); 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Isotope 2 | 3 | _Filter & sort magical layouts_ 4 | 5 | See [isotope.metafizzy.co](https://isotope.metafizzy.co) for complete docs and demos. 6 | 7 | ## Install 8 | 9 | ### Download 10 | 11 | + [isotope.pkgd.js](https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.js) un-minified, or 12 | + [isotope.pkgd.min.js](https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js) minified 13 | 14 | ### CDN 15 | 16 | Link directly to Isotope files on [unpkg](https://unpkg.com). 17 | 18 | ``` html 19 | 20 | 21 | 22 | ``` 23 | 24 | ### Package managers 25 | 26 | npm: `npm install isotope-layout --save` 27 | 28 | Bower: `bower install isotope-layout --save` 29 | 30 | ## License 31 | 32 | ### Commercial license 33 | 34 | If you want to use Isotope to develop commercial sites, themes, projects, and applications, the Commercial license is the appropriate license. With this option, your source code is kept proprietary. Purchase an Isotope Commercial License at [isotope.metafizzy.co](https://isotope.metafizzy.co/#commercial-license) 35 | 36 | ### Open source license 37 | 38 | If you are creating an open source application under a license compatible with the [GNU GPL license v3](https://www.gnu.org/licenses/gpl-3.0.html), you may use Isotope under the terms of the GPLv3. 39 | 40 | [Read more about Isotope's license](https://isotope.metafizzy.co/license.html). 41 | 42 | ## Initialize 43 | 44 | With jQuery 45 | 46 | ``` js 47 | $('.grid').isotope({ 48 | // options... 49 | itemSelector: '.grid-item', 50 | masonry: { 51 | columnWidth: 200 52 | } 53 | }); 54 | ``` 55 | 56 | With vanilla JavaScript 57 | 58 | ``` js 59 | // vanilla JS 60 | var grid = document.querySelector('.grid'); 61 | var iso = new Isotope( grid, { 62 | // options... 63 | itemSelector: '.grid-item', 64 | masonry: { 65 | columnWidth: 200 66 | } 67 | }); 68 | ``` 69 | 70 | With HTML 71 | 72 | Add a `data-isotope` attribute to your element. Options can be set in JSON in the value. 73 | 74 | ``` html 75 |
77 |
78 |
79 | ... 80 |
81 | ``` 82 | 83 | * * * 84 | 85 | By [Metafizzy 🌈🐻](https://metafizzy.co), 2010–2020 86 | -------------------------------------------------------------------------------- /sandbox/fluid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | fluid 7 | 8 | 9 | 10 | 26 | 27 | 28 | 29 | 30 |

fluid

31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /sandbox/sandbox.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | #container, 6 | .container { 7 | background: #EEE; 8 | width: 50%; 9 | margin-bottom: 20px; 10 | } 11 | 12 | .item { 13 | width: 60px; 14 | height: 60px; 15 | float: left; 16 | border: 1px solid; 17 | background: #09F; 18 | } 19 | 20 | .item.w2 { width: 120px; } 21 | .item.w3 { width: 180px; } 22 | 23 | .item.h2 { height: 100px; } 24 | .item.h3 { height: 160px; } 25 | .item.h4 { height: 220px; } 26 | .item.h5 { height: 280px; } 27 | 28 | .stamp { 29 | background: red; 30 | opacity: 0.75; 31 | position: absolute; 32 | border: 1px solid; 33 | } 34 | 35 | 36 | /* element */ 37 | 38 | .element { 39 | width: 80px; 40 | height: 90px; 41 | margin: 5px; 42 | background: #DDD; 43 | float: left; 44 | position: relative; 45 | padding: 5px; 46 | } 47 | 48 | .element > * { 49 | margin: 0; 50 | } 51 | 52 | .element .number { 53 | right: 5px; 54 | top: 5px; 55 | position: absolute; 56 | } 57 | 58 | .element .symbol { 59 | font-size: 30px; 60 | left: 5px; 61 | top: 5px; 62 | color: white; 63 | } 64 | 65 | .element .name { 66 | font-size: 14px; 67 | } 68 | 69 | .element .weight { 70 | font-size: 14px; 71 | } 72 | 73 | .element.alkali { background: #F00; background: hsl( 0, 100%, 50%); } 74 | .element.alkaline-earth { background: #F80; background: hsl( 36, 100%, 50%); } 75 | .element.lanthanoid { background: #FF0; background: hsl( 72, 100%, 50%); } 76 | .element.actinoid { background: #0F0; background: hsl( 108, 100%, 50%); } 77 | .element.transition { background: #0F8; background: hsl( 144, 100%, 50%); } 78 | .element.post-transition { background: #0FF; background: hsl( 180, 100%, 50%); } 79 | .element.metalloid { background: #08F; background: hsl( 216, 100%, 50%); } 80 | .element.other.nonmetal { background: #00F; background: hsl( 252, 100%, 50%); } 81 | .element.halogen { background: #F0F; background: hsl( 288, 100%, 50%); } 82 | .element.noble-gas { background: #F08; background: hsl( 324, 100%, 50%); } 83 | 84 | /* stamps */ 85 | 86 | .stamp { 87 | position: absolute; 88 | background: hsla(0, 100%, 50%, 0.8); 89 | border: 1px solid; 90 | } 91 | 92 | .stamp1 { 93 | left: 10%; 94 | top: 20px; 95 | width: 20%; 96 | height: 200px; 97 | } 98 | 99 | .stamp2 { 100 | right: 200px; 101 | top: 100px; 102 | width: 100px; 103 | height: 100px; 104 | } 105 | -------------------------------------------------------------------------------- /sandbox/stamps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | stamps 7 | 8 | 9 | 10 | 11 | 48 | 49 | 50 | 51 | 52 |

stamps

53 | 54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /sandbox/insert.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | insert 8 | 9 | 10 | 11 | 12 | 13 | 14 |

insert

15 | 16 |

17 | 18 | 19 | 20 |

21 | 22 |
23 |
49
24 |
35
25 |
60
26 |
29
27 |
78
28 |
92
29 |
10
30 |
55
31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /test/unit/arrange-complete.js: -------------------------------------------------------------------------------- 1 | QUnit.test( 'arrangeComplete', function( assert ) { 2 | 'use strict'; 3 | 4 | var iso = new Isotope( '#arrange-complete', { 5 | layoutMode: 'fitRows', 6 | transitionDuration: '0.1s', 7 | } ); 8 | 9 | var done = assert.async(); 10 | 11 | var tests = [ 12 | function() { 13 | iso.once( 'arrangeComplete', function() { 14 | assert.ok( true, 'arrangeComplete after some were filtered' ); 15 | next(); 16 | } ); 17 | 18 | iso.arrange({ 19 | filter: '.a1', 20 | }); 21 | }, 22 | function() { 23 | iso.once( 'arrangeComplete', function() { 24 | assert.ok( true, 'after some revealed, some hidden, some same' ); 25 | next(); 26 | } ); 27 | 28 | iso.arrange({ 29 | filter: '.b2', 30 | }); 31 | }, 32 | function() { 33 | iso.once( 'arrangeComplete', function() { 34 | assert.ok( true, 'after random sort' ); 35 | next(); 36 | } ); 37 | 38 | iso.arrange({ 39 | sortBy: 'random', 40 | }); 41 | }, 42 | function() { 43 | var ticks = 0; 44 | function onArrangeComplete() { 45 | ticks++; 46 | if ( ticks == 2 ) { 47 | assert.ok( true, 'after layout mid-way thru transition' ); 48 | iso.off( 'arrangeComplete', onArrangeComplete ); 49 | return next(); 50 | } else if ( ticks > 2 ) { 51 | assert.ok( false, 'more ticks happened' ); 52 | } 53 | } 54 | 55 | iso.on( 'arrangeComplete', onArrangeComplete ); 56 | 57 | iso.arrange({ 58 | filter: '.a2', 59 | transitionDuration: '0.6s', 60 | }); 61 | 62 | setTimeout( function() { 63 | iso.arrange({ 64 | filter: '.b2', 65 | }); 66 | }, 300 ); 67 | }, 68 | // stagger 69 | function() { 70 | iso.once( 'arrangeComplete', function() { 71 | assert.ok( true, 'arrangeComplete with stagger' ); 72 | next(); 73 | } ); 74 | 75 | iso.arrange({ 76 | stagger: 100, 77 | sortBy: 'random', 78 | filter: '*', 79 | transitionDuration: '0.4s', 80 | }); 81 | }, 82 | // stagger, triggered mid-transition 83 | function() { 84 | var ticks = 0; 85 | function onArrangeComplete() { 86 | ticks++; 87 | if ( ticks == 2 ) { 88 | assert.ok( true, 'after layout mid-way thru transition, with stagger' ); 89 | iso.off( 'arrangeComplete', onArrangeComplete ); 90 | iso.options.stagger = 0; 91 | return next(); 92 | } else if ( ticks > 2 ) { 93 | assert.ok( false, 'more ticks happened' ); 94 | } 95 | } 96 | 97 | iso.on( 'arrangeComplete', onArrangeComplete ); 98 | 99 | iso.arrange({ 100 | stagger: 100, 101 | sortBy: 'random', 102 | transitionDuration: '0.4s', 103 | }); 104 | 105 | setTimeout( function() { 106 | iso.arrange({ 107 | filter: '.a1', 108 | }); 109 | }, 250 ); 110 | }, 111 | ]; 112 | 113 | function next() { 114 | if ( tests.length ) { 115 | var nextTest = tests.shift(); 116 | // HACK for consecutive arrangeComplete calls 117 | setTimeout( nextTest ); 118 | } else { 119 | done(); 120 | } 121 | } 122 | 123 | next(); 124 | 125 | } ); 126 | -------------------------------------------------------------------------------- /sandbox/right-to-left.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | right to left 7 | 8 | 9 | 10 | 26 | 27 | 28 | 29 | 30 |

right to left

31 | 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /sandbox/bottom-up.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | bottom up 7 | 8 | 9 | 10 | 25 | 26 | 27 | 28 | 29 |

bottom up

30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /sandbox/fitrows.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | fitRows 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 |

fitRows

18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /test/unit/sort-data.js: -------------------------------------------------------------------------------- 1 | /* globals jQuery */ 2 | 3 | QUnit.test( 'sort data', function( assert ) { 4 | 5 | 'use strict'; 6 | 7 | var iso = new Isotope( '#get-sort-data', { 8 | layoutMode: 'fitRows', 9 | getSortData: { 10 | ninjaTurtle: '[data-ninja-turtle]', 11 | fruit: 'span.fruit', 12 | b: 'b parseFloat', 13 | i: 'i parseInt', 14 | bbroke: 'b foobar', 15 | }, 16 | } ); 17 | 18 | var item0 = iso.items[0]; 19 | var item1 = iso.items[1]; 20 | 21 | assert.equal( item0.sortData.ninjaTurtle, 'leonardo', '[data-attr] shorthand' ); 22 | assert.equal( item0.sortData.fruit, 'watermelon', 'query selector shorthand' ); 23 | assert.equal( item0.sortData.b, 3.14, 'parseFloat parser' ); 24 | assert.equal( item0.sortData.i, 42, 'parseInt parser' ); 25 | assert.equal( item0.sortData.bbroke, '3.14', 'default nonparser' ); 26 | 27 | // ----- ----- // 28 | 29 | var docElem = document.documentElement; 30 | var textSetter = docElem.textContent !== undefined ? 'textContent' : 'innerText'; 31 | 32 | function setText( elem, value ) { 33 | elem[ textSetter ] = value; 34 | } 35 | 36 | var elem0 = iso.items[0].element; 37 | var elem1 = iso.items[1].element; 38 | 39 | elem0.setAttribute( 'data-ninja-turtle', 'donatello' ); 40 | setText( elem0.querySelector('span.fruit'), 'mango' ); 41 | setText( elem0.querySelector('b'), '7.24' ); 42 | setText( elem0.querySelector('i'), 'foo' ); 43 | 44 | iso.updateSortData( elem0 ); 45 | 46 | var message = ', after updateSortData on single item'; 47 | assert.equal( item0.sortData.ninjaTurtle, 'donatello', 48 | '[data-attr] shorthand' + message ); 49 | assert.equal( item0.sortData.fruit, 'mango', 50 | 'query selector shorthand' + message ); 51 | assert.equal( item0.sortData.b, 7.24, 'parseFloat parser' + message ); 52 | assert.ok( isNaN( item0.sortData.i ), 'parseInt parser' + message ); 53 | assert.equal( item0.sortData.bbroke, '7.24', 'default nonparser' + message ); 54 | 55 | // ----- update all items ----- // 56 | 57 | elem0.setAttribute( 'data-ninja-turtle', 'leonardo' ); 58 | setText( elem0.querySelector('span.fruit'), 'passion fruit' ); 59 | 60 | elem1.setAttribute( 'data-ninja-turtle', 'michelangelo' ); 61 | setText( elem1.querySelector('span.fruit'), 'starfruit' ); 62 | 63 | // update all 64 | iso.updateSortData(); 65 | 66 | message = ', after updateSortData on all items'; 67 | assert.equal( item0.sortData.ninjaTurtle, 'leonardo', 68 | '[data-attr] shorthand' + message ); 69 | assert.equal( item0.sortData.fruit, 'passion fruit', 70 | 'query selector shorthand' + message ); 71 | assert.equal( item1.sortData.ninjaTurtle, 'michelangelo', 72 | '[data-attr] shorthand' + message ); 73 | assert.equal( item1.sortData.fruit, 'starfruit', 74 | 'query selector shorthand' + message ); 75 | 76 | // ----- no items ----- // 77 | 78 | iso.options.itemSelector = 'none'; 79 | iso.reloadItems(); 80 | 81 | iso.updateSortData(); 82 | assert.ok( true, 'updateSortData on empty container is ok' ); 83 | 84 | iso.updateSortData( document.createElement('div') ); 85 | assert.ok( true, 'updateSortData with non-item is ok, with no child items' ); 86 | 87 | iso.updateSortData( false ); 88 | assert.ok( true, 'updateSortData with falsy is ok, with no child items' ); 89 | 90 | iso.updateSortData([]); 91 | assert.ok( true, 'updateSortData with empty array is ok, with no child items' ); 92 | 93 | iso.updateSortData( jQuery() ); 94 | assert.ok( true, 'updateSortData with empty jQuery object is ok, with no child items' ); 95 | 96 | // ----- bad getSortData ----- // 97 | 98 | delete iso.options.itemSelector; 99 | iso.options.getSortData.badQuery = 'bad-query'; 100 | iso.options.getSortData.badAttr = '[bad-attr]'; 101 | iso._getSorters(); 102 | iso.reloadItems(); 103 | 104 | item0 = iso.items[0]; 105 | 106 | assert.equal( item0.sortData.badQuery, null, 'bad query returns null' ); 107 | assert.equal( item0.sortData.badAttr, null, 'bad attr returns null' ); 108 | 109 | } ); 110 | -------------------------------------------------------------------------------- /js/layout-mode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Isotope LayoutMode 3 | */ 4 | 5 | ( function( window, factory ) { 6 | // universal module definition 7 | if ( typeof define == 'function' && define.amd ) { 8 | // AMD 9 | define( [ 10 | 'get-size/get-size', 11 | 'outlayer/outlayer', 12 | ], 13 | factory ); 14 | } else if ( typeof module == 'object' && module.exports ) { 15 | // CommonJS 16 | module.exports = factory( 17 | require('get-size'), 18 | require('outlayer') 19 | ); 20 | } else { 21 | // browser global 22 | window.Isotope = window.Isotope || {}; 23 | window.Isotope.LayoutMode = factory( 24 | window.getSize, 25 | window.Outlayer 26 | ); 27 | } 28 | 29 | }( window, function factory( getSize, Outlayer ) { 30 | 'use strict'; 31 | 32 | // layout mode class 33 | function LayoutMode( isotope ) { 34 | this.isotope = isotope; 35 | // link properties 36 | if ( isotope ) { 37 | this.options = isotope.options[ this.namespace ]; 38 | this.element = isotope.element; 39 | this.items = isotope.filteredItems; 40 | this.size = isotope.size; 41 | } 42 | } 43 | 44 | var proto = LayoutMode.prototype; 45 | 46 | /** 47 | * some methods should just defer to default Outlayer method 48 | * and reference the Isotope instance as `this` 49 | **/ 50 | var facadeMethods = [ 51 | '_resetLayout', 52 | '_getItemLayoutPosition', 53 | '_manageStamp', 54 | '_getContainerSize', 55 | '_getElementOffset', 56 | 'needsResizeLayout', 57 | '_getOption', 58 | ]; 59 | 60 | facadeMethods.forEach( function( methodName ) { 61 | proto[ methodName ] = function() { 62 | return Outlayer.prototype[ methodName ].apply( this.isotope, arguments ); 63 | }; 64 | } ); 65 | 66 | // ----- ----- // 67 | 68 | // for horizontal layout modes, check vertical size 69 | proto.needsVerticalResizeLayout = function() { 70 | // don't trigger if size did not change 71 | var size = getSize( this.isotope.element ); 72 | // check that this.size and size are there 73 | // IE8 triggers resize on body size change, so they might not be 74 | var hasSizes = this.isotope.size && size; 75 | return hasSizes && size.innerHeight != this.isotope.size.innerHeight; 76 | }; 77 | 78 | // ----- measurements ----- // 79 | 80 | proto._getMeasurement = function() { 81 | this.isotope._getMeasurement.apply( this, arguments ); 82 | }; 83 | 84 | proto.getColumnWidth = function() { 85 | this.getSegmentSize( 'column', 'Width' ); 86 | }; 87 | 88 | proto.getRowHeight = function() { 89 | this.getSegmentSize( 'row', 'Height' ); 90 | }; 91 | 92 | /** 93 | * get columnWidth or rowHeight 94 | * @param {String} segment - 'column' or 'row' 95 | * @param {String} size - 'Width' or 'Height' 96 | */ 97 | proto.getSegmentSize = function( segment, size ) { 98 | var segmentName = segment + size; 99 | var outerSize = 'outer' + size; 100 | // columnWidth / outerWidth // rowHeight / outerHeight 101 | this._getMeasurement( segmentName, outerSize ); 102 | // got rowHeight or columnWidth, we can chill 103 | if ( this[ segmentName ] ) { 104 | return; 105 | } 106 | // fall back to item of first element 107 | var firstItemSize = this.getFirstItemSize(); 108 | this[ segmentName ] = firstItemSize && firstItemSize[ outerSize ] || 109 | // or size of container 110 | this.isotope.size[ 'inner' + size ]; 111 | }; 112 | 113 | proto.getFirstItemSize = function() { 114 | var firstItem = this.isotope.filteredItems[0]; 115 | return firstItem && firstItem.element && getSize( firstItem.element ); 116 | }; 117 | 118 | // ----- methods that should reference isotope ----- // 119 | 120 | proto.layout = function() { 121 | this.isotope.layout.apply( this.isotope, arguments ); 122 | }; 123 | 124 | proto.getSize = function() { 125 | this.isotope.getSize(); 126 | this.size = this.isotope.size; 127 | }; 128 | 129 | // -------------------------- create -------------------------- // 130 | 131 | LayoutMode.modes = {}; 132 | 133 | LayoutMode.create = function( namespace, options ) { 134 | 135 | function Mode() { 136 | LayoutMode.apply( this, arguments ); 137 | } 138 | 139 | Mode.prototype = Object.create( proto ); 140 | Mode.prototype.constructor = Mode; 141 | 142 | // default options 143 | if ( options ) { 144 | Mode.options = options; 145 | } 146 | 147 | Mode.prototype.namespace = namespace; 148 | // register in Isotope 149 | LayoutMode.modes[ namespace ] = Mode; 150 | 151 | return Mode; 152 | }; 153 | 154 | return LayoutMode; 155 | 156 | } ) ); 157 | -------------------------------------------------------------------------------- /sandbox/browserify/browserify.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Browserify 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 |

Browserify

21 | 22 |
23 |

Filter

24 |
25 | 26 | 27 | 28 | 29 |
30 |

Sort

31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 | 41 |
42 | 43 |
44 |

80

45 |

Hg

46 |

Mercury

47 |

200.59

48 |
49 | 50 |
51 |

52

52 |

Te

53 |

Tellurium

54 |

127.6

55 |
56 | 57 |
58 |

83

59 |

Bi

60 |

Bismuth

61 |

208.9804

62 |
63 | 64 |
65 |

48

66 |

Cd

67 |

Cadmium

68 |

112.411

69 |
70 | 71 |
72 |

20

73 |

Ca

74 |

Calcium

75 |

40.078

76 |
77 | 78 |
79 |

75

80 |

Re

81 |

Rhenium

82 |

186.207

83 |
84 | 85 |
86 |

81

87 |

Tl

88 |

Thallium

89 |

204.3833

90 |
91 | 92 |
93 |

51

94 |

Sb

95 |

Antimony

96 |

121.76

97 |
98 | 99 |
100 |

27

101 |

Co

102 |

Cobalt

103 |

58.933195

104 |
105 | 106 |
107 |

71

108 |

Lu

109 |

Lutetium

110 |

174.9668

111 |
112 | 113 |
114 |

18

115 |

Ar

116 |

Argon

117 |

39.948

118 |
119 | 120 |
121 |

37

122 |

Rb

123 |

Rubidium

124 |

85.4678

125 |
126 | 127 |
128 |

7

129 |

N

130 |

Nitrogen

131 |

14.0067

132 |
133 | 134 |
135 |

93

136 |

Np

137 |

Neptunium

138 |

(237)

139 |
140 | 141 |
142 |

89

143 |

Ac

144 |

Actinium

145 |

(227)

146 |
147 |
148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Isotope tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |

Isotope tests

44 | 45 |
46 | 47 |

Sorting

48 | 49 |
50 |
B4
51 |
B2
52 |
A4
53 |
A1
54 |
B1
55 |
A3
56 |
A2
57 |
58 | 59 |
60 |
B2Y
61 |
B1Y
62 |
B2X
63 |
B1X
64 |
A2Y
65 |
A1Y
66 |
A2X
67 |
A1X
68 |
69 | 70 |

getSortData

71 | 72 |
73 |
74 | watermelon 75 | 3.14 76 | 42 77 |
78 |
79 | papaya 80 | 2.13 81 | 1001 82 |
83 |
84 | 85 |

Filtering

86 | 87 |
88 |
5
89 |
3
90 |
2
91 |
9
92 |
7
93 |
1
94 |
8
95 |
96 | 97 |

layoutComplete

98 | 99 |
100 |
a1 b1
101 |
a2 b1
102 |
a3 b1
103 |
a1 b2
104 |
a2 b2
105 |
a3 b2
106 |
a1 b3
107 |
a2 b3
108 |
a3 b3
109 |
110 | 111 |

LayoutMode.getSegmentSize

112 | 113 |
114 |
115 |
116 |
117 | 118 |

Masonry

119 | 120 |
121 |
122 |
a
123 |
b
124 |
c
125 |
126 | 127 |

Masonry stamp

128 | 129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | 137 |

fitRows

138 | 139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /sandbox/sorting.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sorting 7 | 8 | 9 | 10 | 11 | 12 | 13 |

sorting

14 | 15 |
16 |

Sort

17 | 25 |
26 | 27 |
28 | 29 |
30 |

80

31 |

Hg

32 |

Mercury

33 |

200.59

34 |
35 | 36 |
37 |

52

38 |

Te

39 |

Tellurium

40 |

127.6

41 |
42 | 43 |
44 |

83

45 |

Bi

46 |

Bismuth

47 |

208.9804

48 |
49 | 50 |
51 |

48

52 |

Cd

53 |

Cadmium

54 |

112.411

55 |
56 | 57 |
58 |

20

59 |

Ca

60 |

Calcium

61 |

40.078

62 |
63 | 64 |
65 |

75

66 |

Re

67 |

Rhenium

68 |

186.207

69 |
70 | 71 |
72 |

81

73 |

Tl

74 |

Thallium

75 |

204.3833

76 |
77 | 78 |
79 |

51

80 |

Sb

81 |

Antimony

82 |

121.76

83 |
84 | 85 |
86 |

27

87 |

Co

88 |

Cobalt

89 |

58.933195

90 |
91 | 92 |
93 |

71

94 |

Lu

95 |

Lutetium

96 |

174.9668

97 |
98 | 99 |
100 |

18

101 |

Ar

102 |

Argon

103 |

39.948

104 |
105 | 106 |
107 |

37

108 |

Rb

109 |

Rubidium

110 |

85.4678

111 |
112 | 113 |
114 |

7

115 |

N

116 |

Nitrogen

117 |

14.0067

118 |
119 | 120 |
121 |

93

122 |

Np

123 |

Neptunium

124 |

(237)

125 |
126 | 127 |
128 |

89

129 |

Ac

130 |

Actinium

131 |

(227)

132 |
133 |
134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /sandbox/masonry.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | masonry 7 | 8 | 9 | 21 | 22 | 23 | 24 | 25 |

sorting

26 | 27 |
28 |

Sort

29 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 |

80

44 |

Hg

45 |

Mercury

46 |

200.59

47 |
48 | 49 |
50 |

52

51 |

Te

52 |

Tellurium

53 |

127.6

54 |
55 | 56 |
57 |

83

58 |

Bi

59 |

Bismuth

60 |

208.9804

61 |
62 | 63 |
64 |

48

65 |

Cd

66 |

Cadmium

67 |

112.411

68 |
69 | 70 |
71 |

20

72 |

Ca

73 |

Calcium

74 |

40.078

75 |
76 | 77 |
78 |

75

79 |

Re

80 |

Rhenium

81 |

186.207

82 |
83 | 84 |
85 |

81

86 |

Tl

87 |

Thallium

88 |

204.3833

89 |
90 | 91 |
92 |

51

93 |

Sb

94 |

Antimony

95 |

121.76

96 |
97 | 98 |
99 |

27

100 |

Co

101 |

Cobalt

102 |

58.933195

103 |
104 | 105 |
106 |

71

107 |

Lu

108 |

Lutetium

109 |

174.9668

110 |
111 | 112 |
113 |

18

114 |

Ar

115 |

Argon

116 |

39.948

117 |
118 | 119 |
120 |

37

121 |

Rb

122 |

Rubidium

123 |

85.4678

124 |
125 | 126 |
127 |

7

128 |

N

129 |

Nitrogen

130 |

14.0067

131 |
132 | 133 |
134 |

93

135 |

Np

136 |

Neptunium

137 |

(237)

138 |
139 | 140 |
141 |

89

142 |

Ac

143 |

Actinium

144 |

(227)

145 |
146 |
147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /sandbox/horizontal-layout-modes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | horizontal layout modes 7 | 8 | 9 | 19 | 20 | 21 | 22 |

horizontal layout modes

23 | 24 |
25 |

Filter

26 |
27 | 28 | 29 | 30 |
31 |

Sort

32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | 42 |
43 | 44 |
45 |

80

46 |

Hg

47 |

Mercury

48 |

200.59

49 |
50 | 51 |
52 |

52

53 |

Te

54 |

Tellurium

55 |

127.6

56 |
57 | 58 |
59 |

83

60 |

Bi

61 |

Bismuth

62 |

208.9804

63 |
64 | 65 |
66 |

48

67 |

Cd

68 |

Cadmium

69 |

112.411

70 |
71 | 72 |
73 |

20

74 |

Ca

75 |

Calcium

76 |

40.078

77 |
78 | 79 |
80 |

75

81 |

Re

82 |

Rhenium

83 |

186.207

84 |
85 | 86 |
87 |

81

88 |

Tl

89 |

Thallium

90 |

204.3833

91 |
92 | 93 |
94 |

51

95 |

Sb

96 |

Antimony

97 |

121.76

98 |
99 | 100 |
101 |

27

102 |

Co

103 |

Cobalt

104 |

58.933195

105 |
106 | 107 |
108 |

71

109 |

Lu

110 |

Lutetium

111 |

174.9668

112 |
113 | 114 |
115 |

18

116 |

Ar

117 |

Argon

118 |

39.948

119 |
120 | 121 |
122 |

37

123 |

Rb

124 |

Rubidium

125 |

85.4678

126 |
127 | 128 |
129 |

7

130 |

N

131 |

Nitrogen

132 |

14.0067

133 |
134 | 135 |
136 |

93

137 |

Np

138 |

Neptunium

139 |

(237)

140 |
141 | 142 |
143 |

89

144 |

Ac

145 |

Actinium

146 |

(227)

147 |
148 |
149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /sandbox/filter-sort.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | filter sort 7 | 8 | 9 | 12 | 13 | 14 | 15 |

filter sort

16 | 17 |
18 |

Filter

19 |
20 | 21 | 22 | 23 | 24 |
25 |

Sort

26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 |
37 | 38 |
39 |

80

40 |

Hg

41 |

Mercury

42 |

200.59

43 |
44 | 45 |
46 |

52

47 |

Te

48 |

Tellurium

49 |

127.6

50 |
51 | 52 |
53 |

83

54 |

Bi

55 |

Bismuth

56 |

208.9804

57 |
58 | 59 |
60 |

48

61 |

Cd

62 |

Cadmium

63 |

112.411

64 |
65 | 66 |
67 |

20

68 |

Ca

69 |

Calcium

70 |

40.078

71 |
72 | 73 |
74 |

75

75 |

Re

76 |

Rhenium

77 |

186.207

78 |
79 | 80 |
81 |

81

82 |

Tl

83 |

Thallium

84 |

204.3833

85 |
86 | 87 |
88 |

51

89 |

Sb

90 |

Antimony

91 |

121.76

92 |
93 | 94 |
95 |

27

96 |

Co

97 |

Cobalt

98 |

58.933195

99 |
100 | 101 |
102 |

71

103 |

Lu

104 |

Lutetium

105 |

174.9668

106 |
107 | 108 |
109 |

18

110 |

Ar

111 |

Argon

112 |

39.948

113 |
114 | 115 |
116 |

37

117 |

Rb

118 |

Rubidium

119 |

85.4678

120 |
121 | 122 |
123 |

7

124 |

N

125 |

Nitrogen

126 |

14.0067

127 |
128 | 129 |
130 |

93

131 |

Np

132 |

Neptunium

133 |

(237)

134 |
135 | 136 |
137 |

89

138 |

Ac

139 |

Actinium

140 |

(227)

141 |
142 |
143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /sandbox/cells-by-row.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | cellsByRow 7 | 8 | 9 | 21 | 22 | 23 | 24 | 25 |

cellsByRow

26 | 27 |
28 |

Sort

29 | 37 |
38 | 39 |
40 | 41 |
42 |

80

43 |

Hg

44 |

Mercury

45 |

200.59

46 |
47 | 48 |
49 |

52

50 |

Te

51 |

Tellurium

52 |

127.6

53 |
54 | 55 |
56 |

83

57 |

Bi

58 |

Bismuth

59 |

208.9804

60 |
61 | 62 |
63 |

48

64 |

Cd

65 |

Cadmium

66 |

112.411

67 |
68 | 69 |
70 |

20

71 |

Ca

72 |

Calcium

73 |

40.078

74 |
75 | 76 |
77 |

75

78 |

Re

79 |

Rhenium

80 |

186.207

81 |
82 | 83 |
84 |

81

85 |

Tl

86 |

Thallium

87 |

204.3833

88 |
89 | 90 |
91 |

51

92 |

Sb

93 |

Antimony

94 |

121.76

95 |
96 | 97 |
98 |

27

99 |

Co

100 |

Cobalt

101 |

58.933195

102 |
103 | 104 |
105 |

71

106 |

Lu

107 |

Lutetium

108 |

174.9668

109 |
110 | 111 |
112 |

18

113 |

Ar

114 |

Argon

115 |

39.948

116 |
117 | 118 |
119 |

37

120 |

Rb

121 |

Rubidium

122 |

85.4678

123 |
124 | 125 |
126 |

7

127 |

N

128 |

Nitrogen

129 |

14.0067

130 |
131 | 132 |
133 |

93

134 |

Np

135 |

Neptunium

136 |

(237)

137 |
138 | 139 |
140 |

89

141 |

Ac

142 |

Actinium

143 |

(227)

144 |
145 |
146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /sandbox/jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | jquery 8 | 9 | 10 | 13 | 14 | 15 | 16 |

jquery

17 | 18 |
19 |

Filter

20 |
21 | 22 | 23 | 24 | 25 |
26 |

Sort

27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 |
38 | 39 |
40 |

80

41 |

Hg

42 |

Mercury

43 |

200.59

44 |
45 | 46 |
47 |

52

48 |

Te

49 |

Tellurium

50 |

127.6

51 |
52 | 53 |
54 |

83

55 |

Bi

56 |

Bismuth

57 |

208.9804

58 |
59 | 60 |
61 |

48

62 |

Cd

63 |

Cadmium

64 |

112.411

65 |
66 | 67 |
68 |

20

69 |

Ca

70 |

Calcium

71 |

40.078

72 |
73 | 74 |
75 |

75

76 |

Re

77 |

Rhenium

78 |

186.207

79 |
80 | 81 |
82 |

81

83 |

Tl

84 |

Thallium

85 |

204.3833

86 |
87 | 88 |
89 |

51

90 |

Sb

91 |

Antimony

92 |

121.76

93 |
94 | 95 |
96 |

27

97 |

Co

98 |

Cobalt

99 |

58.933195

100 |
101 | 102 |
103 |

71

104 |

Lu

105 |

Lutetium

106 |

174.9668

107 |
108 | 109 |
110 |

18

111 |

Ar

112 |

Argon

113 |

39.948

114 |
115 | 116 |
117 |

37

118 |

Rb

119 |

Rubidium

120 |

85.4678

121 |
122 | 123 |
124 |

7

125 |

N

126 |

Nitrogen

127 |

14.0067

128 |
129 | 130 |
131 |

93

132 |

Np

133 |

Neptunium

134 |

(237)

135 |
136 | 137 |
138 |

89

139 |

Ac

140 |

Actinium

141 |

(227)

142 |
143 |
144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /sandbox/combination-filters-inclusive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | combination filters inclusive 7 | 8 | 9 | 131 | 132 | 133 | 134 | 135 |

combination filters

136 | 137 |
138 | 139 |
140 |

Color

141 |
142 | 143 | 144 | 145 | 146 |
147 |
148 | 149 |
150 |

Size

151 |
152 | 153 | 154 | 155 | 156 | 157 |
158 |
159 | 160 |
161 |

Shape

162 |
163 | 164 | 165 | 166 |
167 |
168 | 169 |
170 | 171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /sandbox/combination-filters.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | combination filters 7 | 8 | 9 | 131 | 132 | 133 | 134 | 135 |

combination filters

136 | 137 |
138 | 139 |
140 |

Color

141 |
142 | 143 | 144 | 145 | 146 |
147 |
148 | 149 |
150 |

Size

151 |
152 | 153 | 154 | 155 | 156 | 157 |
158 |
159 | 160 |
161 |

Shape

162 |
163 | 164 | 165 | 166 |
167 |
168 | 169 |
170 | 171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /sandbox/masonry-horizontal.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | horizontal layout modes 7 | 8 | 9 | 45 | 46 | 47 | 48 |

horizontal layout modes

49 | 50 |
51 |

Filter

52 |
53 | 54 | 55 | 56 |
57 |

Sort

58 |
59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 |
67 | 68 |
69 |
70 |
71 | 72 | 73 |
74 |

80

75 |

Hg

76 |

Mercury

77 |

200.59

78 |
79 | 80 |
81 |

52

82 |

Te

83 |

Tellurium

84 |

127.6

85 |
86 | 87 |
88 |

83

89 |

Bi

90 |

Bismuth

91 |

208.9804

92 |
93 | 94 |
95 |

48

96 |

Cd

97 |

Cadmium

98 |

112.411

99 |
100 | 101 |
102 |

20

103 |

Ca

104 |

Calcium

105 |

40.078

106 |
107 | 108 |
109 |

75

110 |

Re

111 |

Rhenium

112 |

186.207

113 |
114 | 115 |
116 |

81

117 |

Tl

118 |

Thallium

119 |

204.3833

120 |
121 | 122 |
123 |

51

124 |

Sb

125 |

Antimony

126 |

121.76

127 |
128 | 129 |
130 |

27

131 |

Co

132 |

Cobalt

133 |

58.933195

134 |
135 | 136 |
137 |

71

138 |

Lu

139 |

Lutetium

140 |

174.9668

141 |
142 | 143 |
144 |

18

145 |

Ar

146 |

Argon

147 |

39.948

148 |
149 | 150 |
151 |

37

152 |

Rb

153 |

Rubidium

154 |

85.4678

155 |
156 | 157 |
158 |

7

159 |

N

160 |

Nitrogen

161 |

14.0067

162 |
163 | 164 |
165 |

93

166 |

Np

167 |

Neptunium

168 |

(237)

169 |
170 | 171 |
172 |

89

173 |

Ac

174 |

Actinium

175 |

(227)

176 |
177 |
178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /sandbox/v3-release-gif2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | v3 release gif 8 | 9 | 142 | 143 | 144 | 145 | 146 |

v3 release gif

147 | 148 |
149 | 150 | 151 | 152 | 153 | 154 |
155 | 156 |
157 |
158 | A 159 | 8 160 |
161 |
162 | B 163 | 12 164 |
165 |
166 | C 167 | 15 168 |
169 |
170 | D 171 | 10 172 |
173 |
174 | E 175 | 9 176 |
177 |
178 | F 179 | 3 180 |
181 |
182 | G 183 | 4 184 |
185 |
186 | H 187 | 12 188 |
189 |
190 | I 191 | 16 192 |
193 |
194 | J 195 | 1 196 |
197 |
198 | K 199 | 11 200 |
201 |
202 | L 203 | 7 204 |
205 |
206 | M 207 | 19 208 |
209 |
210 | N 211 | 13 212 |
213 |
214 | O 215 | 17 216 |
217 |
218 | P 219 | 14 220 |
221 |
222 | Q 223 | 20 224 |
225 |
226 | R 227 | 6 228 |
229 |
230 | S 231 | 2 232 |
233 |
234 | T 235 | 5 236 |
237 |
238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /js/isotope.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Isotope v3.0.6 3 | * 4 | * Licensed GPLv3 for open source use 5 | * or Isotope Commercial License for commercial use 6 | * 7 | * https://isotope.metafizzy.co 8 | * Copyright 2010-2020 Metafizzy 9 | */ 10 | 11 | /* eslint-disable max-params */ 12 | 13 | ( function( window, factory ) { 14 | // universal module definition 15 | if ( typeof define == 'function' && define.amd ) { 16 | // AMD 17 | define( [ 18 | 'outlayer/outlayer', 19 | 'get-size/get-size', 20 | 'desandro-matches-selector/matches-selector', 21 | 'fizzy-ui-utils/utils', 22 | './item', 23 | './layout-mode', 24 | // include default layout modes 25 | './layout-modes/masonry', 26 | './layout-modes/fit-rows', 27 | './layout-modes/vertical', 28 | ], 29 | function( Outlayer, getSize, matchesSelector, utils, Item, LayoutMode ) { 30 | return factory( window, Outlayer, getSize, matchesSelector, utils, 31 | Item, LayoutMode ); 32 | } ); 33 | } else if ( typeof module == 'object' && module.exports ) { 34 | // CommonJS 35 | module.exports = factory( 36 | window, 37 | require('outlayer'), 38 | require('get-size'), 39 | require('desandro-matches-selector'), 40 | require('fizzy-ui-utils'), 41 | require('./item'), 42 | require('./layout-mode'), 43 | // include default layout modes 44 | require('./layout-modes/masonry'), 45 | require('./layout-modes/fit-rows'), 46 | require('./layout-modes/vertical') 47 | ); 48 | } else { 49 | // browser global 50 | window.Isotope = factory( 51 | window, 52 | window.Outlayer, 53 | window.getSize, 54 | window.matchesSelector, 55 | window.fizzyUIUtils, 56 | window.Isotope.Item, 57 | window.Isotope.LayoutMode 58 | ); 59 | } 60 | 61 | }( window, function factory( window, Outlayer, getSize, matchesSelector, utils, 62 | Item, LayoutMode ) { 63 | 64 | 'use strict'; 65 | 66 | // -------------------------- vars -------------------------- // 67 | 68 | var jQuery = window.jQuery; 69 | 70 | // -------------------------- helpers -------------------------- // 71 | 72 | var trim = String.prototype.trim ? 73 | function( str ) { 74 | return str.trim(); 75 | } : 76 | function( str ) { 77 | return str.replace( /^\s+|\s+$/g, '' ); 78 | }; 79 | 80 | // -------------------------- isotopeDefinition -------------------------- // 81 | 82 | // create an Outlayer layout class 83 | var Isotope = Outlayer.create( 'isotope', { 84 | layoutMode: 'masonry', 85 | isJQueryFiltering: true, 86 | sortAscending: true, 87 | } ); 88 | 89 | Isotope.Item = Item; 90 | Isotope.LayoutMode = LayoutMode; 91 | 92 | var proto = Isotope.prototype; 93 | 94 | proto._create = function() { 95 | this.itemGUID = 0; 96 | // functions that sort items 97 | this._sorters = {}; 98 | this._getSorters(); 99 | // call super 100 | Outlayer.prototype._create.call( this ); 101 | 102 | // create layout modes 103 | this.modes = {}; 104 | // start filteredItems with all items 105 | this.filteredItems = this.items; 106 | // keep of track of sortBys 107 | this.sortHistory = [ 'original-order' ]; 108 | // create from registered layout modes 109 | for ( var name in LayoutMode.modes ) { 110 | this._initLayoutMode( name ); 111 | } 112 | }; 113 | 114 | proto.reloadItems = function() { 115 | // reset item ID counter 116 | this.itemGUID = 0; 117 | // call super 118 | Outlayer.prototype.reloadItems.call( this ); 119 | }; 120 | 121 | proto._itemize = function() { 122 | var items = Outlayer.prototype._itemize.apply( this, arguments ); 123 | // assign ID for original-order 124 | for ( var i = 0; i < items.length; i++ ) { 125 | var item = items[i]; 126 | item.id = this.itemGUID++; 127 | } 128 | this._updateItemsSortData( items ); 129 | return items; 130 | }; 131 | 132 | // -------------------------- layout -------------------------- // 133 | 134 | proto._initLayoutMode = function( name ) { 135 | var Mode = LayoutMode.modes[ name ]; 136 | // set mode options 137 | // HACK extend initial options, back-fill in default options 138 | var initialOpts = this.options[ name ] || {}; 139 | this.options[ name ] = Mode.options ? 140 | utils.extend( Mode.options, initialOpts ) : initialOpts; 141 | // init layout mode instance 142 | this.modes[ name ] = new Mode( this ); 143 | }; 144 | 145 | proto.layout = function() { 146 | // if first time doing layout, do all magic 147 | if ( !this._isLayoutInited && this._getOption('initLayout') ) { 148 | this.arrange(); 149 | return; 150 | } 151 | this._layout(); 152 | }; 153 | 154 | // private method to be used in layout() & magic() 155 | proto._layout = function() { 156 | // don't animate first layout 157 | var isInstant = this._getIsInstant(); 158 | // layout flow 159 | this._resetLayout(); 160 | this._manageStamps(); 161 | this.layoutItems( this.filteredItems, isInstant ); 162 | 163 | // flag for initalized 164 | this._isLayoutInited = true; 165 | }; 166 | 167 | // filter + sort + layout 168 | proto.arrange = function( opts ) { 169 | // set any options pass 170 | this.option( opts ); 171 | this._getIsInstant(); 172 | // filter, sort, and layout 173 | 174 | // filter 175 | var filtered = this._filter( this.items ); 176 | this.filteredItems = filtered.matches; 177 | 178 | this._bindArrangeComplete(); 179 | 180 | if ( this._isInstant ) { 181 | this._noTransition( this._hideReveal, [ filtered ] ); 182 | } else { 183 | this._hideReveal( filtered ); 184 | } 185 | 186 | this._sort(); 187 | this._layout(); 188 | }; 189 | // alias to _init for main plugin method 190 | proto._init = proto.arrange; 191 | 192 | proto._hideReveal = function( filtered ) { 193 | this.reveal( filtered.needReveal ); 194 | this.hide( filtered.needHide ); 195 | }; 196 | 197 | // HACK 198 | // Don't animate/transition first layout 199 | // Or don't animate/transition other layouts 200 | proto._getIsInstant = function() { 201 | var isLayoutInstant = this._getOption('layoutInstant'); 202 | var isInstant = isLayoutInstant !== undefined ? isLayoutInstant : 203 | !this._isLayoutInited; 204 | this._isInstant = isInstant; 205 | return isInstant; 206 | }; 207 | 208 | // listen for layoutComplete, hideComplete and revealComplete 209 | // to trigger arrangeComplete 210 | proto._bindArrangeComplete = function() { 211 | // listen for 3 events to trigger arrangeComplete 212 | var isLayoutComplete, isHideComplete, isRevealComplete; 213 | var _this = this; 214 | function arrangeParallelCallback() { 215 | if ( isLayoutComplete && isHideComplete && isRevealComplete ) { 216 | _this.dispatchEvent( 'arrangeComplete', null, [ _this.filteredItems ] ); 217 | } 218 | } 219 | this.once( 'layoutComplete', function() { 220 | isLayoutComplete = true; 221 | arrangeParallelCallback(); 222 | } ); 223 | this.once( 'hideComplete', function() { 224 | isHideComplete = true; 225 | arrangeParallelCallback(); 226 | } ); 227 | this.once( 'revealComplete', function() { 228 | isRevealComplete = true; 229 | arrangeParallelCallback(); 230 | } ); 231 | }; 232 | 233 | // -------------------------- filter -------------------------- // 234 | 235 | proto._filter = function( items ) { 236 | var filter = this.options.filter; 237 | filter = filter || '*'; 238 | var matches = []; 239 | var hiddenMatched = []; 240 | var visibleUnmatched = []; 241 | 242 | var test = this._getFilterTest( filter ); 243 | 244 | // test each item 245 | for ( var i = 0; i < items.length; i++ ) { 246 | var item = items[i]; 247 | if ( item.isIgnored ) { 248 | continue; 249 | } 250 | // add item to either matched or unmatched group 251 | var isMatched = test( item ); 252 | // item.isFilterMatched = isMatched; 253 | // add to matches if its a match 254 | if ( isMatched ) { 255 | matches.push( item ); 256 | } 257 | // add to additional group if item needs to be hidden or revealed 258 | if ( isMatched && item.isHidden ) { 259 | hiddenMatched.push( item ); 260 | } else if ( !isMatched && !item.isHidden ) { 261 | visibleUnmatched.push( item ); 262 | } 263 | } 264 | 265 | // return collections of items to be manipulated 266 | return { 267 | matches: matches, 268 | needReveal: hiddenMatched, 269 | needHide: visibleUnmatched, 270 | }; 271 | }; 272 | 273 | // get a jQuery, function, or a matchesSelector test given the filter 274 | proto._getFilterTest = function( filter ) { 275 | if ( jQuery && this.options.isJQueryFiltering ) { 276 | // use jQuery 277 | return function( item ) { 278 | return jQuery( item.element ).is( filter ); 279 | }; 280 | } 281 | if ( typeof filter == 'function' ) { 282 | // use filter as function 283 | return function( item ) { 284 | return filter( item.element ); 285 | }; 286 | } 287 | // default, use filter as selector string 288 | return function( item ) { 289 | return matchesSelector( item.element, filter ); 290 | }; 291 | }; 292 | 293 | // -------------------------- sorting -------------------------- // 294 | 295 | /** 296 | * @param {Array} elems 297 | */ 298 | proto.updateSortData = function( elems ) { 299 | // get items 300 | var items; 301 | if ( elems ) { 302 | elems = utils.makeArray( elems ); 303 | items = this.getItems( elems ); 304 | } else { 305 | // update all items if no elems provided 306 | items = this.items; 307 | } 308 | 309 | this._getSorters(); 310 | this._updateItemsSortData( items ); 311 | }; 312 | 313 | // ----- munge sorter ----- // 314 | 315 | // encapsulate this, as we just need mungeSorter 316 | // other functions in here are just for munging 317 | var mungeSorter = ( function() { 318 | // add a magic layer to sorters for convienent shorthands 319 | // `.foo-bar` will use the text of .foo-bar querySelector 320 | // `[foo-bar]` will use attribute 321 | // you can also add parser 322 | // `.foo-bar parseInt` will parse that as a number 323 | function mngSorter( sorter ) { 324 | // if not a string, return function or whatever it is 325 | if ( typeof sorter != 'string' ) { 326 | return sorter; 327 | } 328 | // parse the sorter string 329 | var args = trim( sorter ).split(' '); 330 | var query = args[0]; 331 | // check if query looks like [an-attribute] 332 | var attrMatch = query.match( /^\[(.+)\]$/ ); 333 | var attr = attrMatch && attrMatch[1]; 334 | var getValue = getValueGetter( attr, query ); 335 | // use second argument as a parser 336 | var parser = Isotope.sortDataParsers[ args[1] ]; 337 | // parse the value, if there was a parser 338 | sorter = parser ? function( elem ) { 339 | return elem && parser( getValue( elem ) ); 340 | } : 341 | // otherwise just return value 342 | function( elem ) { 343 | return elem && getValue( elem ); 344 | }; 345 | 346 | return sorter; 347 | } 348 | 349 | // get an attribute getter, or get text of the querySelector 350 | function getValueGetter( attr, query ) { 351 | // if query looks like [foo-bar], get attribute 352 | if ( attr ) { 353 | return function getAttribute( elem ) { 354 | return elem.getAttribute( attr ); 355 | }; 356 | } 357 | 358 | // otherwise, assume its a querySelector, and get its text 359 | return function getChildText( elem ) { 360 | var child = elem.querySelector( query ); 361 | return child && child.textContent; 362 | }; 363 | } 364 | 365 | return mngSorter; 366 | } )(); 367 | 368 | proto._getSorters = function() { 369 | var getSortData = this.options.getSortData; 370 | for ( var key in getSortData ) { 371 | var sorter = getSortData[ key ]; 372 | this._sorters[ key ] = mungeSorter( sorter ); 373 | } 374 | }; 375 | 376 | /** 377 | * @param {Array} items - of Isotope.Items 378 | */ 379 | proto._updateItemsSortData = function( items ) { 380 | // do not update if no items 381 | var len = items && items.length; 382 | if ( !len ) { 383 | return; 384 | } 385 | 386 | for ( var i = 0; i < len; i++ ) { 387 | var item = items[i]; 388 | item.updateSortData(); 389 | } 390 | }; 391 | 392 | // parsers used in getSortData shortcut strings 393 | Isotope.sortDataParsers = { 394 | parseInt: function( val ) { 395 | return parseInt( val, 10 ); 396 | }, 397 | parseFloat: function( val ) { 398 | return parseFloat( val ); 399 | }, 400 | }; 401 | 402 | // ----- sort method ----- // 403 | 404 | // sort filteredItem order 405 | proto._sort = function() { 406 | if ( !this.options.sortBy ) { 407 | return; 408 | } 409 | // keep track of sortBy History 410 | var sortBys = utils.makeArray( this.options.sortBy ); 411 | if ( !this._getIsSameSortBy( sortBys ) ) { 412 | // concat all sortBy and sortHistory, add to front, oldest goes in last 413 | this.sortHistory = sortBys.concat( this.sortHistory ); 414 | } 415 | // sort magic 416 | var itemSorter = getItemSorter( this.sortHistory, this.options.sortAscending ); 417 | this.filteredItems.sort( itemSorter ); 418 | }; 419 | 420 | // check if sortBys is same as start of sortHistory 421 | proto._getIsSameSortBy = function( sortBys ) { 422 | for ( var i = 0; i < sortBys.length; i++ ) { 423 | if ( sortBys[i] != this.sortHistory[i] ) { 424 | return false; 425 | } 426 | } 427 | return true; 428 | }; 429 | 430 | // returns a function used for sorting 431 | function getItemSorter( sortBys, sortAsc ) { 432 | return function sorter( itemA, itemB ) { 433 | // cycle through all sortKeys 434 | for ( var i = 0; i < sortBys.length; i++ ) { 435 | var sortBy = sortBys[i]; 436 | var a = itemA.sortData[ sortBy ]; 437 | var b = itemB.sortData[ sortBy ]; 438 | if ( a > b || a < b ) { 439 | // if sortAsc is an object, use the value given the sortBy key 440 | var isAscending = sortAsc[ sortBy ] !== undefined ? sortAsc[ sortBy ] : sortAsc; 441 | var direction = isAscending ? 1 : -1; 442 | return ( a > b ? 1 : -1 ) * direction; 443 | } 444 | } 445 | return 0; 446 | }; 447 | } 448 | 449 | // -------------------------- methods -------------------------- // 450 | 451 | // get layout mode 452 | proto._mode = function() { 453 | var layoutMode = this.options.layoutMode; 454 | var mode = this.modes[ layoutMode ]; 455 | if ( !mode ) { 456 | // TODO console.error 457 | throw new Error( 'No layout mode: ' + layoutMode ); 458 | } 459 | // HACK sync mode's options 460 | // any options set after init for layout mode need to be synced 461 | mode.options = this.options[ layoutMode ]; 462 | return mode; 463 | }; 464 | 465 | proto._resetLayout = function() { 466 | // trigger original reset layout 467 | Outlayer.prototype._resetLayout.call( this ); 468 | this._mode()._resetLayout(); 469 | }; 470 | 471 | proto._getItemLayoutPosition = function( item ) { 472 | return this._mode()._getItemLayoutPosition( item ); 473 | }; 474 | 475 | proto._manageStamp = function( stamp ) { 476 | this._mode()._manageStamp( stamp ); 477 | }; 478 | 479 | proto._getContainerSize = function() { 480 | return this._mode()._getContainerSize(); 481 | }; 482 | 483 | proto.needsResizeLayout = function() { 484 | return this._mode().needsResizeLayout(); 485 | }; 486 | 487 | // -------------------------- adding & removing -------------------------- // 488 | 489 | // HEADS UP overwrites default Outlayer appended 490 | proto.appended = function( elems ) { 491 | var items = this.addItems( elems ); 492 | if ( !items.length ) { 493 | return; 494 | } 495 | // filter, layout, reveal new items 496 | var filteredItems = this._filterRevealAdded( items ); 497 | // add to filteredItems 498 | this.filteredItems = this.filteredItems.concat( filteredItems ); 499 | }; 500 | 501 | // HEADS UP overwrites default Outlayer prepended 502 | proto.prepended = function( elems ) { 503 | var items = this._itemize( elems ); 504 | if ( !items.length ) { 505 | return; 506 | } 507 | // start new layout 508 | this._resetLayout(); 509 | this._manageStamps(); 510 | // filter, layout, reveal new items 511 | var filteredItems = this._filterRevealAdded( items ); 512 | // layout previous items 513 | this.layoutItems( this.filteredItems ); 514 | // add to items and filteredItems 515 | this.filteredItems = filteredItems.concat( this.filteredItems ); 516 | this.items = items.concat( this.items ); 517 | }; 518 | 519 | proto._filterRevealAdded = function( items ) { 520 | var filtered = this._filter( items ); 521 | this.hide( filtered.needHide ); 522 | // reveal all new items 523 | this.reveal( filtered.matches ); 524 | // layout new items, no transition 525 | this.layoutItems( filtered.matches, true ); 526 | return filtered.matches; 527 | }; 528 | 529 | /** 530 | * Filter, sort, and layout newly-appended item elements 531 | * @param {[Array, NodeList, Element]} elems 532 | */ 533 | proto.insert = function( elems ) { 534 | var items = this.addItems( elems ); 535 | if ( !items.length ) { 536 | return; 537 | } 538 | // append item elements 539 | var i, item; 540 | var len = items.length; 541 | for ( i = 0; i < len; i++ ) { 542 | item = items[i]; 543 | this.element.appendChild( item.element ); 544 | } 545 | // filter new stuff 546 | var filteredInsertItems = this._filter( items ).matches; 547 | // set flag 548 | for ( i = 0; i < len; i++ ) { 549 | items[i].isLayoutInstant = true; 550 | } 551 | this.arrange(); 552 | // reset flag 553 | for ( i = 0; i < len; i++ ) { 554 | delete items[i].isLayoutInstant; 555 | } 556 | this.reveal( filteredInsertItems ); 557 | }; 558 | 559 | var _remove = proto.remove; 560 | proto.remove = function( elems ) { 561 | elems = utils.makeArray( elems ); 562 | var removeItems = this.getItems( elems ); 563 | // do regular thing 564 | _remove.call( this, elems ); 565 | // bail if no items to remove 566 | var len = removeItems && removeItems.length; 567 | // remove elems from filteredItems 568 | if ( !len ) { 569 | return; 570 | } 571 | for ( var i = 0; i < len; i++ ) { 572 | var item = removeItems[i]; 573 | // remove item from collection 574 | utils.removeFrom( this.filteredItems, item ); 575 | } 576 | }; 577 | 578 | proto.shuffle = function() { 579 | // update random sortData 580 | for ( var i = 0; i < this.items.length; i++ ) { 581 | var item = this.items[i]; 582 | item.sortData.random = Math.random(); 583 | } 584 | this.options.sortBy = 'random'; 585 | this._sort(); 586 | this._layout(); 587 | }; 588 | 589 | /** 590 | * trigger fn without transition 591 | * kind of hacky to have this in the first place 592 | * @param {Function} fn 593 | * @param {Array} args 594 | * @returns {Object} returnValue 595 | * @private 596 | */ 597 | proto._noTransition = function( fn, args ) { 598 | // save transitionDuration before disabling 599 | var transitionDuration = this.options.transitionDuration; 600 | // disable transition 601 | this.options.transitionDuration = 0; 602 | // do it 603 | var returnValue = fn.apply( this, args ); 604 | // re-enable transition for reveal 605 | this.options.transitionDuration = transitionDuration; 606 | return returnValue; 607 | }; 608 | 609 | // ----- helper methods ----- // 610 | 611 | /** 612 | * getter method for getting filtered item elements 613 | * @returns {Array} elems - collection of item elements 614 | */ 615 | proto.getFilteredItemElements = function() { 616 | return this.filteredItems.map( function( item ) { 617 | return item.element; 618 | } ); 619 | }; 620 | 621 | // ----- ----- // 622 | 623 | return Isotope; 624 | 625 | } ) ); 626 | -------------------------------------------------------------------------------- /dist/isotope.pkgd.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Isotope PACKAGED v3.0.6 3 | * 4 | * Licensed GPLv3 for open source use 5 | * or Isotope Commercial License for commercial use 6 | * 7 | * https://isotope.metafizzy.co 8 | * Copyright 2010-2018 Metafizzy 9 | */ 10 | 11 | !function(t,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery)}(window,function(t,e){"use strict";function i(i,s,a){function u(t,e,o){var n,s="$()."+i+'("'+e+'")';return t.each(function(t,u){var h=a.data(u,i);if(!h)return void r(i+" not initialized. Cannot call methods, i.e. "+s);var d=h[e];if(!d||"_"==e.charAt(0))return void r(s+" is not a valid method");var l=d.apply(h,o);n=void 0===n?l:n}),void 0!==n?n:t}function h(t,e){t.each(function(t,o){var n=a.data(o,i);n?(n.option(e),n._init()):(n=new s(o,e),a.data(o,i,n))})}a=a||e||t.jQuery,a&&(s.prototype.option||(s.prototype.option=function(t){a.isPlainObject(t)&&(this.options=a.extend(!0,this.options,t))}),a.fn[i]=function(t){if("string"==typeof t){var e=n.call(arguments,1);return u(this,t,e)}return h(this,t),this},o(a))}function o(t){!t||t&&t.bridget||(t.bridget=i)}var n=Array.prototype.slice,s=t.console,r="undefined"==typeof s?function(){}:function(t){s.error(t)};return o(e||t.jQuery),i}),function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},o=i[t]=i[t]||[];return o.indexOf(e)==-1&&o.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},o=i[t]=i[t]||{};return o[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var o=i.indexOf(e);return o!=-1&&i.splice(o,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){i=i.slice(0),e=e||[];for(var o=this._onceEvents&&this._onceEvents[t],n=0;n