|
|
/** * Isotope v1.5.25 * An exquisite jQuery plugin for magical layouts * http://isotope.metafizzy.co
* * Commercial use requires one-time license fee * http://metafizzy.co/#licenses
* * Copyright 2012 David DeSandro / Metafizzy */
/*jshint asi: true, browser: true, curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, strict: true, undef: true *//*global jQuery: false */
(function( window, $, undefined ){
'use strict';
// get global vars
var document = window.document; var Modernizr = window.Modernizr;
// helper function
var capitalize = function( str ) { return str.charAt(0).toUpperCase() + str.slice(1); };
// ========================= getStyleProperty by kangax ===============================
// http://perfectionkills.com/feature-testing-css-properties/
var prefixes = 'Moz Webkit O Ms'.split(' ');
var getStyleProperty = function( propName ) { var style = document.documentElement.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'), transitionProp = getStyleProperty('transitionProperty');
// ========================= 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 tests = { csstransforms: function() { return !!transformProp; },
csstransforms3d: function() { var test = !!getStyleProperty('perspective'); // double check for Chrome's false positive
if ( test ) { var vendorCSSPrefixes = ' -o- -moz- -ms- -webkit- -khtml- '.split(' '), mediaQuery = '@media (' + vendorCSSPrefixes.join('transform-3d),(') + 'modernizr)', $style = $('<style>' + mediaQuery + '{#modernizr{height:3px}}' + '</style>') .appendTo('head'), $div = $('<div id="modernizr" />').appendTo('html');
test = $div.height() === 3;
$div.remove(); $style.remove(); } return test; },
csstransitions: function() { return !!transitionProp; } };
var testName;
if ( Modernizr ) { // if there's a previous Modernzir, check if there are necessary tests
for ( testName in tests) { if ( !Modernizr.hasOwnProperty( testName ) ) { // if test hasn't been run, use addTest to run it
Modernizr.addTest( testName, tests[ testName ] ); } } } else { // or create new mini Modernizr that just has the 3 tests
Modernizr = window.Modernizr = { _version : '1.6ish: miniModernizr for Isotope' };
var classes = ' '; var result;
// Run through tests
for ( testName in tests) { result = tests[ testName ](); Modernizr[ testName ] = result; classes += ' ' + ( result ? '' : 'no-' ) + testName; }
// Add the new classes to the <html> element.
$('html').addClass( classes ); }
// ========================= 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 ]; } };
}
// ========================= get transition-end event ===============================
var transitionEndEvent, transitionDurProp;
if ( Modernizr.csstransitions ) { transitionEndEvent = { WebkitTransitionProperty: 'webkitTransitionEnd', // webkit
MozTransitionProperty: 'transitionend', OTransitionProperty: 'oTransitionEnd otransitionend', transitionProperty: 'transitionend' }[ transitionProp ];
transitionDurProp = getStyleProperty('transitionDuration'); }
// ========================= smartresize ===============================
/* * 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, dispatchMethod = $.event.handle ? 'handle' : 'dispatch', 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() { $event[ dispatchMethod ].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, callback ){ this.element = $( element );
this._create( options ); this._init( callback ); };
// styles of container element we want to keep track of
var isoContainerStyles = [ 'width', 'height' ];
var $window = $(window);
$.Isotope.settings = { resizable: true, layoutMode : 'masonry', containerClass : 'isotope', itemClass : 'isotope-item', hiddenClass : 'isotope-hidden', hiddenStyle: { opacity: 0, scale: 0.001 }, visibleStyle: { opacity: 1, scale: 1 }, containerStyle: { position: 'relative', overflow: 'hidden' }, animationEngine: '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( {}, $.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 = {}; // keep track of container styles
var containerStyles = isoContainerStyles.slice(0); for ( var prop in this.options.containerStyle ) { containerStyles.push( prop ); } for ( var i=0, len = containerStyles.length; i < len; i++ ) { prop = containerStyles[i]; this.originalStyle[ prop ] = elemStyle[ prop ] || ''; } // apply container style from options
this.element.css( this.options.containerStyle );
this._updateAnimationEngine(); this._updateUsingTransforms();
// sorting
var originalOrderSorter = { 'original-order' : function( $elem, instance ) { instance.elemCount ++; 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
this.offset = { left: parseInt( ( this.element.css('padding-left') || 0 ), 10 ), top: parseInt( ( this.element.css('padding-top') || 0 ), 10 ) };
// 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(); }); }
// dismiss all click events from hidden events
this.element.delegate( '.' + this.options.hiddenClass, 'click', function(){ return false; });
},
_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' };
// filter out text nodes
$atoms = $atoms.filter( function( i, atom ) { return atom.nodeType === 1; });
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, ''); var isUsingJQueryAnimation; // set applyStyleFnName
switch ( animationEngine ) { case 'css' : case 'none' : isUsingJQueryAnimation = false; break; case 'jquery' : isUsingJQueryAnimation = true; break; default : // best available
isUsingJQueryAnimation = !Modernizr.csstransitions; } this.isUsingJQueryAnimation = isUsingJQueryAnimation; this._updateUsingTransforms(); },
_updateTransformsEnabled : function() { this._updateUsingTransforms(); },
_updateUsingTransforms : function() { var usingTransforms = this.usingTransforms = this.options.transformsEnabled && Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation;
// prevent scales when transforms are disabled
if ( !usingTransforms ) { delete this.options.hiddenStyle.scale; delete this.options.visibleStyle.scale; }
this.getPositionStyles = usingTransforms ? this._translate : this._positionAbs; },
// ====================== Filtering ======================
_filter : function( $atoms ) { var filter = this.options.filter === '' ? '*' : this.options.filter;
if ( !filter ) { return $atoms; }
var hiddenClass = this.options.hiddenClass, hiddenSelector = '.' + hiddenClass, $hiddenAtoms = $atoms.filter( hiddenSelector ), $atomsToShow = $hiddenAtoms;
if ( filter !== '*' ) { $atomsToShow = $hiddenAtoms.filter( filter ); var $atomsToHide = $atoms.not( hiddenSelector ).not( filter ).addClass( hiddenClass ); this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle }); }
this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle }); $atomsToShow.removeClass( hiddenClass );
return $atoms.filter( filter ); },
// ====================== 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 ) { if ( !isIncrementingElemCount && key === 'original-order' ) { // keep original order original
sortData[ key ] = $.data( this, 'isotope-sort-data' )[ key ]; } else { sortData[ key ] = getSortData[ key ]( $this, instance ); } } // apply sort data to element
$.data( this, 'isotope-sort-data', sortData ); }); },
// 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 = Math.round( x + this.offset.left ); y = Math.round( 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( $elems, callback );
this.isLaidOut = true; },
_processStyleQueue : function( $elems, callback ) { // 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, onLayout = this.options.onLayout, objStyleFn, processor, triggerCallbackNow, callbackFn;
// default styleQueue processor, may be overwritten down below
processor = function( i, obj ) { obj.$el[ styleFn ]( obj.style, animOpts ); };
if ( this._isInserting && this.isUsingJQueryAnimation ) { // if using styleQueue to insert items
processor = function( i, obj ) { // only animate if it not being inserted
objStyleFn = obj.$el.hasClass('no-transition') ? 'css' : styleFn; obj.$el[ objStyleFn ]( obj.style, animOpts ); };
} else if ( callback || onLayout || animOpts.complete ) { // has callback
var isCallbackTriggered = false, // array of possible callbacks to trigger
callbacks = [ callback, onLayout, animOpts.complete ], instance = this; triggerCallbackNow = true; // trigger callback only once
callbackFn = function() { if ( isCallbackTriggered ) { return; } var hollaback; for (var i=0, len = callbacks.length; i < len; i++) { hollaback = callbacks[i]; if ( typeof hollaback === 'function' ) { hollaback.call( instance.element, $elems, instance ); } } isCallbackTriggered = true; };
if ( this.isUsingJQueryAnimation && styleFn === 'animate' ) { // add callback to animation options
animOpts.complete = callbackFn; triggerCallbackNow = false;
} else if ( Modernizr.csstransitions ) { // detect if first item has transition
var i = 0, firstItem = this.styleQueue[0], testElem = firstItem && firstItem.$el, styleObj; // get first non-empty jQ object
while ( !testElem || !testElem.length ) { styleObj = this.styleQueue[ i++ ]; // HACK: sometimes styleQueue[i] is undefined
if ( !styleObj ) { return; } testElem = styleObj.$el; } // get transition duration of the first element in that object
// yeah, this is inexact
var duration = parseFloat( getComputedStyle( testElem[0] )[ transitionDurProp ] ); if ( duration > 0 ) { processor = function( i, obj ) { obj.$el[ styleFn ]( obj.style, animOpts ) // trigger callback at transition end
.one( transitionEndEvent, callbackFn ); }; triggerCallbackNow = false; } } }
// process styleQueue
$.each( this.styleQueue, processor );
if ( triggerCallbackNow ) { callbackFn(); }
// 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
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 ); 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._isInserting = false; instance._processStyleQueue( $newAtoms, callback ); }, 10 ); },
// gathers all atoms
reloadItems : function() { this.$allAtoms = this._getAtoms( this.element.children() ); },
// removes elements from Isotope widget
remove: function( $content, callback ) { // remove elements immediately from Isotope instance
this.$allAtoms = this.$allAtoms.not( $content ); this.$filteredAtoms = this.$filteredAtoms.not( $content ); // remove() as a callback, for after transition / animation
var instance = this; var removeContent = function() { $content.remove(); if ( callback ) { callback.call( instance.element ); } };
if ( $content.filter( ':not(.' + this.options.hiddenClass + ')' ).length ) { // if any non-hidden content needs to be removed
this.styleQueue.push({ $el: $content, style: this.options.hiddenStyle }); this._sort(); this.reLayout( removeContent ); } else { // remove it now
removeContent(); }
},
shuffle : function( callback ) { this.updateSortData( this.$allAtoms ); this.options.sortBy = 'random'; this._sort(); this.reLayout( callback ); },
// destroys widget, returns elements and container back (close) to original style
destroy : function() {
var usingTransforms = this.usingTransforms; var options = this.options;
this.$allAtoms .removeClass( options.hiddenClass + ' ' + options.itemClass ) .each(function(){ var style = this.style; style.position = ''; style.top = ''; style.left = ''; style.opacity = ''; if ( usingTransforms ) { style[ transformProp ] = ''; } });
// re-apply saved container styles
var elemStyle = this.element[0].style; for ( var prop in this.originalStyle ) { elemStyle[ prop ] = this.originalStyle[ prop ]; }
this.element .unbind('.isotope') .undelegate( '.' + options.hiddenClass, 'click' ) .removeClass( 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 = Math.floor( 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 = Math.floor( 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 ===============================
/*! * jQuery imagesLoaded plugin v1.1.0 * http://github.com/desandro/imagesloaded
* * MIT License. by Paul Irish et al. */
// $('#my-container').imagesLoaded(myFunction)
// or
// $('img').imagesLoaded(myFunction)
// execute a callback when all images have loaded.
// needed because .load() doesn't work on cached images
// callback function gets image collection as argument
// `this` is the container
$.fn.imagesLoaded = function( callback ) { var $this = this, $images = $this.find('img').add( $this.filter('img') ), len = $images.length, blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', loaded = [];
function triggerCallback() { callback.call( $this, $images ); }
function imgLoaded( event ) { var img = event.target; if ( img.src !== blank && $.inArray( img, loaded ) === -1 ){ loaded.push( img ); if ( --len <= 0 ){ setTimeout( triggerCallback ); $images.unbind( '.imagesLoaded', imgLoaded ); } } }
// if no images, trigger immediately
if ( !len ) { triggerCallback(); }
$images.bind( 'load.imagesLoaded error.imagesLoaded', imgLoaded ).each( function() { // cached images don't fire load sometimes, so we reset src.
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 = blank; this.src = src; });
return $this; };
// helper function for logging errors
// $.error breaks jQuery chaining
var logError = function( message ) { if ( window.console ) { window.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, callback ) { 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( callback ); } else { // initialize new instance
$.data( this, 'isotope', new $.Isotope( options, this, callback ) ); } }); } // return jQuery object
// so plugin methods do not have to
return this; };
})( window, jQuery );
|