|
|
|
/**
|
|
|
|
* Isotope v1.5.01
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*jshint curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, undef: true */
|
|
|
|
/*global Modernizr: true, jQuery: true */
|
|
|
|
|
|
|
|
(function( window, $, undefined ){
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// 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 Khtml 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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if ( window.Modernizr ) {
|
|
|
|
// if there's a previous Modernzir, check if there are necessary tests
|
|
|
|
for ( var 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
|
|
|
|
window.Modernizr = (function(){
|
|
|
|
|
|
|
|
var miniModernizr = {
|
|
|
|
_version : '1.6ish: miniModernizr for Isotope'
|
|
|
|
},
|
|
|
|
classes = ' ',
|
|
|
|
result, testName;
|
|
|
|
|
|
|
|
// Run through tests
|
|
|
|
for ( testName in tests) {
|
|
|
|
result = tests[ testName ]();
|
|
|
|
miniModernizr[ testName ] = result;
|
|
|
|
classes += ' ' + ( result ? '' : 'no-' ) + testName;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the new classes to the <html> element.
|
|
|
|
$('html').addClass( classes );
|
|
|
|
|
|
|
|
return miniModernizr;
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========================= isoTransform ===============================
|
|
|
|
|
|
|
|
/**
|
|
|
|
* provides hooks for .css({ scale: value, translate: [x, y] })
|
|
|
|
* Progressively enhanced CSS transforms
|
|
|
|
* Uses hardware accelerated 3D transforms for Safari
|
|
|
|
* or falls back to 2D transforms.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ( Modernizr.csstransforms ) {
|
|
|
|
|
|
|
|
// i.e. transformFnNotations.scale(0.5) >> 'scale3d( 0.5, 0.5, 1)'
|
|
|
|
var transformFnNotations = Modernizr.csstransforms3d ?
|
|
|
|
{ // 3D transform functions
|
|
|
|
translate : function ( position ) {
|
|
|
|
return 'translate3d(' + position[0] + 'px, ' + position[1] + 'px, 0) ';
|
|
|
|
},
|
|
|
|
scale : function ( scale ) {
|
|
|
|
return 'scale3d(' + scale + ', ' + scale + ', 1) ';
|
|
|
|
}
|
|
|
|
} :
|
|
|
|
{ // 2D transform functions
|
|
|
|
translate : function ( position ) {
|
|
|
|
return 'translate(' + position[0] + 'px, ' + position[1] + 'px) ';
|
|
|
|
},
|
|
|
|
scale : function ( scale ) {
|
|
|
|
return 'scale(' + scale + ') ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
var setIsoTransform = function ( elem, name, value ) {
|
|
|
|
// unpack current transform data
|
|
|
|
var data = $.data( elem, 'isoTransform' ) || {},
|
|
|
|
newData = {},
|
|
|
|
fnName,
|
|
|
|
transformObj = {},
|
|
|
|
transformValue;
|
|
|
|
|
|
|
|
// i.e. newData.scale = 0.5
|
|
|
|
newData[ name ] = value;
|
|
|
|
// extend new value over current data
|
|
|
|
$.extend( data, newData );
|
|
|
|
|
|
|
|
for ( fnName in data ) {
|
|
|
|
transformValue = data[ fnName ];
|
|
|
|
transformObj[ fnName ] = transformFnNotations[ fnName ]( transformValue );
|
|
|
|
}
|
|
|
|
|
|
|
|
// get proper order
|
|
|
|
// ideally, we could loop through this give an array, but since we only have
|
|
|
|
// a couple transforms we're keeping track of, we'll do it like so
|
|
|
|
var translateFn = transformObj.translate || '',
|
|
|
|
scaleFn = transformObj.scale || '',
|
|
|
|
// sorting so translate always comes first
|
|
|
|
valueFns = translateFn + scaleFn;
|
|
|
|
|
|
|
|
// set data back in elem
|
|
|
|
$.data( elem, 'isoTransform', data );
|
|
|
|
|
|
|
|
// set name to vendor specific property
|
|
|
|
elem.style[ transformProp ] = valueFns;
|
|
|
|
};
|
|
|
|
|
|
|
|
// ==================== scale ===================
|
|
|
|
|
|
|
|
$.cssNumber.scale = true;
|
|
|
|
|
|
|
|
$.cssHooks.scale = {
|
|
|
|
set: function( elem, value ) {
|
|
|
|
// uncomment this bit if you want to properly parse strings
|
|
|
|
// if ( typeof value === 'string' ) {
|
|
|
|
// value = parseFloat( value );
|
|
|
|
// }
|
|
|
|
setIsoTransform( elem, 'scale', value );
|
|
|
|
},
|
|
|
|
get: function( elem, computed ) {
|
|
|
|
var transform = $.data( elem, 'isoTransform' );
|
|
|
|
return transform && transform.scale ? transform.scale : 1;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
$.fx.step.scale = function( fx ) {
|
|
|
|
$.cssHooks.scale.set( fx.elem, fx.now+fx.unit );
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== translate ===================
|
|
|
|
|
|
|
|
$.cssNumber.translate = true;
|
|
|
|
|
|
|
|
$.cssHooks.translate = {
|
|
|
|
set: function( elem, value ) {
|
|
|
|
|
|
|
|
// uncomment this bit if you want to properly parse strings
|
|
|
|
// if ( typeof value === 'string' ) {
|
|
|
|
// value = value.split(' ');
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// var i, val;
|
|
|
|
// for ( i = 0; i < 2; i++ ) {
|
|
|
|
// val = value[i];
|
|
|
|
// if ( typeof val === 'string' ) {
|
|
|
|
// val = parseInt( val );
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
setIsoTransform( elem, 'translate', value );
|
|
|
|
},
|
|
|
|
|
|
|
|
get: function( elem, computed ) {
|
|
|
|
var transform = $.data( elem, 'isoTransform' );
|
|
|
|
return transform && transform.translate ? transform.translate : [ 0, 0 ];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// ========================= get transition-end event ===============================
|
|
|
|
var transitionEndEvent, transitionDurProp;
|
|
|
|
|
|
|
|
if ( Modernizr.csstransitions ) {
|
|
|
|
transitionEndEvent = {
|
|
|
|
WebkitTransitionProperty: 'webkitTransitionEnd', // webkit
|
|
|
|
MozTransitionProperty: 'transitionend',
|
|
|
|
OTransitionProperty: '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,
|
|
|
|
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, callback ){
|
|
|
|
this.element = $( element );
|
|
|
|
|
|
|
|
this._create( options );
|
|
|
|
this._init( callback );
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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( {}, $.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 ] || '';
|
|
|
|
}
|
|
|
|
|
|
|
|
this.element.css({
|
|
|
|
overflow : 'hidden',
|
|
|
|
position : 'relative'
|
|
|
|
});
|
|
|
|
|
|
|
|
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
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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' };
|
|
|
|
|
|
|
|
if ( this.usingTransforms ) {
|
|
|
|
atomStyle.left = 0;
|
|
|
|
atomStyle.top = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
$atoms.css( atomStyle ).addClass( this.options.itemClass );
|
|
|
|
|
|
|
|
this.updateSortData( $atoms, true );
|
|
|
|
|
|
|
|
return $atoms;
|
|
|
|
},
|
|
|
|
|
|
|
|
// _init fires when your instance is first created
|
|
|
|
// (from the constructor above), and when you
|
|
|
|
// attempt to initialize the widget again (by the bridge)
|
|
|
|
// after it has already been initialized.
|
|
|
|
_init : function( callback ) {
|
|
|
|
|
|
|
|
this.$filteredAtoms = this._filter( this.$allAtoms );
|
|
|
|
this._sort();
|
|
|
|
this.reLayout( callback );
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
option : function( opts ){
|
|
|
|
// change options AFTER initialization:
|
|
|
|
// signature: $('#foo').bar({ cool:false });
|
|
|
|
if ( $.isPlainObject( opts ) ){
|
|
|
|
this.options = $.extend( true, this.options, opts );
|
|
|
|
|
|
|
|
// trigger _updateOptionName if it exists
|
|
|
|
var updateOptionFn;
|
|
|
|
for ( var optionName in opts ) {
|
|
|
|
updateOptionFn = '_update' + capitalize( optionName );
|
|
|
|
if ( this[ updateOptionFn ] ) {
|
|
|
|
this[ updateOptionFn ]();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// ====================== updaters ====================== //
|
|
|
|
// kind of like setters
|
|
|
|
|
|
|
|
_updateAnimationEngine : function() {
|
|
|
|
var animationEngine = this.options.animationEngine.toLowerCase().replace( /[ _\-]/g, '');
|
|
|
|
// set applyStyleFnName
|
|
|
|
switch ( animationEngine ) {
|
|
|
|
case 'css' :
|
|
|
|
case 'none' :
|
|
|
|
this.isUsingJQueryAnimation = false;
|
|
|
|
break;
|
|
|
|
case 'jquery' :
|
|
|
|
this.isUsingJQueryAnimation = true;
|
|
|
|
break;
|
|
|
|
default : // best available
|
|
|
|
this.isUsingJQueryAnimation = !Modernizr.csstransitions;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._updateUsingTransforms();
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateTransformsEnabled : function() {
|
|
|
|
this._updateUsingTransforms();
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateUsingTransforms : function() {
|
|
|
|
this.usingTransforms = this.options.transformsEnabled && Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation;
|
|
|
|
|
|
|
|
this.getPositionStyles = this.usingTransforms ? this._translate : this._positionAbs;
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ====================== Filtering ======================
|
|
|
|
|
|
|
|
_filter : function( $atoms ) {
|
|
|
|
var 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 += this.offset.left;
|
|
|
|
y += this.offset.top;
|
|
|
|
var position = this.getPositionStyles( x, y );
|
|
|
|
this.styleQueue.push({ $el: $elem, style: position });
|
|
|
|
if ( this.options.itemPositionDataEnabled ) {
|
|
|
|
$elem.data('isotope-item-position', {x: x, y: y} );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ====================== General Layout ======================
|
|
|
|
|
|
|
|
// used on collection of atoms (should be filtered, and sorted before )
|
|
|
|
// accepts atoms-to-be-laid-out to start with
|
|
|
|
layout : function( $elems, callback ) {
|
|
|
|
|
|
|
|
var layoutMode = this.options.layoutMode;
|
|
|
|
|
|
|
|
// layout logic
|
|
|
|
this[ '_' + layoutMode + 'Layout' ]( $elems );
|
|
|
|
|
|
|
|
// set the size of the container
|
|
|
|
if ( this.options.resizesContainer ) {
|
|
|
|
var containerStyle = this[ '_' + layoutMode + 'GetContainerSize' ]();
|
|
|
|
this.styleQueue.push({ $el: this.element, style: containerStyle });
|
|
|
|
}
|
|
|
|
|
|
|
|
this._processStyleQueue( $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,
|
|
|
|
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 ) {
|
|
|
|
// has callback
|
|
|
|
var isCallbackTriggered = false,
|
|
|
|
instance = this;
|
|
|
|
triggerCallbackNow = true;
|
|
|
|
// trigger callback only once
|
|
|
|
callbackFn = function() {
|
|
|
|
if ( isCallbackTriggered ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
callback.call( instance.element, $elems );
|
|
|
|
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,
|
|
|
|
testElem = this.styleQueue[0].$el,
|
|
|
|
styleObj;
|
|
|
|
// get first non-empty jQ object
|
|
|
|
while ( !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, true );
|
|
|
|
instance._addHideAppended( $newFilteredAtoms );
|
|
|
|
instance._sort();
|
|
|
|
instance.reLayout();
|
|
|
|
instance._revealAppended( $newFilteredAtoms, callback );
|
|
|
|
});
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
// convienence method for working with Infinite Scroll
|
|
|
|
appended : function( $content, callback ) {
|
|
|
|
var instance = this;
|
|
|
|
this.addItems( $content, function( $newAtoms ) {
|
|
|
|
instance._addHideAppended( $newAtoms );
|
|
|
|
instance.layout( $newAtoms );
|
|
|
|
instance._revealAppended( $newAtoms, callback );
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// adds new atoms, then hides them before positioning
|
|
|
|
_addHideAppended : function( $newAtoms ) {
|
|
|
|
this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms );
|
|
|
|
$newAtoms.addClass('no-transition');
|
|
|
|
|
|
|
|
this._isInserting = true;
|
|
|
|
|
|
|
|
// apply hidden styles
|
|
|
|
this.styleQueue.push({ $el: $newAtoms, style: this.options.hiddenStyle });
|
|
|
|
},
|
|
|
|
|
|
|
|
// sets visible style on new atoms
|
|
|
|
_revealAppended : function( $newAtoms, callback ) {
|
|
|
|
var instance = this;
|
|
|
|
// apply visible style after a sec
|
|
|
|
setTimeout( function() {
|
|
|
|
// enable animation
|
|
|
|
$newAtoms.removeClass('no-transition');
|
|
|
|
// reveal newly inserted filtered elements
|
|
|
|
instance.styleQueue.push({ $el: $newAtoms, style: instance.options.visibleStyle });
|
|
|
|
instance._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 ) {
|
|
|
|
|
|
|
|
this.$allAtoms = this.$allAtoms.not( $content );
|
|
|
|
this.$filteredAtoms = this.$filteredAtoms.not( $content );
|
|
|
|
|
|
|
|
$content.remove();
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
this.$allAtoms
|
|
|
|
.removeClass( this.options.hiddenClass + ' ' + this.options.itemClass )
|
|
|
|
.each(function(){
|
|
|
|
this.style.position = '';
|
|
|
|
this.style.top = '';
|
|
|
|
this.style.left = '';
|
|
|
|
this.style.opacity = '';
|
|
|
|
if ( usingTransforms ) {
|
|
|
|
this.style[ transformProp ] = '';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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')
|
|
|
|
.undelegate( '.' + this.options.hiddenClass, 'click' )
|
|
|
|
.removeClass( this.options.containerClass )
|
|
|
|
.removeData('isotope');
|
|
|
|
|
|
|
|
$(window).unbind('.isotope');
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ====================== LAYOUTS ======================
|
|
|
|
|
|
|
|
// calculates number of rows or columns
|
|
|
|
// requires columnWidth or rowHeight to be set on namespaced object
|
|
|
|
// i.e. this.masonry.columnWidth = 200
|
|
|
|
_getSegments : function( isRows ) {
|
|
|
|
var namespace = this.options.layoutMode,
|
|
|
|
measure = isRows ? 'rowHeight' : 'columnWidth',
|
|
|
|
size = isRows ? 'height' : 'width',
|
|
|
|
segmentsName = isRows ? 'rows' : 'cols',
|
|
|
|
containerSize = this.element[ size ](),
|
|
|
|
segments,
|
|
|
|
// i.e. options.masonry && options.masonry.columnWidth
|
|
|
|
segmentSize = this.options[ namespace ] && this.options[ namespace ][ measure ] ||
|
|
|
|
// or use the size of the first item, i.e. outerWidth
|
|
|
|
this.$filteredAtoms[ 'outer' + capitalize(size) ](true) ||
|
|
|
|
// if there's no items, use size of container
|
|
|
|
containerSize;
|
|
|
|
|
|
|
|
segments = Math.floor( containerSize / segmentSize );
|
|
|
|
segments = Math.max( segments, 1 );
|
|
|
|
|
|
|
|
// i.e. this.masonry.cols = ....
|
|
|
|
this[ namespace ][ segmentsName ] = segments;
|
|
|
|
// i.e. this.masonry.columnWidth = ...
|
|
|
|
this[ namespace ][ measure ] = segmentSize;
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
_checkIfSegmentsChanged : function( isRows ) {
|
|
|
|
var namespace = this.options.layoutMode,
|
|
|
|
segmentsName = isRows ? 'rows' : 'cols',
|
|
|
|
prevSegments = this[ namespace ][ segmentsName ];
|
|
|
|
// update cols/rows
|
|
|
|
this._getSegments( isRows );
|
|
|
|
// return if updated cols/rows is not equal to previous
|
|
|
|
return ( this[ namespace ][ segmentsName ] !== prevSegments );
|
|
|
|
},
|
|
|
|
|
|
|
|
// ====================== Masonry ======================
|
|
|
|
|
|
|
|
_masonryReset : function() {
|
|
|
|
// layout-specific props
|
|
|
|
this.masonry = {};
|
|
|
|
// FIXME shouldn't have to call this again
|
|
|
|
this._getSegments();
|
|
|
|
var i = this.masonry.cols;
|
|
|
|
this.masonry.colYs = [];
|
|
|
|
while (i--) {
|
|
|
|
this.masonry.colYs.push( 0 );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_masonryLayout : function( $elems ) {
|
|
|
|
var instance = this,
|
|
|
|
props = instance.masonry;
|
|
|
|
$elems.each(function(){
|
|
|
|
var $this = $(this),
|
|
|
|
//how many columns does this brick span
|
|
|
|
colSpan = Math.ceil( $this.outerWidth(true) / props.columnWidth );
|
|
|
|
colSpan = Math.min( colSpan, props.cols );
|
|
|
|
|
|
|
|
if ( colSpan === 1 ) {
|
|
|
|
// if brick spans only one column, just like singleMode
|
|
|
|
instance._masonryPlaceBrick( $this, props.colYs );
|
|
|
|
} else {
|
|
|
|
// brick spans more than one column
|
|
|
|
// how many different places could this brick fit horizontally
|
|
|
|
var groupCount = props.cols + 1 - colSpan,
|
|
|
|
groupY = [],
|
|
|
|
groupColY,
|
|
|
|
i;
|
|
|
|
|
|
|
|
// for each group potential horizontal position
|
|
|
|
for ( i=0; i < groupCount; i++ ) {
|
|
|
|
// make an array of colY values for that one group
|
|
|
|
groupColY = props.colYs.slice( i, i+colSpan );
|
|
|
|
// and get the max value of the array
|
|
|
|
groupY[i] = Math.max.apply( Math, groupColY );
|
|
|
|
}
|
|
|
|
|
|
|
|
instance._masonryPlaceBrick( $this, groupY );
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// worker method that places brick in the columnSet
|
|
|
|
// with the the minY
|
|
|
|
_masonryPlaceBrick : function( $brick, setY ) {
|
|
|
|
// get the minimum Y value from the columns
|
|
|
|
var minimumY = Math.min.apply( Math, setY ),
|
|
|
|
shortCol = 0;
|
|
|
|
|
|
|
|
// Find index of short column, the first from the left
|
|
|
|
for (var i=0, len = setY.length; i < len; i++) {
|
|
|
|
if ( setY[i] === minimumY ) {
|
|
|
|
shortCol = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// position the brick
|
|
|
|
var x = this.masonry.columnWidth * shortCol,
|
|
|
|
y = minimumY;
|
|
|
|
this._pushPosition( $brick, x, y );
|
|
|
|
|
|
|
|
// apply setHeight to necessary columns
|
|
|
|
var setHeight = minimumY + $brick.outerHeight(true),
|
|
|
|
setSpan = this.masonry.cols + 1 - len;
|
|
|
|
for ( i=0; i < setSpan; i++ ) {
|
|
|
|
this.masonry.colYs[ shortCol + i ] = setHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
_masonryGetContainerSize : function() {
|
|
|
|
var containerHeight = Math.max.apply( Math, this.masonry.colYs );
|
|
|
|
return { height: containerHeight };
|
|
|
|
},
|
|
|
|
|
|
|
|
_masonryResizeChanged : function() {
|
|
|
|
return this._checkIfSegmentsChanged();
|
|
|
|
},
|
|
|
|
|
|
|
|
// ====================== fitRows ======================
|
|
|
|
|
|
|
|
_fitRowsReset : function() {
|
|
|
|
this.fitRows = {
|
|
|
|
x : 0,
|
|
|
|
y : 0,
|
|
|
|
height : 0
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
_fitRowsLayout : function( $elems ) {
|
|
|
|
var instance = this,
|
|
|
|
containerWidth = this.element.width(),
|
|
|
|
props = this.fitRows;
|
|
|
|
|
|
|
|
$elems.each( function() {
|
|
|
|
var $this = $(this),
|
|
|
|
atomW = $this.outerWidth(true),
|
|
|
|
atomH = $this.outerHeight(true);
|
|
|
|
|
|
|
|
if ( props.x !== 0 && atomW + props.x > containerWidth ) {
|
|
|
|
// if this element cannot fit in the current row
|
|
|
|
props.x = 0;
|
|
|
|
props.y = props.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
// position the atom
|
|
|
|
instance._pushPosition( $this, props.x, props.y );
|
|
|
|
|
|
|
|
props.height = Math.max( props.y + atomH, props.height );
|
|
|
|
props.x += atomW;
|
|
|
|
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_fitRowsGetContainerSize : function () {
|
|
|
|
return { height : this.fitRows.height };
|
|
|
|
},
|
|
|
|
|
|
|
|
_fitRowsResizeChanged : function() {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// ====================== cellsByRow ======================
|
|
|
|
|
|
|
|
_cellsByRowReset : function() {
|
|
|
|
this.cellsByRow = {
|
|
|
|
index : 0
|
|
|
|
};
|
|
|
|
// get this.cellsByRow.columnWidth
|
|
|
|
this._getSegments();
|
|
|
|
// get this.cellsByRow.rowHeight
|
|
|
|
this._getSegments(true);
|
|
|
|
},
|
|
|
|
|
|
|
|
_cellsByRowLayout : function( $elems ) {
|
|
|
|
var instance = this,
|
|
|
|
props = this.cellsByRow;
|
|
|
|
$elems.each( function(){
|
|
|
|
var $this = $(this),
|
|
|
|
col = props.index % props.cols,
|
|
|
|
row = 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.0.3
|
|
|
|
* 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==';
|
|
|
|
|
|
|
|
function triggerCallback() {
|
|
|
|
callback.call( $this, $images );
|
|
|
|
}
|
|
|
|
|
|
|
|
function imgLoaded( event ) {
|
|
|
|
if ( --len <= 0 && event.target.src !== blank ){
|
|
|
|
setTimeout( triggerCallback );
|
|
|
|
$images.unbind( 'load error', imgLoaded );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !len ) {
|
|
|
|
triggerCallback();
|
|
|
|
}
|
|
|
|
|
|
|
|
$images.bind( 'load error', imgLoaded ).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 = 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 );
|