/*! * Isotope v2.0.0-beta.1 * Magical sorting and filtering layouts * http://isotope.metafizzy.co */ ( function( window ) { 'use strict'; // -------------------------- vars -------------------------- // var jQuery = window.jQuery; // -------------------------- helpers -------------------------- // // extend objects function extend( a, b ) { for ( var prop in b ) { a[ prop ] = b[ prop ]; } return a; } var trim = String.prototype.trim ? function( str ) { return str.trim(); } : function( str ) { return str.replace( /^\s+|\s+$/g, '' ); }; var docElem = document.documentElement; var getText = docElem.textContent ? function( elem ) { return elem.textContent; } : function( elem ) { return elem.innerText; }; // -------------------------- isotopeDefinition -------------------------- // // used for AMD definition and requires function isotopeDefinition( Outlayer, getSize, matchesSelector, Item, LayoutMode ) { // create an Outlayer layout class var Isotope = Outlayer.create( 'isotope', { sortAscending: true }); Isotope.Item = Isotope.prototype.settings.item = Item; Isotope.LayoutMode = LayoutMode; Isotope.prototype._create = function() { this.itemGUID = 0; // call super Outlayer.prototype._create.call( this ); // create layout modes this.modes = {}; // create from registered layout modes for ( var name in LayoutMode.modes ) { this._initLayoutMode( name ); } // start filteredItems with all items this.filteredItems = this.items; // functions that sort items this._sorters = {}; // keep of track of sortBys this.sortHistory = [ 'original-order' ]; this.updateSortData(); }; Isotope.prototype.reloadItems = function() { // reset item ID counter this.itemGUID = 0; // call super Outlayer.prototype.reloadItems.call( this ); }; Isotope.prototype._itemize = function() { var items = Outlayer.prototype._itemize.apply( this, arguments ); // assign ID for original-order for ( var i=0, len = items.length; i < len; i++ ) { var item = items[i]; item.id = this.itemGUID++; } return items; }; // -------------------------- layout -------------------------- // Isotope.prototype._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 ? extend( Mode.options, initialOpts ) : initialOpts; // init layout mode instance this.modes[ name ] = new Mode( this ); }; Isotope.prototype.layout = function() { // if first time doing layout, do all magic if ( !this._isLayoutInited && this.options.isInitLayout ) { this.arrange(); return; } this._layout(); }; // private method to be used in layout() & magic() Isotope.prototype._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 Isotope.prototype.arrange = function( opts ) { // set any options pass this.option( opts ); this._getIsInstant(); // where the magic happens, amirite this.filteredItems = this._filter( this.items ); this._sort(); this._layout(); }; // alias to _init for main plugin method Isotope.prototype._init = Isotope.prototype.arrange; // HACK // Don't animate/transition first layout // Or don't animate/transition other layouts Isotope.prototype._getIsInstant = function() { var isInstant = this.options.isLayoutInstant !== undefined ? this.options.isLayoutInstant : !this._isLayoutInited; return this._isInstant = isInstant; }; // -------------------------- filter -------------------------- // Isotope.prototype._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, len = items.length; i < len; 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 ); } } // HACK // disable transition if instant var _transitionDuration = this.options.transitionDuration; if ( this._isInstant ) { this.options.transitionDuration = 0; } this.reveal( hiddenMatched ); this.hide( visibleUnmatched ); // set back if ( this._isInstant ) { this.options.transitionDuration = _transitionDuration; } return matches; }; // get a jQuery, function, or a matchesSelector test given the filter Isotope.prototype._getFilterTest = function( filter ) { var test; if ( jQuery && this.options.isJQueryFiltering ) { test = function( item ) { return jQuery( item.element ).is( filter ); }; } else if ( typeof filter === 'function' ) { test = function( item ) { return filter( item.element ); }; } else { test = function( item ) { return matchesSelector( item.element, filter ); }; } return test; }; // -------------------------- sorting -------------------------- // Isotope.prototype.updateSortData = function( items ) { // update sorters var getSortData = this.options.getSortData; for ( var key in getSortData ) { var sorter = getSortData[ key ]; this._sorters[ key ] = mungeSorter( sorter ); } // update item sort data // default to all items if none are passed in items = items || this.items; for ( var i=0, len = items.length; i < len; i++ ) { var item = items[i]; if ( item.isIgnored ) { continue; } 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 = getParser( 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 ) { var getValue; // if query looks like [foo-bar], get attribute if ( attr ) { getValue = function( elem ) { return elem.getAttribute( attr ); }; } else { // otherwise, assume its a querySelector, and get its text getValue = function( elem ) { var child = elem.querySelector( query ); return child && getText( child ); }; } return getValue; } // return a parser function if arg matches function getParser( arg ) { var parser; switch ( arg ) { case 'parseInt' : parser = function( val ) { return parseInt( val, 10 ); }; break; case 'parseFloat' : parser = function( val ) { return parseFloat( val ); }; break; default : // just return val if parser isn't one of these // TODO - console log that that parser doesn't exist parser = function( val ) { return val; }; } return parser; } return mungeSorter; })(); // ----- sort method ----- // // sort filteredItem order Isotope.prototype._sort = function() { var sortByOpt = this.options.sortBy; if ( !sortByOpt ) { return; } // concat all sortBy and sortHistory var sortBys = [].concat.apply( sortByOpt, this.sortHistory ); // sort magic var itemSorter = getItemSorter( sortBys, this.options.sortAscending ); this.filteredItems.sort( itemSorter ); // keep track of sortBy History if ( sortByOpt !== this.sortHistory[0] ) { // add to front, oldest goes in last this.sortHistory.unshift( sortByOpt ); } }; // returns a function used for sorting function getItemSorter( sortBys, sortAsc ) { return function sorter( itemA, itemB ) { // cycle through all sortKeys for ( var i = 0, len = sortBys.length; i < len; 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 Isotope.prototype._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; }; Isotope.prototype._resetLayout = function() { // trigger original reset layout Outlayer.prototype._resetLayout.call( this ); this._mode()._resetLayout(); }; Isotope.prototype._getItemLayoutPosition = function( item ) { return this._mode()._getItemLayoutPosition( item ); }; Isotope.prototype._manageStamp = function( stamp ) { var mode = this._mode(); // HACK copy over some options mode.options.isOriginLeft = this.options.isOriginLeft; mode.options.isOriginTop = this.options.isOriginTop; mode._manageStamp( stamp ); }; Isotope.prototype._getContainerSize = function() { return this._mode()._getContainerSize(); }; Isotope.prototype.resize = function() { this._mode().resize(); }; return Isotope; } // -------------------------- transport -------------------------- // if ( typeof define === 'function' && define.amd ) { // AMD define( [ 'outlayer/outlayer', 'get-size/get-size', 'matches-selector/matches-selector', './item.js', './layout-modes.js' ], isotopeDefinition ); } else { // browser global window.Isotope = isotopeDefinition( window.Outlayer, window.getSize, window.matchesSelector, window.Isotope.Item, window.Isotope.LayoutMode ); } })( window );