123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- /*!
- * Isotope v3.0.4
- *
- * Licensed GPLv3 for open source use
- * or Isotope Commercial License for commercial use
- *
- * http://isotope.metafizzy.co
- * Copyright 2017 Metafizzy
- */
- ( function( window, factory ) {
- // universal module definition
- /* jshint strict: false */ /*globals define, module, require */
- if ( typeof define == 'function' && define.amd ) {
- // AMD
- define( [
- 'outlayer/outlayer',
- 'get-size/get-size',
- 'desandro-matches-selector/matches-selector',
- 'fizzy-ui-utils/utils',
- './item',
- './layout-mode',
- // include default layout modes
- './layout-modes/masonry',
- './layout-modes/fit-rows',
- './layout-modes/vertical'
- ],
- function( Outlayer, getSize, matchesSelector, utils, Item, LayoutMode ) {
- return factory( window, Outlayer, getSize, matchesSelector, utils, Item, LayoutMode );
- });
- } else if ( typeof module == 'object' && module.exports ) {
- // CommonJS
- module.exports = factory(
- window,
- require('outlayer'),
- require('get-size'),
- require('desandro-matches-selector'),
- require('fizzy-ui-utils'),
- require('./item'),
- require('./layout-mode'),
- // include default layout modes
- require('./layout-modes/masonry'),
- require('./layout-modes/fit-rows'),
- require('./layout-modes/vertical')
- );
- } else {
- // browser global
- window.Isotope = factory(
- window,
- window.Outlayer,
- window.getSize,
- window.matchesSelector,
- window.fizzyUIUtils,
- window.Isotope.Item,
- window.Isotope.LayoutMode
- );
- }
- }( window, function factory( window, Outlayer, getSize, matchesSelector, utils,
- Item, LayoutMode ) {
- 'use strict';
- // -------------------------- vars -------------------------- //
- var jQuery = window.jQuery;
- // -------------------------- helpers -------------------------- //
- var trim = String.prototype.trim ?
- function( str ) {
- return str.trim();
- } :
- function( str ) {
- return str.replace( /^\s+|\s+$/g, '' );
- };
- // -------------------------- isotopeDefinition -------------------------- //
- // create an Outlayer layout class
- var Isotope = Outlayer.create( 'isotope', {
- layoutMode: 'masonry',
- isJQueryFiltering: true,
- sortAscending: true
- });
- Isotope.Item = Item;
- Isotope.LayoutMode = LayoutMode;
- var proto = Isotope.prototype;
- proto._create = function() {
- this.itemGUID = 0;
- // functions that sort items
- this._sorters = {};
- this._getSorters();
- // call super
- Outlayer.prototype._create.call( this );
- // create layout modes
- this.modes = {};
- // start filteredItems with all items
- this.filteredItems = this.items;
- // keep of track of sortBys
- this.sortHistory = [ 'original-order' ];
- // create from registered layout modes
- for ( var name in LayoutMode.modes ) {
- this._initLayoutMode( name );
- }
- };
- proto.reloadItems = function() {
- // reset item ID counter
- this.itemGUID = 0;
- // call super
- Outlayer.prototype.reloadItems.call( this );
- };
- proto._itemize = function() {
- var items = Outlayer.prototype._itemize.apply( this, arguments );
- // assign ID for original-order
- for ( var i=0; i < items.length; i++ ) {
- var item = items[i];
- item.id = this.itemGUID++;
- }
- this._updateItemsSortData( items );
- return items;
- };
- // -------------------------- layout -------------------------- //
- proto._initLayoutMode = function( name ) {
- var Mode = LayoutMode.modes[ name ];
- // set mode options
- // HACK extend initial options, back-fill in default options
- var initialOpts = this.options[ name ] || {};
- this.options[ name ] = Mode.options ?
- utils.extend( Mode.options, initialOpts ) : initialOpts;
- // init layout mode instance
- this.modes[ name ] = new Mode( this );
- };
- proto.layout = function() {
- // if first time doing layout, do all magic
- if ( !this._isLayoutInited && this._getOption('initLayout') ) {
- this.arrange();
- return;
- }
- this._layout();
- };
- // private method to be used in layout() & magic()
- proto._layout = function() {
- // don't animate first layout
- var isInstant = this._getIsInstant();
- // layout flow
- this._resetLayout();
- this._manageStamps();
- this.layoutItems( this.filteredItems, isInstant );
- // flag for initalized
- this._isLayoutInited = true;
- };
- // filter + sort + layout
- proto.arrange = function( opts ) {
- // set any options pass
- this.option( opts );
- this._getIsInstant();
- // filter, sort, and layout
- // filter
- var filtered = this._filter( this.items );
- this.filteredItems = filtered.matches;
- this._bindArrangeComplete();
- if ( this._isInstant ) {
- this._noTransition( this._hideReveal, [ filtered ] );
- } else {
- this._hideReveal( filtered );
- }
- this._sort();
- this._layout();
- };
- // alias to _init for main plugin method
- proto._init = proto.arrange;
- proto._hideReveal = function( filtered ) {
- this.reveal( filtered.needReveal );
- this.hide( filtered.needHide );
- };
- // HACK
- // Don't animate/transition first layout
- // Or don't animate/transition other layouts
- proto._getIsInstant = function() {
- var isLayoutInstant = this._getOption('layoutInstant');
- var isInstant = isLayoutInstant !== undefined ? isLayoutInstant :
- !this._isLayoutInited;
- this._isInstant = isInstant;
- return isInstant;
- };
- // listen for layoutComplete, hideComplete and revealComplete
- // to trigger arrangeComplete
- proto._bindArrangeComplete = function() {
- // listen for 3 events to trigger arrangeComplete
- var isLayoutComplete, isHideComplete, isRevealComplete;
- var _this = this;
- function arrangeParallelCallback() {
- if ( isLayoutComplete && isHideComplete && isRevealComplete ) {
- _this.dispatchEvent( 'arrangeComplete', null, [ _this.filteredItems ] );
- }
- }
- this.once( 'layoutComplete', function() {
- isLayoutComplete = true;
- arrangeParallelCallback();
- });
- this.once( 'hideComplete', function() {
- isHideComplete = true;
- arrangeParallelCallback();
- });
- this.once( 'revealComplete', function() {
- isRevealComplete = true;
- arrangeParallelCallback();
- });
- };
- // -------------------------- filter -------------------------- //
- proto._filter = function( items ) {
- var filter = this.options.filter;
- filter = filter || '*';
- var matches = [];
- var hiddenMatched = [];
- var visibleUnmatched = [];
- var test = this._getFilterTest( filter );
- // test each item
- for ( var i=0; i < items.length; i++ ) {
- var item = items[i];
- if ( item.isIgnored ) {
- continue;
- }
- // add item to either matched or unmatched group
- var isMatched = test( item );
- // item.isFilterMatched = isMatched;
- // add to matches if its a match
- if ( isMatched ) {
- matches.push( item );
- }
- // add to additional group if item needs to be hidden or revealed
- if ( isMatched && item.isHidden ) {
- hiddenMatched.push( item );
- } else if ( !isMatched && !item.isHidden ) {
- visibleUnmatched.push( item );
- }
- }
- // return collections of items to be manipulated
- return {
- matches: matches,
- needReveal: hiddenMatched,
- needHide: visibleUnmatched
- };
- };
- // get a jQuery, function, or a matchesSelector test given the filter
- proto._getFilterTest = function( filter ) {
- if ( jQuery && this.options.isJQueryFiltering ) {
- // use jQuery
- return function( item ) {
- return jQuery( item.element ).is( filter );
- };
- }
- if ( typeof filter == 'function' ) {
- // use filter as function
- return function( item ) {
- return filter( item.element );
- };
- }
- // default, use filter as selector string
- return function( item ) {
- return matchesSelector( item.element, filter );
- };
- };
- // -------------------------- sorting -------------------------- //
- /**
- * @params {Array} elems
- * @public
- */
- proto.updateSortData = function( elems ) {
- // get items
- var items;
- if ( elems ) {
- elems = utils.makeArray( elems );
- items = this.getItems( elems );
- } else {
- // update all items if no elems provided
- items = this.items;
- }
- this._getSorters();
- this._updateItemsSortData( items );
- };
- proto._getSorters = function() {
- var getSortData = this.options.getSortData;
- for ( var key in getSortData ) {
- var sorter = getSortData[ key ];
- this._sorters[ key ] = mungeSorter( sorter );
- }
- };
- /**
- * @params {Array} items - of Isotope.Items
- * @private
- */
- proto._updateItemsSortData = function( items ) {
- // do not update if no items
- var len = items && items.length;
- for ( var i=0; len && i < len; i++ ) {
- var item = items[i];
- item.updateSortData();
- }
- };
- // ----- munge sorter ----- //
- // encapsulate this, as we just need mungeSorter
- // other functions in here are just for munging
- var mungeSorter = ( function() {
- // add a magic layer to sorters for convienent shorthands
- // `.foo-bar` will use the text of .foo-bar querySelector
- // `[foo-bar]` will use attribute
- // you can also add parser
- // `.foo-bar parseInt` will parse that as a number
- function mungeSorter( sorter ) {
- // if not a string, return function or whatever it is
- if ( typeof sorter != 'string' ) {
- return sorter;
- }
- // parse the sorter string
- var args = trim( sorter ).split(' ');
- var query = args[0];
- // check if query looks like [an-attribute]
- var attrMatch = query.match( /^\[(.+)\]$/ );
- var attr = attrMatch && attrMatch[1];
- var getValue = getValueGetter( attr, query );
- // use second argument as a parser
- var parser = Isotope.sortDataParsers[ args[1] ];
- // parse the value, if there was a parser
- sorter = parser ? function( elem ) {
- return elem && parser( getValue( elem ) );
- } :
- // otherwise just return value
- function( elem ) {
- return elem && getValue( elem );
- };
- return sorter;
- }
- // get an attribute getter, or get text of the querySelector
- function getValueGetter( attr, query ) {
- // if query looks like [foo-bar], get attribute
- if ( attr ) {
- return function getAttribute( elem ) {
- return elem.getAttribute( attr );
- };
- }
- // otherwise, assume its a querySelector, and get its text
- return function getChildText( elem ) {
- var child = elem.querySelector( query );
- return child && child.textContent;
- };
- }
- return mungeSorter;
- })();
- // parsers used in getSortData shortcut strings
- Isotope.sortDataParsers = {
- 'parseInt': function( val ) {
- return parseInt( val, 10 );
- },
- 'parseFloat': function( val ) {
- return parseFloat( val );
- }
- };
- // ----- sort method ----- //
- // sort filteredItem order
- proto._sort = function() {
- if ( !this.options.sortBy ) {
- return;
- }
- // keep track of sortBy History
- var sortBys = utils.makeArray( this.options.sortBy );
- if ( !this._getIsSameSortBy( sortBys ) ) {
- // concat all sortBy and sortHistory, add to front, oldest goes in last
- this.sortHistory = sortBys.concat( this.sortHistory );
- }
- // sort magic
- var itemSorter = getItemSorter( this.sortHistory, this.options.sortAscending );
- this.filteredItems.sort( itemSorter );
- };
- // check if sortBys is same as start of sortHistory
- proto._getIsSameSortBy = function( sortBys ) {
- for ( var i=0; i < sortBys.length; i++ ) {
- if ( sortBys[i] != this.sortHistory[i] ) {
- return false;
- }
- }
- return true;
- };
- // returns a function used for sorting
- function getItemSorter( sortBys, sortAsc ) {
- return function sorter( itemA, itemB ) {
- // cycle through all sortKeys
- for ( var i = 0; i < sortBys.length; i++ ) {
- var sortBy = sortBys[i];
- var a = itemA.sortData[ sortBy ];
- var b = itemB.sortData[ sortBy ];
- if ( a > b || a < b ) {
- // if sortAsc is an object, use the value given the sortBy key
- var isAscending = sortAsc[ sortBy ] !== undefined ? sortAsc[ sortBy ] : sortAsc;
- var direction = isAscending ? 1 : -1;
- return ( a > b ? 1 : -1 ) * direction;
- }
- }
- return 0;
- };
- }
- // -------------------------- methods -------------------------- //
- // get layout mode
- proto._mode = function() {
- var layoutMode = this.options.layoutMode;
- var mode = this.modes[ layoutMode ];
- if ( !mode ) {
- // TODO console.error
- throw new Error( 'No layout mode: ' + layoutMode );
- }
- // HACK sync mode's options
- // any options set after init for layout mode need to be synced
- mode.options = this.options[ layoutMode ];
- return mode;
- };
- proto._resetLayout = function() {
- // trigger original reset layout
- Outlayer.prototype._resetLayout.call( this );
- this._mode()._resetLayout();
- };
- proto._getItemLayoutPosition = function( item ) {
- return this._mode()._getItemLayoutPosition( item );
- };
- proto._manageStamp = function( stamp ) {
- this._mode()._manageStamp( stamp );
- };
- proto._getContainerSize = function() {
- return this._mode()._getContainerSize();
- };
- proto.needsResizeLayout = function() {
- return this._mode().needsResizeLayout();
- };
- // -------------------------- adding & removing -------------------------- //
- // HEADS UP overwrites default Outlayer appended
- proto.appended = function( elems ) {
- var items = this.addItems( elems );
- if ( !items.length ) {
- return;
- }
- // filter, layout, reveal new items
- var filteredItems = this._filterRevealAdded( items );
- // add to filteredItems
- this.filteredItems = this.filteredItems.concat( filteredItems );
- };
- // HEADS UP overwrites default Outlayer prepended
- proto.prepended = function( elems ) {
- var items = this._itemize( elems );
- if ( !items.length ) {
- return;
- }
- // start new layout
- this._resetLayout();
- this._manageStamps();
- // filter, layout, reveal new items
- var filteredItems = this._filterRevealAdded( items );
- // layout previous items
- this.layoutItems( this.filteredItems );
- // add to items and filteredItems
- this.filteredItems = filteredItems.concat( this.filteredItems );
- this.items = items.concat( this.items );
- };
- proto._filterRevealAdded = function( items ) {
- var filtered = this._filter( items );
- this.hide( filtered.needHide );
- // reveal all new items
- this.reveal( filtered.matches );
- // layout new items, no transition
- this.layoutItems( filtered.matches, true );
- return filtered.matches;
- };
- /**
- * Filter, sort, and layout newly-appended item elements
- * @param {Array or NodeList or Element} elems
- */
- proto.insert = function( elems ) {
- var items = this.addItems( elems );
- if ( !items.length ) {
- return;
- }
- // append item elements
- var i, item;
- var len = items.length;
- for ( i=0; i < len; i++ ) {
- item = items[i];
- this.element.appendChild( item.element );
- }
- // filter new stuff
- var filteredInsertItems = this._filter( items ).matches;
- // set flag
- for ( i=0; i < len; i++ ) {
- items[i].isLayoutInstant = true;
- }
- this.arrange();
- // reset flag
- for ( i=0; i < len; i++ ) {
- delete items[i].isLayoutInstant;
- }
- this.reveal( filteredInsertItems );
- };
- var _remove = proto.remove;
- proto.remove = function( elems ) {
- elems = utils.makeArray( elems );
- var removeItems = this.getItems( elems );
- // do regular thing
- _remove.call( this, elems );
- // bail if no items to remove
- var len = removeItems && removeItems.length;
- // remove elems from filteredItems
- for ( var i=0; len && i < len; i++ ) {
- var item = removeItems[i];
- // remove item from collection
- utils.removeFrom( this.filteredItems, item );
- }
- };
- proto.shuffle = function() {
- // update random sortData
- for ( var i=0; i < this.items.length; i++ ) {
- var item = this.items[i];
- item.sortData.random = Math.random();
- }
- this.options.sortBy = 'random';
- this._sort();
- this._layout();
- };
- /**
- * trigger fn without transition
- * kind of hacky to have this in the first place
- * @param {Function} fn
- * @param {Array} args
- * @returns ret
- * @private
- */
- proto._noTransition = function( fn, args ) {
- // save transitionDuration before disabling
- var transitionDuration = this.options.transitionDuration;
- // disable transition
- this.options.transitionDuration = 0;
- // do it
- var returnValue = fn.apply( this, args );
- // re-enable transition for reveal
- this.options.transitionDuration = transitionDuration;
- return returnValue;
- };
- // ----- helper methods ----- //
- /**
- * getter method for getting filtered item elements
- * @returns {Array} elems - collection of item elements
- */
- proto.getFilteredItemElements = function() {
- return this.filteredItems.map( function( item ) {
- return item.element;
- });
- };
- // ----- ----- //
- return Isotope;
- }));
|