/** * Isotope v1.4.110629 * An exquisite jQuery plugin for magical layouts * http://isotope.metafizzy.co * * Commercial use requires one-time license fee * http://metafizzy.co/#licenses * * Copyright 2011 David DeSandro / Metafizzy */ /*global Modernizr: true */ (function( window, $, undefined ){ // helper function function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } // ========================= getStyleProperty by kangax =============================== // http://perfectionkills.com/feature-testing-css-properties/ var prefixes = 'Moz Webkit Khtml O Ms'.split(' '); function getStyleProperty( propName, element ) { element = element || document.documentElement; var style = element.style, prefixed; // test standard property first if ( typeof style[propName] === 'string' ) { return propName; } // capitalize propName = capitalize( propName ); // test vendor specific properties for ( var i=0, len = prefixes.length; i < len; i++ ) { prefixed = prefixes[i] + propName; if ( typeof style[ prefixed ] === 'string' ) { return prefixed; } } } var transformProp = getStyleProperty('transform'); // ========================= miniModernizr =============================== // <3<3<3 and thanks to Faruk and Paul for doing the heavy lifting /*! * Modernizr v1.6ish: miniModernizr for Isotope * http://www.modernizr.com * * Developed by: * - Faruk Ates http://farukat.es/ * - Paul Irish http://paulirish.com/ * * Copyright (c) 2009-2010 * Dual-licensed under the BSD or MIT licenses. * http://www.modernizr.com/license/ */ /* * This version whittles down the script just to check support for * CSS transitions, transforms, and 3D transforms. */ var docElement = document.documentElement, tests = [ { name : 'csstransforms', getResult : function() { return !!transformProp; } }, { name : 'csstransforms3d', getResult : function() { var test = !!getStyleProperty('perspective'); // double check for Chrome's false positive if ( test ){ var st = document.createElement('style'), div = document.createElement('div'), vendorCSSPrefixes = ' -o- -moz- -ms- -webkit- -khtml- '.split(' '), mq = '@media (' + vendorCSSPrefixes.join('transform-3d),(') + 'modernizr)'; st.textContent = mq + '{#modernizr{height:3px}}'; (document.head || document.getElementsByTagName('head')[0]).appendChild(st); div.id = 'modernizr'; docElement.appendChild(div); test = div.offsetHeight === 3; st.parentNode.removeChild(st); div.parentNode.removeChild(div); } return !!test; } }, { name : 'csstransitions', getResult : function() { return !!getStyleProperty('transitionProperty'); } } ], i, len = tests.length ; if ( window.Modernizr ) { // if there's a previous Modernzir, check if there are necessary tests for ( i=0; i < len; i++ ) { var test = tests[i]; if ( !Modernizr.hasOwnProperty( test.name ) ) { // if test hasn't been run, use addTest to run it Modernizr.addTest( test.name, test.getResult ); } } } else { // or create new mini Modernizr that just has the 3 tests window.Modernizr = (function(){ var miniModernizr = { _version : '1.6ish: miniModernizr for Isotope' }, classes = [], test, result, className; // Run through tests for ( i=0; i < len; i++ ) { test = tests[i]; result = test.getResult(); miniModernizr[ test.name ] = result; className = ( result ? '' : 'no-' ) + test.name; classes.push( className ); } // Add the new classes to the element. docElement.className += ' ' + classes.join( ' ' ); return miniModernizr; })(); } // ========================= isoTransform =============================== /** * provides hooks for .css({ scale: value, translate: [x, y] }) * Progressively enhanced CSS transforms * Uses hardware accelerated 3D transforms for Safari * or falls back to 2D transforms. */ if ( Modernizr.csstransforms ) { // i.e. transformFnNotations.scale(0.5) >> 'scale3d( 0.5, 0.5, 1)' var transformFnNotations = Modernizr.csstransforms3d ? { // 3D transform functions translate : function ( position ) { return 'translate3d(' + position[0] + 'px, ' + position[1] + 'px, 0) '; }, scale : function ( scale ) { return 'scale3d(' + scale + ', ' + scale + ', 1) '; } } : { // 2D transform functions translate : function ( position ) { return 'translate(' + position[0] + 'px, ' + position[1] + 'px) '; }, scale : function ( scale ) { return 'scale(' + scale + ') '; } } ; var setIsoTransform = function ( elem, name, value ) { // unpack current transform data var data = $.data( elem, 'isoTransform' ) || {}, newData = {}, fnName, transformObj = {}, transformValue; // i.e. newData.scale = 0.5 newData[ name ] = value; // extend new value over current data $.extend( data, newData ); for ( fnName in data ) { transformValue = data[ fnName ]; transformObj[ fnName ] = transformFnNotations[ fnName ]( transformValue ); } // get proper order // ideally, we could loop through this give an array, but since we only have // a couple transforms we're keeping track of, we'll do it like so var translateFn = transformObj.translate || '', scaleFn = transformObj.scale || '', // sorting so translate always comes first valueFns = translateFn + scaleFn; // set data back in elem $.data( elem, 'isoTransform', data ); // set name to vendor specific property elem.style[ transformProp ] = valueFns; }; // ==================== scale =================== $.cssNumber.scale = true; $.cssHooks.scale = { set: function( elem, value ) { // uncomment this bit if you want to properly parse strings // if ( typeof value === 'string' ) { // value = parseFloat( value ); // } setIsoTransform( elem, 'scale', value ); }, get: function( elem, computed ) { var transform = $.data( elem, 'isoTransform' ); return transform && transform.scale ? transform.scale : 1; } }; $.fx.step.scale = function( fx ) { $.cssHooks.scale.set( fx.elem, fx.now+fx.unit ); }; // ==================== translate =================== $.cssNumber.translate = true; $.cssHooks.translate = { set: function( elem, value ) { // uncomment this bit if you want to properly parse strings // if ( typeof value === 'string' ) { // value = value.split(' '); // } // // var i, val; // for ( i = 0; i < 2; i++ ) { // val = value[i]; // if ( typeof val === 'string' ) { // val = parseInt( val ); // } // } setIsoTransform( elem, 'translate', value ); }, get: function( elem, computed ) { var transform = $.data( elem, 'isoTransform' ); return transform && transform.translate ? transform.translate : [ 0, 0 ]; } }; } /* * smartresize: debounced resize event for jQuery * * latest version and complete README available on Github: * https://github.com/louisremi/jquery.smartresize.js * * Copyright 2011 @louis_remi * Licensed under the MIT license. */ var $event = $.event, resizeTimeout; $event.special.smartresize = { setup: function() { $(this).bind( "resize", $event.special.smartresize.handler ); }, teardown: function() { $(this).unbind( "resize", $event.special.smartresize.handler ); }, handler: function( event, execAsap ) { // Save the context var context = this, args = arguments; // set correct event type event.type = "smartresize"; if ( resizeTimeout ) { clearTimeout( resizeTimeout ); } resizeTimeout = setTimeout(function() { jQuery.event.handle.apply( context, args ); }, execAsap === "execAsap"? 0 : 100 ); } }; $.fn.smartresize = function( fn ) { return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] ); }; // ========================= Isotope =============================== // our "Widget" object constructor $.Isotope = function( options, element ){ this.element = $( element ); this._create( options ); this._init(); }; // styles of container element we want to keep track of var isoContainerStyles = [ 'overflow', 'position', 'width', 'height' ]; $.Isotope.settings = { resizable: true, layoutMode : 'masonry', containerClass : 'isotope', itemClass : 'isotope-item', hiddenClass : 'isotope-hidden', hiddenStyle : Modernizr.csstransforms && !$.browser.opera ? { opacity : 0, scale : 0.001 } : // browsers support CSS transforms, not Opera { opacity : 0 }, // other browsers, including Opera visibleStyle : Modernizr.csstransforms && !$.browser.opera ? { opacity : 1, scale : 1 } : // browsers support CSS transforms, not Opera { opacity : 1 }, // other browsers, including Opera animationEngine : $.browser.opera ? 'jquery' : 'best-available', animationOptions: { queue: false, duration: 800 }, sortBy : 'original-order', sortAscending : true, resizesContainer : true, transformsEnabled : true, itemPositionDataEnabled: false }; $.Isotope.prototype = { // sets up widget _create : function( options ) { this.options = $.extend( true, {}, $.Isotope.settings, options ); this.styleQueue = []; this.elemCount = 0; // get original styles in case we re-apply them in .destroy() var elemStyle = this.element[0].style; this.originalStyle = {}; for ( var i=0, len = isoContainerStyles.length; i < len; i++ ) { var prop = isoContainerStyles[i]; this.originalStyle[ prop ] = elemStyle[ prop ] || null; } this.element.css({ overflow : 'hidden', position : 'relative' }); this._updateAnimationEngine(); this._updateUsingTransforms(); // sorting var originalOrderSorter = { 'original-order' : function( $elem, instance ) { return instance.elemCount; }, random : function() { return Math.random(); } }; this.options.getSortData = $.extend( this.options.getSortData, originalOrderSorter ); // need to get atoms this.reloadItems(); // get top left position of where the bricks should be var $cursor = $( document.createElement('div') ).prependTo( this.element ); this.offset = $cursor.position(); $cursor.remove(); // add isotope class first time around var instance = this; setTimeout( function() { instance.element.addClass( instance.options.containerClass ); }, 0 ); // bind resize method if ( this.options.resizable ) { $(window).bind( 'smartresize.isotope', function() { instance.resize(); }); } }, _getAtoms : function( $elems ) { var selector = this.options.itemSelector, // filter & find $atoms = selector ? $elems.filter( selector ).add( $elems.find( selector ) ) : $elems, // base style for atoms atomStyle = { position: 'absolute' }; if ( this.usingTransforms ) { atomStyle.left = 0; atomStyle.top = 0; } $atoms.css( atomStyle ).addClass( this.options.itemClass ); this.updateSortData( $atoms, true ); return $atoms; }, // _init fires when your instance is first created // (from the constructor above), and when you // attempt to initialize the widget again (by the bridge) // after it has already been initialized. _init : function( callback ) { this.$filteredAtoms = this._filter( this.$allAtoms ); this._sort(); this.reLayout( callback ); }, option : function( opts ){ // change options AFTER initialization: // signature: $('#foo').bar({ cool:false }); if ( $.isPlainObject( opts ) ){ this.options = $.extend( true, this.options, opts ); // trigger _updateOptionName if it exists var updateOptionFn; for ( var optionName in opts ) { updateOptionFn = '_update' + capitalize( optionName ); if ( this[ updateOptionFn ] ) { this[ updateOptionFn ](); } } } }, // ====================== updaters ====================== // // kind of like setters _updateAnimationEngine : function() { var animationEngine = this.options.animationEngine.toLowerCase().replace( /[ _\-]/g, ''); // set applyStyleFnName switch ( animationEngine ) { case 'css' : case 'none' : this.isUsingJQueryAnimation = false; break; case 'jquery' : this.isUsingJQueryAnimation = true; break; default : // best available this.isUsingJQueryAnimation = !Modernizr.csstransitions; } this._updateUsingTransforms(); }, _updateTransformsEnabled : function() { this._updateUsingTransforms(); }, _updateUsingTransforms : function() { this.usingTransforms = this.options.transformsEnabled && Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation; this.getPositionStyles = this.usingTransforms ? this._translate : this._positionAbs; }, // ====================== Filtering ====================== _filter : function( $atoms ) { var $filteredAtoms, filter = this.options.filter === '' ? '*' : this.options.filter; if ( !filter ) { $filteredAtoms = $atoms; } else { var hiddenClass = this.options.hiddenClass, hiddenSelector = '.' + hiddenClass, $visibleAtoms = $atoms.not( hiddenSelector ), $hiddenAtoms = $atoms.filter( hiddenSelector ), $atomsToShow = $hiddenAtoms; $filteredAtoms = $atoms.filter( filter ); if ( filter !== '*' ) { $atomsToShow = $hiddenAtoms.filter( filter ); var $atomsToHide = $visibleAtoms.not( filter ).toggleClass( hiddenClass ); $atomsToHide.addClass( hiddenClass ); this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle }); } this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle }); $atomsToShow.removeClass( hiddenClass ); } return $filteredAtoms; }, // ====================== Sorting ====================== updateSortData : function( $atoms, isIncrementingElemCount ) { var instance = this, getSortData = this.options.getSortData, $this, sortData; $atoms.each(function(){ $this = $(this); sortData = {}; // get value for sort data based on fn( $elem ) passed in for ( var key in getSortData ) { sortData[ key ] = getSortData[ key ]( $this, instance ); } // apply sort data to element $.data( this, 'isotope-sort-data', sortData ); if ( isIncrementingElemCount ) { instance.elemCount ++; } }); }, // used on all the filtered atoms _sort : function() { var sortBy = this.options.sortBy, getSorter = this._getSorter, sortDir = this.options.sortAscending ? 1 : -1, sortFn = function( alpha, beta ) { var a = getSorter( alpha, sortBy ), b = getSorter( beta, sortBy ); // fall back to original order if data matches if ( a === b && sortBy !== 'original-order') { a = getSorter( alpha, 'original-order' ); b = getSorter( beta, 'original-order' ); } return ( ( a > b ) ? 1 : ( a < b ) ? -1 : 0 ) * sortDir; }; this.$filteredAtoms.sort( sortFn ); }, _getSorter : function( elem, sortBy ) { return $.data( elem, 'isotope-sort-data' )[ sortBy ]; }, // ====================== Layout Helpers ====================== _translate : function( x, y ) { return { translate : [ x, y ] }; }, _positionAbs : function( x, y ) { return { left: x, top: y }; }, _pushPosition : function( $elem, x, y ) { x += this.offset.left; y += this.offset.top; var position = this.getPositionStyles( x, y ); this.styleQueue.push({ $el: $elem, style: position }); if ( this.options.itemPositionDataEnabled ) { $elem.data('isotope-item-position', {x: x, y: y} ); } }, // ====================== General Layout ====================== // used on collection of atoms (should be filtered, and sorted before ) // accepts atoms-to-be-laid-out to start with layout : function( $elems, callback ) { var layoutMode = this.options.layoutMode; // layout logic this[ '_' + layoutMode + 'Layout' ]( $elems ); // set the size of the container if ( this.options.resizesContainer ) { var containerStyle = this[ '_' + layoutMode + 'GetContainerSize' ](); this.styleQueue.push({ $el: this.element, style: containerStyle }); } this._processStyleQueue(); // provide $elems as context for the callback if ( callback ) { callback.call( $elems ); } this.isLaidOut = true; }, _processStyleQueue : function() { // are we animating the layout arrangement? // use plugin-ish syntax for css or animate var styleFn = !this.isLaidOut ? 'css' : ( this.isUsingJQueryAnimation ? 'animate' : 'css' ), animOpts = this.options.animationOptions, _isInsertingAnimated = this._isInserting && this.isUsingJQueryAnimation, objStyleFn; // process styleQueue $.each( this.styleQueue, function( i, obj ) { objStyleFn = _isInsertingAnimated && obj.$el.hasClass('no-transition') ? 'css' : styleFn; obj.$el[ objStyleFn ]( obj.style, animOpts ); }); // clear out queue for next time this.styleQueue = []; }, resize : function() { if ( this[ '_' + this.options.layoutMode + 'ResizeChanged' ]() ) { this.reLayout(); } }, reLayout : function( callback ) { this[ '_' + this.options.layoutMode + 'Reset' ](); this.layout( this.$filteredAtoms, callback ); }, // ====================== Convenience methods ====================== // ====================== Adding items ====================== // adds a jQuery object of items to a isotope container addItems : function( $content, callback ) { var $newAtoms = this._getAtoms( $content ); // add new atoms to atoms pools // FIXME : this breaks shuffle order and returns to original order this.$allAtoms = this.$allAtoms.add( $newAtoms ); if ( callback ) { callback( $newAtoms ); } }, // convienence method for adding elements properly to any layout // positions items, hides them, then animates them back in <--- very sezzy insert : function( $content, callback ) { // position items this.element.append( $content ); var instance = this; this.addItems( $content, function( $newAtoms ) { var $newFilteredAtoms = instance._filter( $newAtoms, true ); instance._addHideAppended( $newFilteredAtoms ); instance._sort(); instance.reLayout(); instance._revealAppended( $newFilteredAtoms, callback ); }); }, // convienence method for working with Infinite Scroll appended : function( $content, callback ) { var instance = this; this.addItems( $content, function( $newAtoms ) { instance._addHideAppended( $newAtoms ); instance.layout( $newAtoms ); instance._revealAppended( $newAtoms, callback ); }); }, // adds new atoms, then hides them before positioning _addHideAppended : function( $newAtoms ) { this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms ); $newAtoms.addClass('no-transition'); this._isInserting = true; // apply hidden styles this.styleQueue.push({ $el: $newAtoms, style: this.options.hiddenStyle }); }, // sets visible style on new atoms _revealAppended : function( $newAtoms, callback ) { var instance = this; // apply visible style after a sec setTimeout( function() { // enable animation $newAtoms.removeClass('no-transition'); // reveal newly inserted filtered elements instance.styleQueue.push({ $el: $newAtoms, style: instance.options.visibleStyle }); instance._processStyleQueue(); delete instance._isInserting; if ( callback ) { callback( $newAtoms ); } }, 10 ); }, // gathers all atoms reloadItems : function() { this.$allAtoms = this._getAtoms( this.element.children() ); }, // removes elements from Isotope widget remove : function( $content ) { this.$allAtoms = this.$allAtoms.not( $content ); this.$filteredAtoms = this.$filteredAtoms.not( $content ); $content.remove(); }, shuffle : function() { this.updateSortData( this.$allAtoms ); this.options.sortBy = 'random'; this._sort(); this.reLayout(); }, // destroys widget, returns elements and container back (close) to original style destroy : function() { var usingTransforms = this.usingTransforms; this.$allAtoms .removeClass( this.options.hiddenClass + ' ' + this.options.itemClass ) .each(function(){ this.style.position = null; this.style.top = null; this.style.left = null; this.style.opacity = null; if ( usingTransforms ) { this.style[ transformProp ] = null; } }); // re-apply saved container styles var elemStyle = this.element[0].style; for ( var i=0, len = isoContainerStyles.length; i < len; i++ ) { var prop = isoContainerStyles[i]; elemStyle[ prop ] = this.originalStyle[ prop ]; } this.element .unbind('.isotope') .removeClass( this.options.containerClass ) .removeData('isotope'); $(window).unbind('.isotope'); }, // ====================== LAYOUTS ====================== // calculates number of rows or columns // requires columnWidth or rowHeight to be set on namespaced object // i.e. this.masonry.columnWidth = 200 _getSegments : function( isRows ) { var namespace = this.options.layoutMode, measure = isRows ? 'rowHeight' : 'columnWidth', size = isRows ? 'height' : 'width', segmentsName = isRows ? 'rows' : 'cols', containerSize = this.element[ size ](), segments, // i.e. options.masonry && options.masonry.columnWidth segmentSize = this.options[ namespace ] && this.options[ namespace ][ measure ] || // or use the size of the first item, i.e. outerWidth this.$filteredAtoms[ 'outer' + capitalize(size) ](true) || // if there's no items, use size of container containerSize; segments = Math.floor( containerSize / segmentSize ); segments = Math.max( segments, 1 ); // i.e. this.masonry.cols = .... this[ namespace ][ segmentsName ] = segments; // i.e. this.masonry.columnWidth = ... this[ namespace ][ measure ] = segmentSize; }, _checkIfSegmentsChanged : function( isRows ) { var namespace = this.options.layoutMode, segmentsName = isRows ? 'rows' : 'cols', prevSegments = this[ namespace ][ segmentsName ]; // update cols/rows this._getSegments( isRows ); // return if updated cols/rows is not equal to previous return ( this[ namespace ][ segmentsName ] !== prevSegments ); }, // ====================== Masonry ====================== _masonryReset : function() { // layout-specific props this.masonry = {}; // FIXME shouldn't have to call this again this._getSegments(); var i = this.masonry.cols; this.masonry.colYs = []; while (i--) { this.masonry.colYs.push( 0 ); } }, _masonryLayout : function( $elems ) { var instance = this, props = instance.masonry; $elems.each(function(){ var $this = $(this), //how many columns does this brick span colSpan = Math.ceil( $this.outerWidth(true) / props.columnWidth ); colSpan = Math.min( colSpan, props.cols ); if ( colSpan === 1 ) { // if brick spans only one column, just like singleMode instance._masonryPlaceBrick( $this, props.colYs ); } else { // brick spans more than one column // how many different places could this brick fit horizontally var groupCount = props.cols + 1 - colSpan, groupY = [], groupColY, i; // for each group potential horizontal position for ( i=0; i < groupCount; i++ ) { // make an array of colY values for that one group groupColY = props.colYs.slice( i, i+colSpan ); // and get the max value of the array groupY[i] = Math.max.apply( Math, groupColY ); } instance._masonryPlaceBrick( $this, groupY ); } }); }, // worker method that places brick in the columnSet // with the the minY _masonryPlaceBrick : function( $brick, setY ) { // get the minimum Y value from the columns var minimumY = Math.min.apply( Math, setY ), shortCol = 0; // Find index of short column, the first from the left for (var i=0, len = setY.length; i < len; i++) { if ( setY[i] === minimumY ) { shortCol = i; break; } } // position the brick var x = this.masonry.columnWidth * shortCol, y = minimumY; this._pushPosition( $brick, x, y ); // apply setHeight to necessary columns var setHeight = minimumY + $brick.outerHeight(true), setSpan = this.masonry.cols + 1 - len; for ( i=0; i < setSpan; i++ ) { this.masonry.colYs[ shortCol + i ] = setHeight; } }, _masonryGetContainerSize : function() { var containerHeight = Math.max.apply( Math, this.masonry.colYs ); return { height: containerHeight }; }, _masonryResizeChanged : function() { return this._checkIfSegmentsChanged(); }, // ====================== fitRows ====================== _fitRowsReset : function() { this.fitRows = { x : 0, y : 0, height : 0 }; }, _fitRowsLayout : function( $elems ) { var instance = this, containerWidth = this.element.width(), props = this.fitRows; $elems.each( function() { var $this = $(this), atomW = $this.outerWidth(true), atomH = $this.outerHeight(true); if ( props.x !== 0 && atomW + props.x > containerWidth ) { // if this element cannot fit in the current row props.x = 0; props.y = props.height; } // position the atom instance._pushPosition( $this, props.x, props.y ); props.height = Math.max( props.y + atomH, props.height ); props.x += atomW; }); }, _fitRowsGetContainerSize : function () { return { height : this.fitRows.height }; }, _fitRowsResizeChanged : function() { return true; }, // ====================== cellsByRow ====================== _cellsByRowReset : function() { this.cellsByRow = { index : 0 }; // get this.cellsByRow.columnWidth this._getSegments(); // get this.cellsByRow.rowHeight this._getSegments(true); }, _cellsByRowLayout : function( $elems ) { var instance = this, props = this.cellsByRow; $elems.each( function(){ var $this = $(this), col = props.index % props.cols, row = ~~( props.index / props.cols ), x = ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2, y = ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2; instance._pushPosition( $this, x, y ); props.index ++; }); }, _cellsByRowGetContainerSize : function() { return { height : Math.ceil( this.$filteredAtoms.length / this.cellsByRow.cols ) * this.cellsByRow.rowHeight + this.offset.top }; }, _cellsByRowResizeChanged : function() { return this._checkIfSegmentsChanged(); }, // ====================== straightDown ====================== _straightDownReset : function() { this.straightDown = { y : 0 }; }, _straightDownLayout : function( $elems ) { var instance = this; $elems.each( function( i ){ var $this = $(this); instance._pushPosition( $this, 0, instance.straightDown.y ); instance.straightDown.y += $this.outerHeight(true); }); }, _straightDownGetContainerSize : function() { return { height : this.straightDown.y }; }, _straightDownResizeChanged : function() { return true; }, // ====================== masonryHorizontal ====================== _masonryHorizontalReset : function() { // layout-specific props this.masonryHorizontal = {}; // FIXME shouldn't have to call this again this._getSegments( true ); var i = this.masonryHorizontal.rows; this.masonryHorizontal.rowXs = []; while (i--) { this.masonryHorizontal.rowXs.push( 0 ); } }, _masonryHorizontalLayout : function( $elems ) { var instance = this, props = instance.masonryHorizontal; $elems.each(function(){ var $this = $(this), //how many rows does this brick span rowSpan = Math.ceil( $this.outerHeight(true) / props.rowHeight ); rowSpan = Math.min( rowSpan, props.rows ); if ( rowSpan === 1 ) { // if brick spans only one column, just like singleMode instance._masonryHorizontalPlaceBrick( $this, props.rowXs ); } else { // brick spans more than one row // how many different places could this brick fit horizontally var groupCount = props.rows + 1 - rowSpan, groupX = [], groupRowX, i; // for each group potential horizontal position for ( i=0; i < groupCount; i++ ) { // make an array of colY values for that one group groupRowX = props.rowXs.slice( i, i+rowSpan ); // and get the max value of the array groupX[i] = Math.max.apply( Math, groupRowX ); } instance._masonryHorizontalPlaceBrick( $this, groupX ); } }); }, _masonryHorizontalPlaceBrick : function( $brick, setX ) { // get the minimum Y value from the columns var minimumX = Math.min.apply( Math, setX ), smallRow = 0; // Find index of smallest row, the first from the top for (var i=0, len = setX.length; i < len; i++) { if ( setX[i] === minimumX ) { smallRow = i; break; } } // position the brick var x = minimumX, y = this.masonryHorizontal.rowHeight * smallRow; this._pushPosition( $brick, x, y ); // apply setHeight to necessary columns var setWidth = minimumX + $brick.outerWidth(true), setSpan = this.masonryHorizontal.rows + 1 - len; for ( i=0; i < setSpan; i++ ) { this.masonryHorizontal.rowXs[ smallRow + i ] = setWidth; } }, _masonryHorizontalGetContainerSize : function() { var containerWidth = Math.max.apply( Math, this.masonryHorizontal.rowXs ); return { width: containerWidth }; }, _masonryHorizontalResizeChanged : function() { return this._checkIfSegmentsChanged(true); }, // ====================== fitColumns ====================== _fitColumnsReset : function() { this.fitColumns = { x : 0, y : 0, width : 0 }; }, _fitColumnsLayout : function( $elems ) { var instance = this, containerHeight = this.element.height(), props = this.fitColumns; $elems.each( function() { var $this = $(this), atomW = $this.outerWidth(true), atomH = $this.outerHeight(true); if ( props.y !== 0 && atomH + props.y > containerHeight ) { // if this element cannot fit in the current column props.x = props.width; props.y = 0; } // position the atom instance._pushPosition( $this, props.x, props.y ); props.width = Math.max( props.x + atomW, props.width ); props.y += atomH; }); }, _fitColumnsGetContainerSize : function () { return { width : this.fitColumns.width }; }, _fitColumnsResizeChanged : function() { return true; }, // ====================== cellsByColumn ====================== _cellsByColumnReset : function() { this.cellsByColumn = { index : 0 }; // get this.cellsByColumn.columnWidth this._getSegments(); // get this.cellsByColumn.rowHeight this._getSegments(true); }, _cellsByColumnLayout : function( $elems ) { var instance = this, props = this.cellsByColumn; $elems.each( function(){ var $this = $(this), col = ~~( props.index / props.rows ), row = props.index % props.rows, x = ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2, y = ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2; instance._pushPosition( $this, x, y ); props.index ++; }); }, _cellsByColumnGetContainerSize : function() { return { width : Math.ceil( this.$filteredAtoms.length / this.cellsByColumn.rows ) * this.cellsByColumn.columnWidth }; }, _cellsByColumnResizeChanged : function() { return this._checkIfSegmentsChanged(true); }, // ====================== straightAcross ====================== _straightAcrossReset : function() { this.straightAcross = { x : 0 }; }, _straightAcrossLayout : function( $elems ) { var instance = this; $elems.each( function( i ){ var $this = $(this); instance._pushPosition( $this, instance.straightAcross.x, 0 ); instance.straightAcross.x += $this.outerWidth(true); }); }, _straightAcrossGetContainerSize : function() { return { width : this.straightAcross.x }; }, _straightAcrossResizeChanged : function() { return true; } }; // ======================= imagesLoaded Plugin =============================== // A fork of http://gist.github.com/268257 by Paul Irish // mit license. paul irish. 2010. // webkit fix from Oren Solomianik. thx! $.fn.imagesLoaded = function(callback){ var elems = this.find('img'), len = elems.length, _this = this; if ( !elems.length ) { callback.call( this ); } elems.bind('load',function(){ if (--len <= 0){ callback.call( _this ); } }).each(function(){ // cached images don't fire load sometimes, so we reset src. if (this.complete || this.complete === undefined){ var src = this.src; // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f // data uri bypasses webkit log warning (thx doug jones) this.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; this.src = src; } }); return this; }; // helper function for logging errors // $.error breaks jQuery chaining var logError = function( message ) { if ( this.console ) { console.error( message ); } }; // ======================= Plugin bridge =============================== // leverages data method to either create or return $.Isotope constructor // A bit from jQuery UI // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js // A bit from jcarousel // https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js $.fn.isotope = function( options ) { if ( typeof options === 'string' ) { // call method var args = Array.prototype.slice.call( arguments, 1 ); this.each(function(){ var instance = $.data( this, 'isotope' ); if ( !instance ) { logError( "cannot call methods on isotope prior to initialization; " + "attempted to call method '" + options + "'" ); return; } if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) { logError( "no such method '" + options + "' for isotope instance" ); return; } // apply method instance[ options ].apply( instance, args ); }); } else { this.each(function() { var instance = $.data( this, 'isotope' ); if ( instance ) { // apply options & init instance.option( options ); instance._init(); } else { // initialize new instance $.data( this, 'isotope', new $.Isotope( options, this ) ); } }); } // return jQuery object // so plugin methods do not have to return this; }; })( window, jQuery );