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