Filter & sort magical layouts http://isotope.metafizzy.co
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1258 lines
37 KiB

14 years ago
/**
* Isotope v1.3.110604
14 years ago
* An exquisite jQuery plugin for magical layouts
* http://isotope.metafizzy.co
*
* Commercial use requires one-time license fee
* http://metafizzy.co/#licenses
*
14 years ago
* Copyright 2011 David DeSandro / Metafizzy
*/
(function( window, $, undefined ){
// ========================= getStyleProperty by kangax ===============================
// http://perfectionkills.com/feature-testing-css-properties/
var getStyleProperty = (function(){
var prefixes = ['Moz', 'Webkit', 'Khtml', 'O', 'Ms'];
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 = propName.charAt(0).toUpperCase() + propName.slice(1);
// test vendor specific properties
for (var i=0, l=prefixes.length; i<l; i++) {
prefixed = prefixes[i] + propName;
if (typeof style[prefixed] === 'string') {
return prefixed;
}
}
}
return getStyleProperty;
})();
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,
vendorCSSPrefixes = ' -o- -moz- -ms- -webkit- -khtml- '.split(' '),
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'),
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 <html> element.
docElement.className += ' ' + classes.join( ' ' );
return miniModernizr;
})();
}
// ========================= isoTransform ===============================
14 years ago
/**
* provides hooks for .css({ scale: value, translate: [x, y] })
14 years ago
* 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"] );
};
14 years ago
// ========================= 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;
}
};
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 );
for ( var optionName in opts ) {
this._updateOption( optionName );
}
}
},
// ====================== updaters ====================== //
// kind of like setters
// trigger _updateOptionName if it exists
_updateOption : function( optionName ) {
var updateOptionFn = '_update' + optionName.charAt(0).toUpperCase() + optionName.slice(1);
if ( this[ updateOptionFn ] ) {
this[ updateOptionFn ]();
}
},
_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 );
}
14 years ago
return $filteredAtoms;
14 years ago
},
14 years ago
// ====================== Sorting ======================
updateSortData : function( $atoms, isIncrementingElemCount ) {
var instance = this,
getSortData = this.options.getSortData,
14 years ago
$this, sortData;
$atoms.each(function(){
$this = $(this);
sortData = {};
// get value for sort data based on fn( $elem ) passed in
14 years ago
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 ++;
}
});
},
14 years ago
// used on all the filtered atoms
_sort : function() {
var sortBy = this.options.sortBy,
getSorter = this._getSorter,
14 years ago
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 );
14 years ago
},
_getSorter : function( elem, sortBy ) {
return $.data( elem, 'isotope-sort-data' )[ sortBy ];
},
// ====================== Layout Helpers ======================
14 years ago
_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 });
}
// 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;
// process styleQueue
$.each( this.styleQueue, function( i, obj ) {
obj.$el[ styleFn ]( obj.style, animOpts );
});
// clear out queue for next time
this.styleQueue = [];
// provide $elems as context for the callback
if ( callback ) {
callback.call( $elems );
}
this.isLaidOut = true;
14 years ago
},
14 years ago
resize : function() {
if ( this[ '_' + this.options.layoutMode + 'ResizeChanged' ]() ) {
this.reLayout();
}
14 years ago
},
reLayout : function( callback ) {
this[ '_' + this.options.layoutMode + 'Reset' ]();
this.layout( this.$filteredAtoms, callback );
},
// ====================== Convenience methods ======================
// 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
insert : function( $content, callback ) {
this.element.append( $content );
var instance = this;
this.addItems( $content, function( $newAtoms ) {
14 years ago
var $filteredAtoms = instance._filter( $newAtoms );
instance.$filteredAtoms = instance.$filteredAtoms.add( $filteredAtoms );
});
this._sort();
this.reLayout( callback );
},
// convienence method for working with Infinite Scroll
appended : function( $content, callback ) {
var instance = this;
this.addItems( $content, function( $newAtoms ){
instance.$filteredAtoms = instance.$filteredAtoms.add( $newAtoms );
14 years ago
instance.layout( $newAtoms, callback );
});
},
// 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();
},
_shuffleArray : function ( array ) {
var tmp, current, i = array.length;
if ( i ){
while(--i) {
current = ~~( Math.random() * (i + 1) );
tmp = array[current];
array[current] = array[i];
array[i] = tmp;
}
}
return array;
},
// HACKy should probably remove
shuffle : function( callback ) {
this.options.sortBy = 'shuffle';
this.$allAtoms = this._shuffleArray( this.$allAtoms );
this.$filteredAtoms = this._filter( this.$allAtoms );
this.reLayout( callback );
},
// 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 ======================
14 years ago
// 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',
UCSize = isRows ? 'Height' : 'Width',
segmentsName = isRows ? 'rows' : 'cols',
containerSize = this.element[ size ](),
segments,
segmentSize;
// i.e. options.masonry && options.masonry.columnWidth
segmentSize = this.options[ namespace ] && this.options[ namespace ][ measure ] ||
// or use the size of the first item
this.$filteredAtoms[ 'outer' + UCSize ](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 = [],
14 years ago
groupColY,
i;
// for each group potential horizontal position
14 years ago
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
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
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;
}
};
14 years ago
// ======================= 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 );