mirror of https://github.com/metafizzy/isotope
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.
1267 lines
38 KiB
1267 lines
38 KiB
/** |
|
* Isotope v1.3.110524 |
|
* 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 |
|
*/ |
|
|
|
(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 =============================== |
|
|
|
/** |
|
* 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 ) { |
|
var $elem = $(elem), |
|
// unpack current transform data |
|
data = $elem.data('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 |
|
$elem.data( 'isoTransform', data ); |
|
|
|
// set name to vendor specific property |
|
elem.style[ transformProp ] = valueFns; |
|
}; |
|
|
|
// ==================== scale =================== |
|
|
|
$.cssNumber.scale = true; |
|
|
|
$.cssHooks.scale = { |
|
set: function( elem, value ) { |
|
// 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 ) { |
|
|
|
// all this would be for public use |
|
// properly parsing strings and whatnot |
|
// 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; |
|
} |
|
}; |
|
|
|
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( key, value ){ |
|
// get/change options AFTER initialization: |
|
|
|
// signature: $('#foo').bar({ cool:false }); |
|
if ( $.isPlainObject( key ) ){ |
|
this.options = $.extend(true, this.options, key); |
|
var optionName; |
|
for ( optionName in key ) { |
|
this._updateOption( optionName ); |
|
} |
|
|
|
// signature: $('#foo').bar('option', 'baz', false); |
|
} else { |
|
this.options[ key ] = value; |
|
this._updateOption( key ); |
|
} |
|
}, |
|
|
|
// ====================== 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 ); |
|
} |
|
|
|
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 |
|
$this.data( '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 $(elem).data('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 }); |
|
} |
|
|
|
// 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; |
|
}, |
|
|
|
|
|
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 ====================== |
|
|
|
// 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 ) { |
|
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 ); |
|
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 ====================== |
|
|
|
// 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', |
|
segments, |
|
segmentSize; |
|
|
|
this[ size ] = this.element[ size ](); |
|
|
|
// 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 |
|
this[ size ]; |
|
|
|
segments = Math.floor( this[ size ] / 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; |
|
$elems.each(function(){ |
|
var $this = $(this), |
|
//how many columns does this brick span |
|
colSpan = Math.ceil( $this.outerWidth(true) / instance.masonry.columnWidth ); |
|
colSpan = Math.min( colSpan, instance.masonry.cols ); |
|
|
|
if ( colSpan === 1 ) { |
|
// if brick spans only one column, just like singleMode |
|
instance._masonryPlaceBrick( $this, instance.masonry.colYs ); |
|
} else { |
|
// brick spans more than one column |
|
// how many different places could this brick fit horizontally |
|
var groupCount = instance.masonry.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 = instance.masonry.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 ), |
|
setHeight = minimumY + $brick.outerHeight(true), |
|
i = setY.length, |
|
shortCol = i, |
|
setSpan = this.masonry.cols + 1 - i, |
|
x, y ; |
|
// Which column has the minY value, closest to the left |
|
while (i--) { |
|
if ( setY[i] === minimumY ) { |
|
shortCol = i; |
|
} |
|
} |
|
|
|
// position the brick |
|
x = this.masonry.columnWidth * shortCol; |
|
y = minimumY; |
|
this._pushPosition( $brick, x, y ); |
|
|
|
// apply setHeight to necessary columns |
|
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 ) { |
|
this.width = this.element.width(); |
|
var instance = this; |
|
|
|
$elems.each( function() { |
|
var $this = $(this), |
|
atomW = $this.outerWidth(true), |
|
atomH = $this.outerHeight(true), |
|
x, y; |
|
|
|
if ( instance.fitRows.x !== 0 && atomW + instance.fitRows.x > instance.width ) { |
|
// if this element cannot fit in the current row |
|
instance.fitRows.x = 0; |
|
instance.fitRows.y = instance.fitRows.height; |
|
} |
|
|
|
// position the atom |
|
x = instance.fitRows.x; |
|
y = instance.fitRows.y; |
|
instance._pushPosition( $this, x, y ); |
|
|
|
instance.fitRows.height = Math.max( instance.fitRows.y + atomH, instance.fitRows.height ); |
|
instance.fitRows.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; |
|
$elems.each(function(){ |
|
var $this = $(this), |
|
//how many rows does this brick span |
|
rowSpan = Math.ceil( $this.outerHeight(true) / instance.masonryHorizontal.rowHeight ); |
|
rowSpan = Math.min( rowSpan, instance.masonryHorizontal.rows ); |
|
|
|
if ( rowSpan === 1 ) { |
|
// if brick spans only one column, just like singleMode |
|
instance._masonryHorizontalPlaceBrick( $this, instance.masonryHorizontal.rowXs ); |
|
} else { |
|
// brick spans more than one row |
|
// how many different places could this brick fit horizontally |
|
var groupCount = instance.masonryHorizontal.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 = instance.masonryHorizontal.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 ) { |
|
// here, `this` refers to a child element or "brick" |
|
// get the minimum Y value from the columns |
|
var minimumX = Math.min.apply( Math, setX ), |
|
setWidth = minimumX + $brick.outerWidth(true), |
|
i = setX.length, |
|
smallRow = i, |
|
setSpan = this.masonryHorizontal.rows + 1 - i, |
|
x, y ; |
|
// Which column has the minY value, closest to the left |
|
while (i--) { |
|
if ( setX[i] === minimumX ) { |
|
smallRow = i; |
|
} |
|
} |
|
|
|
// position the brick |
|
x = minimumX; |
|
y = this.masonryHorizontal.rowHeight * smallRow; |
|
this._pushPosition( $brick, x, y ); |
|
|
|
// apply setHeight to necessary columns |
|
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; |
|
this.height = this.element.height(); |
|
$elems.each( function() { |
|
var $this = $(this), |
|
atomW = $this.outerWidth(true), |
|
atomH = $this.outerHeight(true), |
|
x, y; |
|
|
|
if ( instance.fitColumns.y !== 0 && atomH + instance.fitColumns.y > instance.height ) { |
|
// if this element cannot fit in the current column |
|
instance.fitColumns.x = instance.fitColumns.width; |
|
instance.fitColumns.y = 0; |
|
} |
|
|
|
// position the atom |
|
x = instance.fitColumns.x; |
|
y = instance.fitColumns.y; |
|
instance._pushPosition( $this, x, y ); |
|
|
|
instance.fitColumns.width = Math.max( instance.fitColumns.x + atomW, instance.fitColumns.width ); |
|
instance.fitColumns.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; |
|
}; |
|
|
|
|
|
|
|
// ======================= 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 ) { |
|
return $.error( "cannot call methods on isotope prior to initialization; " + |
|
"attempted to call method '" + options + "'" ); |
|
} |
|
if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) { |
|
return $.error( "no such method '" + options + "' for isotope instance" ); |
|
} |
|
// 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 ); |