From 7ad8133147620d0ac628dcb7d42bf3d1c2c270aa Mon Sep 17 00:00:00 2001 From: Glen Cheney Date: Sun, 11 May 2014 21:43:30 -0700 Subject: [PATCH] Fix callback events when using jQuery.animate. Replaced setTimeout/proxy with a defer method which can take a context. Changed jQuery to use v1 instead of v2 in testing so that old IE can use the tests. Add a few specs where `supported = false` so jQuery.animate will be tested. Add isTransitioning flag to determine when shuffle is already doing something. --- Gruntfile.js | 6 +- dist/jquery.shuffle.js | 146 ++++++++++++-------- dist/jquery.shuffle.min.js | 2 +- dist/jquery.shuffle.modernizr.js | 146 ++++++++++++-------- dist/jquery.shuffle.modernizr.min.js | 2 +- src/shuffle.js | 146 ++++++++++++-------- test/_SpecRunner.html | 2 +- test/specs.js | 192 +++++++++++++++++++-------- 8 files changed, 422 insertions(+), 220 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 1900cdd..6eb8c7a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -44,11 +44,11 @@ module.exports = function(grunt) { }, src: { files: 'src/*.js', - tasks: ['concat'] + tasks: ['concat', 'test'] }, test: { files: 'test/specs.js', - tasks: ['jasmine:main'] + tasks: ['test'] } }, @@ -117,7 +117,7 @@ module.exports = function(grunt) { specs: 'test/specs.js', vendor: [ 'dist/modernizr.custom.min.js', - 'http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js', + 'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js', 'bower_components/jasmine-jquery/lib/jasmine-jquery.js' ], outfile: 'test/_SpecRunner.html', diff --git a/dist/jquery.shuffle.js b/dist/jquery.shuffle.js index 8eb6bc8..0d665a3 100644 --- a/dist/jquery.shuffle.js +++ b/dist/jquery.shuffle.js @@ -108,6 +108,11 @@ function each(obj, iterator, context) { } } +function defer(fn, context, wait) { + return setTimeout( $.proxy( fn, context ), wait || 0 ); +} + + // Used for unique instance variables var id = 0; var $window = $( window ); @@ -133,10 +138,10 @@ var Shuffle = function( element, options ) { // Dispatch the done event asynchronously so that people can bind to it after // Shuffle has been initialized. - setTimeout( $.proxy(function() { + defer(function() { this.initialized = true; this._fire( Shuffle.EventType.DONE ); - }, this), 16 ); + }, this, 16); }; @@ -317,10 +322,10 @@ Shuffle.prototype._init = function() { // The shuffle items haven't had transitions set on them yet // so the user doesn't see the first layout. Set them now that the first layout is done. if ( this.supported ) { - setTimeout($.proxy(function() { + defer(function() { this._setTransitions(); this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing; - }, this), 0); + }, this); } }; @@ -474,7 +479,7 @@ Shuffle.prototype._initItems = function( $items ) { Shuffle.ClassName.SHUFFLE_ITEM, Shuffle.ClassName.FILTERED ].join(' ')); - $items.css( this.itemCss ).data('position', {x: 0, y: 0}); + $items.css( this.itemCss ).data('position', {x: 0, y: 0}).data('scale', DEFAULT_SCALE); }; /** @@ -655,8 +660,6 @@ Shuffle.prototype._fire = function( name, args ) { * @param {boolean} isOnlyPosition If true this will position the items with zero opacity. */ Shuffle.prototype._layout = function( items, isOnlyPosition ) { - var added = 0; - each(items, function( item ) { var $item = $(item); var itemData = $item.data(); @@ -665,6 +668,7 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { // Save data for shrink $item.data( 'position', pos ); + $item.data( 'scale', DEFAULT_SCALE ); // If the item will not change its position, do not add it to the render // queue. Transitions don't fire when setting a property to the same value. @@ -691,7 +695,6 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { } }; - added++; this.styleQueue.push( transitionObj ); }, this); @@ -699,12 +702,6 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { // queue here with styles from the shrink method this._processStyleQueue(); - // A call to layout happened, but none of the newly filtered items will - // change position. Asynchronously fire the callback here. - if ( items.length > 0 && added === 0 ) { - this._layoutEnd(); - } - // Adjust the height of the container this._setContainerSize(); }; @@ -813,6 +810,8 @@ Shuffle.prototype._shrink = function( $collection ) { return; } + $item.data( 'scale', CONCEALED_SCALE ); + var transitionObj = { $item: $item, x: itemData.position.x, @@ -834,7 +833,7 @@ Shuffle.prototype._shrink = function( $collection ) { */ Shuffle.prototype._onResize = function() { // If shuffle is disabled, destroyed, don't do anything - if ( !this.enabled || this.destroyed ) { + if ( !this.enabled || this.destroyed || this.isTransitioning ) { return; } @@ -887,16 +886,13 @@ Shuffle.prototype._getStylesForTransition = function( opts ) { * @private */ Shuffle.prototype._transition = function( opts ) { - opts.$item.data('scale', opts.scale); - var styles = this._getStylesForTransition( opts ); this._startItemAnimation( opts.$item, styles, opts.callfront || $.noop, opts.callback || $.noop ); }; Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) { - callfront(); - + // Transition end handler removes its listener. function handleTransitionEnd( evt ) { // Make sure this event handler has not bubbled up from a child. if ( evt.target === evt.currentTarget ) { @@ -905,29 +901,34 @@ Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, call } } + callfront(); + + // Transitions are not set until shuffle has loaded to avoid the initial transition. + if ( !this.initialized ) { + $item.css( styles ); + callback(); + return; + } + // Use CSS Transforms if we have them if ( this.supported ) { - $item.css( styles ); - // Transitions are not set until shuffle has loaded to avoid the initial transition. - if ( this.initialized ) { - // Namespaced because the reveal appended function also wants to know - // about the transition end event. - $item.on( TRANSITIONEND, handleTransitionEnd ); - } else { - callback(); - } + // Namespaced because the reveal appended function also wants to know + // about the transition end event. + $item.on( TRANSITIONEND, handleTransitionEnd ); // Use jQuery to animate left/top } else { - $item.stop( true ).animate( styles, this.speed, 'swing', callback ); + var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback ); + this._animations.push( anim.promise() ); } }; -Shuffle.prototype._processStyleQueue = function() { +Shuffle.prototype._processStyleQueue = function( noLayout ) { var $transitions = $(); + // Iterate over the queue and keep track of ones that use transitions. each(this.styleQueue, function( transitionObj ) { if ( transitionObj.skipTransition ) { this._styleImmediately( transitionObj ); @@ -937,13 +938,24 @@ Shuffle.prototype._processStyleQueue = function() { } }, this); - if ( $transitions.length > 0 ) { - if ( this.initialized && this.supported ) { - // TODO: Transitioning flag. - this._whenCollectionDone( $transitions, TRANSITIONEND, this._layoutEnd ); + + if ( $transitions.length > 0 && this.initialized ) { + // Set flag that shuffle is currently in motion. + this.isTransitioning = true; + + if ( this.supported ) { + this._whenCollectionDone( $transitions, TRANSITIONEND, this._movementFinished ); + + // The _transition function appends a promise to the animations array. + // When they're all complete, do things. } else { - this._layoutEnd(); + this._whenAnimationsDone( this._movementFinished ); } + + // A call to layout happened, but none of the newly filtered items will + // change position. Asynchronously fire the callback here. + } else if ( !noLayout ) { + defer( this._layoutEnd, this ); } // Remove everything in the style queue @@ -956,8 +968,13 @@ Shuffle.prototype._styleImmediately = function( opts ) { }, this); }; +Shuffle.prototype._movementFinished = function() { + this.isTransitioning = false; + this._layoutEnd(); +}; + Shuffle.prototype._layoutEnd = function() { - setTimeout($.proxy( this._fire, this, Shuffle.EventType.LAYOUT ), 0); + this._fire( Shuffle.EventType.LAYOUT ); }; Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { @@ -975,7 +992,9 @@ Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { each(this.styleQueue, function( transitionObj ) { transitionObj.skipTransition = true; }); - this._processStyleQueue(); + + // Apply shrink positions, but do not cause a layout event. + this._processStyleQueue( true ); if ( addToEnd ) { this._addItemsToEnd( $newItems, isSequential ); @@ -1008,7 +1027,7 @@ Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) { * @private */ Shuffle.prototype._revealAppended = function( newFilteredItems ) { - setTimeout($.proxy(function() { + defer(function() { each(newFilteredItems, function( el ) { this._transition({ $item: $(el), @@ -1019,8 +1038,9 @@ Shuffle.prototype._revealAppended = function( newFilteredItems ) { this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() { $(newFilteredItems).css( TRANSITION_DELAY, '0ms' ); + this._movementFinished(); }); - }, this), this.revealAppendedDelay); + }, this, this.revealAppendedDelay); }; @@ -1039,7 +1059,6 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba function handleEventName( evt ) { if ( evt.target === evt.currentTarget ) { $( evt.target ).off( eventName, handleEventName ); - done++; // Execute callback if all items have emitted the correct event. @@ -1054,6 +1073,19 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba }; +/** + * Execute a callback after jQuery `animate` for a collection has finished. + * @param {Function} callback Callback to execute when they're done. + * @private + */ +Shuffle.prototype._whenAnimationsDone = function( callback ) { + $.when.apply( null, this._animations ).always( $.proxy( function() { + this._animations.length = 0; + callback.call( this ); + }, this )); +}; + + /** * Public Methods */ @@ -1064,7 +1096,7 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba * @param {Object} [sortObj] A sort object which can sort the filtered set */ Shuffle.prototype.shuffle = function( category, sortObj ) { - if ( !this.enabled ) { + if ( !this.enabled || this.isTransitioning ) { return; } @@ -1089,14 +1121,16 @@ Shuffle.prototype.shuffle = function( category, sortObj ) { * @param {Object} opts the options object for the sorted plugin */ Shuffle.prototype.sort = function( opts ) { - this._resetCols(); + if ( this.enabled && !this.isTransitioning ) { + this._resetCols(); - var sortOptions = opts || this.lastSort; - var items = this._getFilteredItems().sorted( sortOptions ); + var sortOptions = opts || this.lastSort; + var items = this._getFilteredItems().sorted( sortOptions ); - this._layout( items ); + this._layout( items ); - this.lastSort = opts; + this.lastSort = opts; + } }; /** @@ -1105,7 +1139,7 @@ Shuffle.prototype.sort = function( opts ) { * recalculated. */ Shuffle.prototype.update = function( isOnlyLayout ) { - if ( this.enabled ) { + if ( this.enabled && !this.isTransitioning ) { if ( !isOnlyLayout ) { // Get updated colCount @@ -1167,25 +1201,27 @@ Shuffle.prototype.remove = function( $collection ) { return; } - this._whenCollectionDone( $collection, TRANSITIONEND, function() { + function handleRemoved() { // Remove the collection in the callback $collection.remove(); - // Update the items, layout, count and fire off `removed` event + // Update things now that elements have been removed. this.$items = this._getItems(); - this.layout(); this._updateItemCount(); + this._fire( Shuffle.EventType.REMOVED, [ $collection, this ] ); // Let it get garbage collected $collection = null; - }); + } // Hide collection first. + this._toggleFilterClasses( $(), $collection ); this._shrink( $collection ); - // Process changes - this._processStyleQueue(); + this.sort(); + + this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) ); }; /** @@ -1251,7 +1287,8 @@ Shuffle.settings = { itemCss : { // default CSS for each item position: 'absolute', top: 0, - left: 0 + left: 0, + visibility: 'visible' }, offset: { top: 0, left: 0 }, revealAppendedDelay: 300, @@ -1260,6 +1297,7 @@ Shuffle.settings = { enabled: true, destroyed: false, initialized: false, + _animations: [], styleQueue: [] }; diff --git a/dist/jquery.shuffle.min.js b/dist/jquery.shuffle.min.js index 5101498..725f0e8 100644 --- a/dist/jquery.shuffle.min.js +++ b/dist/jquery.shuffle.min.js @@ -5,4 +5,4 @@ * @license MIT license * @version 2.1.1 */ -!function(a){"function"==typeof define&&define.amd?define(["jquery","modernizr"],a):a(window.$,window.Modernizr)}(function(a,b,c){"use strict";function d(a){return a?a.replace(/([A-Z])/g,function(a,b){return"-"+b.toLowerCase()}).replace(/^ms-/,"-ms-"):""}function e(b,c,d){var e,f,g,h=null,i=0;d=d||{};var j=function(){i=d.leading===!1?0:a.now(),h=null,g=b.apply(e,f),e=f=null};return function(){var k=a.now();i||d.leading!==!1||(i=k);var l=c-(k-i);return e=this,f=arguments,0>=l||l>c?(clearTimeout(h),h=null,i=k,g=b.apply(e,f),e=f=null):h||d.trailing===!1||(h=setTimeout(j,l)),g}}function f(a,b,c){for(var d=0,e=a.length;e>d;d++)if(b.call(c,a[d],d,a)==={})return}function g(a){var b,c,d=a.length;if(!d)return a;for(;--d;)c=Math.floor(Math.random()*(d+1)),b=a[c],a[c]=a[d],a[d]=b;return a}if("object"!=typeof b)throw new Error("Shuffle.js requires Modernizr.\nhttp://vestride.github.io/Shuffle/#dependencies");var h=b.prefixed("transition"),i=b.prefixed("transitionDelay"),j=b.prefixed("transitionDuration"),k={WebkitTransition:"webkitTransitionEnd",transition:"transitionend"}[h],l=b.prefixed("transform"),m=d(l),n=b.csstransforms&&b.csstransitions,o=b.csstransforms3d,p="shuffle",q="all",r="groups",s=1,t=.001,u=0,v=a(window),w=function(b,c){c=c||{},a.extend(this,w.options,c,w.settings),this.$el=a(b),this.element=b,this.unique="shuffle_"+u++,this._fire(w.EventType.LOADING),this._init(),setTimeout(a.proxy(function(){this.initialized=!0,this._fire(w.EventType.DONE)},this),16)};return w.EventType={LOADING:"loading",DONE:"done",SORTED:"sorted",LAYOUT:"layout",REMOVED:"removed"},w.ClassName={BASE:p,SHUFFLE_ITEM:"shuffle-item",FILTERED:"filtered",CONCEALED:"concealed"},w._getItemTransformString=function(a,b,c){return o?"translate3d("+a+"px, "+b+"px, 0) scale3d("+c+", "+c+", 1)":"translate("+a+"px, "+b+"px) scale("+c+", "+c+")"},w._getPreciseDimension=function(b,c){var d;return d=window.getComputedStyle?window.getComputedStyle(b,null)[c]:a(b).css(c),parseFloat(d)},w._getOuterWidth=function(b,c){var d=b.offsetWidth;if(c){var e=a(b).css(["marginLeft","marginRight"]),f=parseFloat(e.marginLeft)||0,g=parseFloat(e.marginRight)||0;d+=f+g}return d},w._getOuterHeight=function(b,c){var d=b.offsetHeight;if(c){var e=a(b).css(["marginTop","marginBottom"]),f=parseFloat(e.marginTop)||0,g=parseFloat(e.marginBottom)||0;d+=f+g}return d},w._skipTransition=function(a,b,c){var d=a.style[j];a.style[j]="0ms",b.call(c);var e=a.offsetWidth;e=null,a.style[j]=d},w.prototype._init=function(){this.$items=this._getItems(),this.sizer=this._getElementOption(this.sizer),this.sizer&&(this.useSizer=!0),this.$el.addClass(w.ClassName.BASE),this._initItems(),v.on("resize."+p+"."+this.unique,this._getResizeFunction());var b=this.$el.css(["paddingLeft","paddingRight","position"]),c=w._getOuterWidth(this.element);"static"===b.position&&(this.element.style.position="relative"),this.offset={left:parseInt(b.paddingLeft,10)||0,top:parseInt(b.paddingTop,10)||0},this._setColumns(parseInt(c,10)),this.shuffle(this.group,this.initialSort),this.supported&&setTimeout(a.proxy(function(){this._setTransitions(),this.element.style[h]="height "+this.speed+"ms "+this.easing},this),0)},w.prototype._getResizeFunction=function(){var b=a.proxy(this._onResize,this);return this.throttle?this.throttle(b,this.throttleTime):b},w.prototype._getElementOption=function(a){return"string"==typeof a?this.$el.find(a)[0]||null:a&&a.nodeType&&1===a.nodeType?a:a&&a.jquery?a[0]:null},w.prototype._filter=function(a,b){a=a||this.lastFilter,b=b||this.$items;var c=this._getFilteredSets(a,b);return this._toggleFilterClasses(c.filtered,c.concealed),this.lastFilter=a,"string"==typeof a&&(this.group=a),c.filtered},w.prototype._getFilteredSets=function(b,c){var d=a(),e=a();return b===q?d=c:f(c,function(c){var f=a(c);this._doesPassFilter(b,f)?d=d.add(f):e=e.add(f)},this),{filtered:d,concealed:e}},w.prototype._doesPassFilter=function(b,c){if(a.isFunction(b))return b.call(c[0],c,this);var d=c.data(r),e=this.delimeter&&!a.isArray(d)?d.split(this.delimeter):d;return a.inArray(b,e)>-1},w.prototype._toggleFilterClasses=function(a,b){a.removeClass(w.ClassName.CONCEALED).addClass(w.ClassName.FILTERED),b.removeClass(w.ClassName.FILTERED).addClass(w.ClassName.CONCEALED)},w.prototype._initItems=function(a){a=a||this.$items,a.addClass([w.ClassName.SHUFFLE_ITEM,w.ClassName.FILTERED].join(" ")),a.css(this.itemCss).data("position",{x:0,y:0})},w.prototype._updateItemCount=function(){this.visibleItems=this._getFilteredItems().length},w.prototype._setTransition=function(a){a.style[h]=m+" "+this.speed+"ms "+this.easing+", opacity "+this.speed+"ms "+this.easing},w.prototype._setTransitions=function(a){a=a||this.$items,f(a,function(a){this._setTransition(a)},this)},w.prototype._setSequentialDelay=function(a){this.supported&&f(a,function(a,b){a.style[i]="0ms,"+(b+1)*this.sequentialFadeDelay+"ms"},this)},w.prototype._getItems=function(){return this.$el.children(this.itemSelector)},w.prototype._getFilteredItems=function(){return this.$items.filter("."+w.ClassName.FILTERED)},w.prototype._getConcealedItems=function(){return this.$items.filter("."+w.ClassName.CONCEALED)},w.prototype._getColumnSize=function(b,c){var d;return d=a.isFunction(this.columnWidth)?this.columnWidth(c):this.useSizer?w._getPreciseDimension(this.sizer,"width"):this.columnWidth?this.columnWidth:this.$items.length>0?w._getOuterWidth(this.$items[0],!0):c,0===d&&(d=c),d+b},w.prototype._getGutterSize=function(b){var c;return c=a.isFunction(this.gutterWidth)?this.gutterWidth(b):this.useSizer?w._getPreciseDimension(this.sizer,"marginLeft"):this.gutterWidth},w.prototype._setColumns=function(a){var b=a||w._getOuterWidth(this.element),c=this._getGutterSize(b),d=this._getColumnSize(c,b),e=(b+c)/d;Math.abs(Math.round(e)-e)<.03&&(e=Math.round(e)),this.cols=Math.max(Math.floor(e),1),this.containerWidth=b,this.colWidth=d},w.prototype._setContainerSize=function(){this.$el.css("height",this._getContainerSize())},w.prototype._getContainerSize=function(){return Math.max.apply(Math,this.colYs)},w.prototype._fire=function(a,b){this.$el.trigger(a+"."+p,b&&b.length?b:[this])},w.prototype._layout=function(b,c){var d=0;f(b,function(b){var e=a(b),f=e.data(),g=f.position,h=this._getItemPosition(e);if(e.data("position",h),h.x!==g.x||h.y!==g.y||f.scale!==s){var i={$item:e,x:h.x,y:h.y,scale:s,opacity:c?0:1,skipTransition:!!c,callfront:function(){c||e.css("visibility","visible")},callback:function(){c&&e.css("visibility","hidden")}};d++,this.styleQueue.push(i)}},this),this._processStyleQueue(),b.length>0&&0===d&&this._layoutEnd(),this._setContainerSize()},w.prototype._resetCols=function(){var a=this.cols;for(this.colYs=[];a--;)this.colYs.push(0)},w.prototype._getItemPosition=function(a){var b=w._getOuterWidth(a[0],!0),c=b/this.colWidth;Math.abs(Math.round(c)-c)<.03&&(c=Math.round(c));var d=Math.min(Math.ceil(c),this.cols);if(1===d)return this._placeItem(a,this.colYs);var e,f,g=this.cols+1-d,h=[];for(f=0;g>f;f++)e=this.colYs.slice(f,f+d),h[f]=Math.max.apply(Math,e);return this._placeItem(a,h)},w.prototype._placeItem=function(a,b){for(var c=Math.min.apply(Math,b),d=0,e=0,f=b.length;f>e;e++)if(b[e]>=c-this.buffer&&b[e]<=c+this.buffer){d=e;break}var g={x:Math.round(this.colWidth*d+this.offset.left),y:Math.round(c+this.offset.top)},h=c+w._getOuterHeight(a[0],!0),i=this.cols+1-f;for(e=0;i>e;e++)this.colYs[d+e]=h;return g},w.prototype._shrink=function(b){var c=b||this._getConcealedItems();f(c,function(b){var c=a(b),d=c.data(),e=d.scale===t;if(!e){var f={$item:c,x:d.position.x,y:d.position.y,scale:t,opacity:0,callback:function(){c.css("visibility","hidden")}};this.styleQueue.push(f)}},this)},w.prototype._onResize=function(){if(this.enabled&&!this.destroyed){var a=w._getOuterWidth(this.element);a!==this.containerWidth&&this.update()}},w.prototype._getStylesForTransition=function(a){var b={opacity:a.opacity};return this.supported?a.x!==c&&(b[l]=w._getItemTransformString(a.x,a.y,a.scale)):(b.left=a.x,b.top=a.y),b},w.prototype._transition=function(b){b.$item.data("scale",b.scale);var c=this._getStylesForTransition(b);this._startItemAnimation(b.$item,c,b.callfront||a.noop,b.callback||a.noop)},w.prototype._startItemAnimation=function(b,c,d,e){function f(b){b.target===b.currentTarget&&(a(b.target).off(k,f),e())}d(),this.supported?(b.css(c),this.initialized?b.on(k,f):e()):b.stop(!0).animate(c,this.speed,"swing",e)},w.prototype._processStyleQueue=function(){var b=a();f(this.styleQueue,function(a){a.skipTransition?this._styleImmediately(a):(b=b.add(a.$item),this._transition(a))},this),b.length>0&&(this.initialized&&this.supported?this._whenCollectionDone(b,k,this._layoutEnd):this._layoutEnd()),this.styleQueue.length=0},w.prototype._styleImmediately=function(a){w._skipTransition(a.$item[0],function(){a.$item.css(this._getStylesForTransition(a))},this)},w.prototype._layoutEnd=function(){setTimeout(a.proxy(this._fire,this,w.EventType.LAYOUT),0)},w.prototype._addItems=function(a,b,c){this._initItems(a),this._setTransitions(a),this.$items=this._getItems(),this._shrink(a),f(this.styleQueue,function(a){a.skipTransition=!0}),this._processStyleQueue(),b?this._addItemsToEnd(a,c):this.shuffle(this.lastFilter)},w.prototype._addItemsToEnd=function(a,b){var c=this._filter(null,a),d=c.get();this._updateItemCount(),this._layout(d,!0),b&&this.supported&&this._setSequentialDelay(d),this._revealAppended(d)},w.prototype._revealAppended=function(b){setTimeout(a.proxy(function(){f(b,function(b){this._transition({$item:a(b),opacity:1,scale:s})},this),this._whenCollectionDone(a(b),k,function(){a(b).css(i,"0ms")})},this),this.revealAppendedDelay)},w.prototype._whenCollectionDone=function(b,c,d){function e(b){b.target===b.currentTarget&&(a(b.target).off(c,e),f++,f===g&&d.call(h))}var f=0,g=b.length,h=this;b.on(c,e)},w.prototype.shuffle=function(a,b){this.enabled&&(a||(a=q),this._filter(a),this._updateItemCount(),this._shrink(),this.sort(b))},w.prototype.sort=function(a){this._resetCols();var b=a||this.lastSort,c=this._getFilteredItems().sorted(b);this._layout(c),this.lastSort=a},w.prototype.update=function(a){this.enabled&&(a||this._setColumns(),this.sort())},w.prototype.layout=function(){this.update(!0)},w.prototype.appended=function(a,b,c){this._addItems(a,b===!0,c!==!1)},w.prototype.disable=function(){this.enabled=!1},w.prototype.enable=function(a){this.enabled=!0,a!==!1&&this.update()},w.prototype.remove=function(a){a.length&&a.jquery&&(this._whenCollectionDone(a,k,function(){a.remove(),this.$items=this._getItems(),this.layout(),this._updateItemCount(),this._fire(w.EventType.REMOVED,[a,this]),a=null}),this._shrink(a),this._processStyleQueue())},w.prototype.destroy=function(){v.off("."+this.unique),this.$el.removeClass(p).removeAttr("style").removeData(p),this.$items.removeAttr("style").removeData("position").removeData("scale").removeClass([w.ClassName.CONCEALED,w.ClassName.FILTERED,w.ClassName.SHUFFLE_ITEM].join(" ")),this.$items=null,this.$el=null,this.sizer=null,this.element=null,this.destroyed=!0},w.options={group:q,speed:250,easing:"ease-out",itemSelector:"",sizer:null,gutterWidth:0,columnWidth:0,delimeter:null,buffer:0,initialSort:null,throttle:e,throttleTime:300,sequentialFadeDelay:150,supported:n},w.settings={useSizer:!1,itemCss:{position:"absolute",top:0,left:0},offset:{top:0,left:0},revealAppendedDelay:300,lastSort:{},lastFilter:q,enabled:!0,destroyed:!1,initialized:!1,styleQueue:[]},a.fn.shuffle=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),e=d.data(p);e?"string"==typeof b&&e[b]&&e[b].apply(e,c):(e=new w(this,b),d.data(p,e))})},a.fn.sorted=function(b){var d=a.extend({},a.fn.sorted.defaults,b),e=this.get(),f=!1;return e.length?d.randomize?g(e):(a.isFunction(d.by)&&e.sort(function(b,e){if(f)return 0;var g=d.by(a(b)),h=d.by(a(e));return g===c&&h===c?(f=!0,0):h>g||"sortFirst"===g||"sortLast"===h?-1:g>h||"sortLast"===g||"sortFirst"===h?1:0}),f?this.get():(d.reverse&&e.reverse(),e)):[]},a.fn.sorted.defaults={reverse:!1,by:null,randomize:!1},w}); \ No newline at end of file +!function(a){"function"==typeof define&&define.amd?define(["jquery","modernizr"],a):a(window.$,window.Modernizr)}(function(a,b,c){"use strict";function d(a){return a?a.replace(/([A-Z])/g,function(a,b){return"-"+b.toLowerCase()}).replace(/^ms-/,"-ms-"):""}function e(b,c,d){var e,f,g,h=null,i=0;d=d||{};var j=function(){i=d.leading===!1?0:a.now(),h=null,g=b.apply(e,f),e=f=null};return function(){var k=a.now();i||d.leading!==!1||(i=k);var l=c-(k-i);return e=this,f=arguments,0>=l||l>c?(clearTimeout(h),h=null,i=k,g=b.apply(e,f),e=f=null):h||d.trailing===!1||(h=setTimeout(j,l)),g}}function f(a,b,c){for(var d=0,e=a.length;e>d;d++)if(b.call(c,a[d],d,a)==={})return}function g(b,c,d){return setTimeout(a.proxy(b,c),d||0)}function h(a){var b,c,d=a.length;if(!d)return a;for(;--d;)c=Math.floor(Math.random()*(d+1)),b=a[c],a[c]=a[d],a[d]=b;return a}if("object"!=typeof b)throw new Error("Shuffle.js requires Modernizr.\nhttp://vestride.github.io/Shuffle/#dependencies");var i=b.prefixed("transition"),j=b.prefixed("transitionDelay"),k=b.prefixed("transitionDuration"),l={WebkitTransition:"webkitTransitionEnd",transition:"transitionend"}[i],m=b.prefixed("transform"),n=d(m),o=b.csstransforms&&b.csstransitions,p=b.csstransforms3d,q="shuffle",r="all",s="groups",t=1,u=.001,v=0,w=a(window),x=function(b,c){c=c||{},a.extend(this,x.options,c,x.settings),this.$el=a(b),this.element=b,this.unique="shuffle_"+v++,this._fire(x.EventType.LOADING),this._init(),g(function(){this.initialized=!0,this._fire(x.EventType.DONE)},this,16)};return x.EventType={LOADING:"loading",DONE:"done",SORTED:"sorted",LAYOUT:"layout",REMOVED:"removed"},x.ClassName={BASE:q,SHUFFLE_ITEM:"shuffle-item",FILTERED:"filtered",CONCEALED:"concealed"},x._getItemTransformString=function(a,b,c){return p?"translate3d("+a+"px, "+b+"px, 0) scale3d("+c+", "+c+", 1)":"translate("+a+"px, "+b+"px) scale("+c+", "+c+")"},x._getPreciseDimension=function(b,c){var d;return d=window.getComputedStyle?window.getComputedStyle(b,null)[c]:a(b).css(c),parseFloat(d)},x._getOuterWidth=function(b,c){var d=b.offsetWidth;if(c){var e=a(b).css(["marginLeft","marginRight"]),f=parseFloat(e.marginLeft)||0,g=parseFloat(e.marginRight)||0;d+=f+g}return d},x._getOuterHeight=function(b,c){var d=b.offsetHeight;if(c){var e=a(b).css(["marginTop","marginBottom"]),f=parseFloat(e.marginTop)||0,g=parseFloat(e.marginBottom)||0;d+=f+g}return d},x._skipTransition=function(a,b,c){var d=a.style[k];a.style[k]="0ms",b.call(c);var e=a.offsetWidth;e=null,a.style[k]=d},x.prototype._init=function(){this.$items=this._getItems(),this.sizer=this._getElementOption(this.sizer),this.sizer&&(this.useSizer=!0),this.$el.addClass(x.ClassName.BASE),this._initItems(),w.on("resize."+q+"."+this.unique,this._getResizeFunction());var a=this.$el.css(["paddingLeft","paddingRight","position"]),b=x._getOuterWidth(this.element);"static"===a.position&&(this.element.style.position="relative"),this.offset={left:parseInt(a.paddingLeft,10)||0,top:parseInt(a.paddingTop,10)||0},this._setColumns(parseInt(b,10)),this.shuffle(this.group,this.initialSort),this.supported&&g(function(){this._setTransitions(),this.element.style[i]="height "+this.speed+"ms "+this.easing},this)},x.prototype._getResizeFunction=function(){var b=a.proxy(this._onResize,this);return this.throttle?this.throttle(b,this.throttleTime):b},x.prototype._getElementOption=function(a){return"string"==typeof a?this.$el.find(a)[0]||null:a&&a.nodeType&&1===a.nodeType?a:a&&a.jquery?a[0]:null},x.prototype._filter=function(a,b){a=a||this.lastFilter,b=b||this.$items;var c=this._getFilteredSets(a,b);return this._toggleFilterClasses(c.filtered,c.concealed),this.lastFilter=a,"string"==typeof a&&(this.group=a),c.filtered},x.prototype._getFilteredSets=function(b,c){var d=a(),e=a();return b===r?d=c:f(c,function(c){var f=a(c);this._doesPassFilter(b,f)?d=d.add(f):e=e.add(f)},this),{filtered:d,concealed:e}},x.prototype._doesPassFilter=function(b,c){if(a.isFunction(b))return b.call(c[0],c,this);var d=c.data(s),e=this.delimeter&&!a.isArray(d)?d.split(this.delimeter):d;return a.inArray(b,e)>-1},x.prototype._toggleFilterClasses=function(a,b){a.removeClass(x.ClassName.CONCEALED).addClass(x.ClassName.FILTERED),b.removeClass(x.ClassName.FILTERED).addClass(x.ClassName.CONCEALED)},x.prototype._initItems=function(a){a=a||this.$items,a.addClass([x.ClassName.SHUFFLE_ITEM,x.ClassName.FILTERED].join(" ")),a.css(this.itemCss).data("position",{x:0,y:0}).data("scale",t)},x.prototype._updateItemCount=function(){this.visibleItems=this._getFilteredItems().length},x.prototype._setTransition=function(a){a.style[i]=n+" "+this.speed+"ms "+this.easing+", opacity "+this.speed+"ms "+this.easing},x.prototype._setTransitions=function(a){a=a||this.$items,f(a,function(a){this._setTransition(a)},this)},x.prototype._setSequentialDelay=function(a){this.supported&&f(a,function(a,b){a.style[j]="0ms,"+(b+1)*this.sequentialFadeDelay+"ms"},this)},x.prototype._getItems=function(){return this.$el.children(this.itemSelector)},x.prototype._getFilteredItems=function(){return this.$items.filter("."+x.ClassName.FILTERED)},x.prototype._getConcealedItems=function(){return this.$items.filter("."+x.ClassName.CONCEALED)},x.prototype._getColumnSize=function(b,c){var d;return d=a.isFunction(this.columnWidth)?this.columnWidth(c):this.useSizer?x._getPreciseDimension(this.sizer,"width"):this.columnWidth?this.columnWidth:this.$items.length>0?x._getOuterWidth(this.$items[0],!0):c,0===d&&(d=c),d+b},x.prototype._getGutterSize=function(b){var c;return c=a.isFunction(this.gutterWidth)?this.gutterWidth(b):this.useSizer?x._getPreciseDimension(this.sizer,"marginLeft"):this.gutterWidth},x.prototype._setColumns=function(a){var b=a||x._getOuterWidth(this.element),c=this._getGutterSize(b),d=this._getColumnSize(c,b),e=(b+c)/d;Math.abs(Math.round(e)-e)<.03&&(e=Math.round(e)),this.cols=Math.max(Math.floor(e),1),this.containerWidth=b,this.colWidth=d},x.prototype._setContainerSize=function(){this.$el.css("height",this._getContainerSize())},x.prototype._getContainerSize=function(){return Math.max.apply(Math,this.colYs)},x.prototype._fire=function(a,b){this.$el.trigger(a+"."+q,b&&b.length?b:[this])},x.prototype._layout=function(b,c){f(b,function(b){var d=a(b),e=d.data(),f=e.position,g=this._getItemPosition(d);if(d.data("position",g),d.data("scale",t),g.x!==f.x||g.y!==f.y||e.scale!==t){var h={$item:d,x:g.x,y:g.y,scale:t,opacity:c?0:1,skipTransition:!!c,callfront:function(){c||d.css("visibility","visible")},callback:function(){c&&d.css("visibility","hidden")}};this.styleQueue.push(h)}},this),this._processStyleQueue(),this._setContainerSize()},x.prototype._resetCols=function(){var a=this.cols;for(this.colYs=[];a--;)this.colYs.push(0)},x.prototype._getItemPosition=function(a){var b=x._getOuterWidth(a[0],!0),c=b/this.colWidth;Math.abs(Math.round(c)-c)<.03&&(c=Math.round(c));var d=Math.min(Math.ceil(c),this.cols);if(1===d)return this._placeItem(a,this.colYs);var e,f,g=this.cols+1-d,h=[];for(f=0;g>f;f++)e=this.colYs.slice(f,f+d),h[f]=Math.max.apply(Math,e);return this._placeItem(a,h)},x.prototype._placeItem=function(a,b){for(var c=Math.min.apply(Math,b),d=0,e=0,f=b.length;f>e;e++)if(b[e]>=c-this.buffer&&b[e]<=c+this.buffer){d=e;break}var g={x:Math.round(this.colWidth*d+this.offset.left),y:Math.round(c+this.offset.top)},h=c+x._getOuterHeight(a[0],!0),i=this.cols+1-f;for(e=0;i>e;e++)this.colYs[d+e]=h;return g},x.prototype._shrink=function(b){var c=b||this._getConcealedItems();f(c,function(b){var c=a(b),d=c.data(),e=d.scale===u;if(!e){c.data("scale",u);var f={$item:c,x:d.position.x,y:d.position.y,scale:u,opacity:0,callback:function(){c.css("visibility","hidden")}};this.styleQueue.push(f)}},this)},x.prototype._onResize=function(){if(this.enabled&&!this.destroyed&&!this.isTransitioning){var a=x._getOuterWidth(this.element);a!==this.containerWidth&&this.update()}},x.prototype._getStylesForTransition=function(a){var b={opacity:a.opacity};return this.supported?a.x!==c&&(b[m]=x._getItemTransformString(a.x,a.y,a.scale)):(b.left=a.x,b.top=a.y),b},x.prototype._transition=function(b){var c=this._getStylesForTransition(b);this._startItemAnimation(b.$item,c,b.callfront||a.noop,b.callback||a.noop)},x.prototype._startItemAnimation=function(b,c,d,e){function f(b){b.target===b.currentTarget&&(a(b.target).off(l,f),e())}if(d(),!this.initialized)return b.css(c),void e();if(this.supported)b.css(c),b.on(l,f);else{var g=b.stop(!0).animate(c,this.speed,"swing",e);this._animations.push(g.promise())}},x.prototype._processStyleQueue=function(b){var c=a();f(this.styleQueue,function(a){a.skipTransition?this._styleImmediately(a):(c=c.add(a.$item),this._transition(a))},this),c.length>0&&this.initialized?(this.isTransitioning=!0,this.supported?this._whenCollectionDone(c,l,this._movementFinished):this._whenAnimationsDone(this._movementFinished)):b||g(this._layoutEnd,this),this.styleQueue.length=0},x.prototype._styleImmediately=function(a){x._skipTransition(a.$item[0],function(){a.$item.css(this._getStylesForTransition(a))},this)},x.prototype._movementFinished=function(){this.isTransitioning=!1,this._layoutEnd()},x.prototype._layoutEnd=function(){this._fire(x.EventType.LAYOUT)},x.prototype._addItems=function(a,b,c){this._initItems(a),this._setTransitions(a),this.$items=this._getItems(),this._shrink(a),f(this.styleQueue,function(a){a.skipTransition=!0}),this._processStyleQueue(!0),b?this._addItemsToEnd(a,c):this.shuffle(this.lastFilter)},x.prototype._addItemsToEnd=function(a,b){var c=this._filter(null,a),d=c.get();this._updateItemCount(),this._layout(d,!0),b&&this.supported&&this._setSequentialDelay(d),this._revealAppended(d)},x.prototype._revealAppended=function(b){g(function(){f(b,function(b){this._transition({$item:a(b),opacity:1,scale:t})},this),this._whenCollectionDone(a(b),l,function(){a(b).css(j,"0ms"),this._movementFinished()})},this,this.revealAppendedDelay)},x.prototype._whenCollectionDone=function(b,c,d){function e(b){b.target===b.currentTarget&&(a(b.target).off(c,e),f++,f===g&&d.call(h))}var f=0,g=b.length,h=this;b.on(c,e)},x.prototype._whenAnimationsDone=function(b){a.when.apply(null,this._animations).always(a.proxy(function(){this._animations.length=0,b.call(this)},this))},x.prototype.shuffle=function(a,b){this.enabled&&!this.isTransitioning&&(a||(a=r),this._filter(a),this._updateItemCount(),this._shrink(),this.sort(b))},x.prototype.sort=function(a){if(this.enabled&&!this.isTransitioning){this._resetCols();var b=a||this.lastSort,c=this._getFilteredItems().sorted(b);this._layout(c),this.lastSort=a}},x.prototype.update=function(a){this.enabled&&!this.isTransitioning&&(a||this._setColumns(),this.sort())},x.prototype.layout=function(){this.update(!0)},x.prototype.appended=function(a,b,c){this._addItems(a,b===!0,c!==!1)},x.prototype.disable=function(){this.enabled=!1},x.prototype.enable=function(a){this.enabled=!0,a!==!1&&this.update()},x.prototype.remove=function(b){function c(){b.remove(),this.$items=this._getItems(),this._updateItemCount(),this._fire(x.EventType.REMOVED,[b,this]),b=null}b.length&&b.jquery&&(this._toggleFilterClasses(a(),b),this._shrink(b),this.sort(),this.$el.one(x.EventType.LAYOUT+"."+q,a.proxy(c,this)))},x.prototype.destroy=function(){w.off("."+this.unique),this.$el.removeClass(q).removeAttr("style").removeData(q),this.$items.removeAttr("style").removeData("position").removeData("scale").removeClass([x.ClassName.CONCEALED,x.ClassName.FILTERED,x.ClassName.SHUFFLE_ITEM].join(" ")),this.$items=null,this.$el=null,this.sizer=null,this.element=null,this.destroyed=!0},x.options={group:r,speed:250,easing:"ease-out",itemSelector:"",sizer:null,gutterWidth:0,columnWidth:0,delimeter:null,buffer:0,initialSort:null,throttle:e,throttleTime:300,sequentialFadeDelay:150,supported:o},x.settings={useSizer:!1,itemCss:{position:"absolute",top:0,left:0,visibility:"visible"},offset:{top:0,left:0},revealAppendedDelay:300,lastSort:{},lastFilter:r,enabled:!0,destroyed:!1,initialized:!1,_animations:[],styleQueue:[]},a.fn.shuffle=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),e=d.data(q);e?"string"==typeof b&&e[b]&&e[b].apply(e,c):(e=new x(this,b),d.data(q,e))})},a.fn.sorted=function(b){var d=a.extend({},a.fn.sorted.defaults,b),e=this.get(),f=!1;return e.length?d.randomize?h(e):(a.isFunction(d.by)&&e.sort(function(b,e){if(f)return 0;var g=d.by(a(b)),h=d.by(a(e));return g===c&&h===c?(f=!0,0):h>g||"sortFirst"===g||"sortLast"===h?-1:g>h||"sortLast"===g||"sortFirst"===h?1:0}),f?this.get():(d.reverse&&e.reverse(),e)):[]},a.fn.sorted.defaults={reverse:!1,by:null,randomize:!1},x}); \ No newline at end of file diff --git a/dist/jquery.shuffle.modernizr.js b/dist/jquery.shuffle.modernizr.js index 2d2d5b0..747e0cf 100644 --- a/dist/jquery.shuffle.modernizr.js +++ b/dist/jquery.shuffle.modernizr.js @@ -114,6 +114,11 @@ function each(obj, iterator, context) { } } +function defer(fn, context, wait) { + return setTimeout( $.proxy( fn, context ), wait || 0 ); +} + + // Used for unique instance variables var id = 0; var $window = $( window ); @@ -139,10 +144,10 @@ var Shuffle = function( element, options ) { // Dispatch the done event asynchronously so that people can bind to it after // Shuffle has been initialized. - setTimeout( $.proxy(function() { + defer(function() { this.initialized = true; this._fire( Shuffle.EventType.DONE ); - }, this), 16 ); + }, this, 16); }; @@ -323,10 +328,10 @@ Shuffle.prototype._init = function() { // The shuffle items haven't had transitions set on them yet // so the user doesn't see the first layout. Set them now that the first layout is done. if ( this.supported ) { - setTimeout($.proxy(function() { + defer(function() { this._setTransitions(); this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing; - }, this), 0); + }, this); } }; @@ -480,7 +485,7 @@ Shuffle.prototype._initItems = function( $items ) { Shuffle.ClassName.SHUFFLE_ITEM, Shuffle.ClassName.FILTERED ].join(' ')); - $items.css( this.itemCss ).data('position', {x: 0, y: 0}); + $items.css( this.itemCss ).data('position', {x: 0, y: 0}).data('scale', DEFAULT_SCALE); }; /** @@ -661,8 +666,6 @@ Shuffle.prototype._fire = function( name, args ) { * @param {boolean} isOnlyPosition If true this will position the items with zero opacity. */ Shuffle.prototype._layout = function( items, isOnlyPosition ) { - var added = 0; - each(items, function( item ) { var $item = $(item); var itemData = $item.data(); @@ -671,6 +674,7 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { // Save data for shrink $item.data( 'position', pos ); + $item.data( 'scale', DEFAULT_SCALE ); // If the item will not change its position, do not add it to the render // queue. Transitions don't fire when setting a property to the same value. @@ -697,7 +701,6 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { } }; - added++; this.styleQueue.push( transitionObj ); }, this); @@ -705,12 +708,6 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { // queue here with styles from the shrink method this._processStyleQueue(); - // A call to layout happened, but none of the newly filtered items will - // change position. Asynchronously fire the callback here. - if ( items.length > 0 && added === 0 ) { - this._layoutEnd(); - } - // Adjust the height of the container this._setContainerSize(); }; @@ -819,6 +816,8 @@ Shuffle.prototype._shrink = function( $collection ) { return; } + $item.data( 'scale', CONCEALED_SCALE ); + var transitionObj = { $item: $item, x: itemData.position.x, @@ -840,7 +839,7 @@ Shuffle.prototype._shrink = function( $collection ) { */ Shuffle.prototype._onResize = function() { // If shuffle is disabled, destroyed, don't do anything - if ( !this.enabled || this.destroyed ) { + if ( !this.enabled || this.destroyed || this.isTransitioning ) { return; } @@ -893,16 +892,13 @@ Shuffle.prototype._getStylesForTransition = function( opts ) { * @private */ Shuffle.prototype._transition = function( opts ) { - opts.$item.data('scale', opts.scale); - var styles = this._getStylesForTransition( opts ); this._startItemAnimation( opts.$item, styles, opts.callfront || $.noop, opts.callback || $.noop ); }; Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) { - callfront(); - + // Transition end handler removes its listener. function handleTransitionEnd( evt ) { // Make sure this event handler has not bubbled up from a child. if ( evt.target === evt.currentTarget ) { @@ -911,29 +907,34 @@ Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, call } } + callfront(); + + // Transitions are not set until shuffle has loaded to avoid the initial transition. + if ( !this.initialized ) { + $item.css( styles ); + callback(); + return; + } + // Use CSS Transforms if we have them if ( this.supported ) { - $item.css( styles ); - // Transitions are not set until shuffle has loaded to avoid the initial transition. - if ( this.initialized ) { - // Namespaced because the reveal appended function also wants to know - // about the transition end event. - $item.on( TRANSITIONEND, handleTransitionEnd ); - } else { - callback(); - } + // Namespaced because the reveal appended function also wants to know + // about the transition end event. + $item.on( TRANSITIONEND, handleTransitionEnd ); // Use jQuery to animate left/top } else { - $item.stop( true ).animate( styles, this.speed, 'swing', callback ); + var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback ); + this._animations.push( anim.promise() ); } }; -Shuffle.prototype._processStyleQueue = function() { +Shuffle.prototype._processStyleQueue = function( noLayout ) { var $transitions = $(); + // Iterate over the queue and keep track of ones that use transitions. each(this.styleQueue, function( transitionObj ) { if ( transitionObj.skipTransition ) { this._styleImmediately( transitionObj ); @@ -943,13 +944,24 @@ Shuffle.prototype._processStyleQueue = function() { } }, this); - if ( $transitions.length > 0 ) { - if ( this.initialized && this.supported ) { - // TODO: Transitioning flag. - this._whenCollectionDone( $transitions, TRANSITIONEND, this._layoutEnd ); + + if ( $transitions.length > 0 && this.initialized ) { + // Set flag that shuffle is currently in motion. + this.isTransitioning = true; + + if ( this.supported ) { + this._whenCollectionDone( $transitions, TRANSITIONEND, this._movementFinished ); + + // The _transition function appends a promise to the animations array. + // When they're all complete, do things. } else { - this._layoutEnd(); + this._whenAnimationsDone( this._movementFinished ); } + + // A call to layout happened, but none of the newly filtered items will + // change position. Asynchronously fire the callback here. + } else if ( !noLayout ) { + defer( this._layoutEnd, this ); } // Remove everything in the style queue @@ -962,8 +974,13 @@ Shuffle.prototype._styleImmediately = function( opts ) { }, this); }; +Shuffle.prototype._movementFinished = function() { + this.isTransitioning = false; + this._layoutEnd(); +}; + Shuffle.prototype._layoutEnd = function() { - setTimeout($.proxy( this._fire, this, Shuffle.EventType.LAYOUT ), 0); + this._fire( Shuffle.EventType.LAYOUT ); }; Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { @@ -981,7 +998,9 @@ Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { each(this.styleQueue, function( transitionObj ) { transitionObj.skipTransition = true; }); - this._processStyleQueue(); + + // Apply shrink positions, but do not cause a layout event. + this._processStyleQueue( true ); if ( addToEnd ) { this._addItemsToEnd( $newItems, isSequential ); @@ -1014,7 +1033,7 @@ Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) { * @private */ Shuffle.prototype._revealAppended = function( newFilteredItems ) { - setTimeout($.proxy(function() { + defer(function() { each(newFilteredItems, function( el ) { this._transition({ $item: $(el), @@ -1025,8 +1044,9 @@ Shuffle.prototype._revealAppended = function( newFilteredItems ) { this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() { $(newFilteredItems).css( TRANSITION_DELAY, '0ms' ); + this._movementFinished(); }); - }, this), this.revealAppendedDelay); + }, this, this.revealAppendedDelay); }; @@ -1045,7 +1065,6 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba function handleEventName( evt ) { if ( evt.target === evt.currentTarget ) { $( evt.target ).off( eventName, handleEventName ); - done++; // Execute callback if all items have emitted the correct event. @@ -1060,6 +1079,19 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba }; +/** + * Execute a callback after jQuery `animate` for a collection has finished. + * @param {Function} callback Callback to execute when they're done. + * @private + */ +Shuffle.prototype._whenAnimationsDone = function( callback ) { + $.when.apply( null, this._animations ).always( $.proxy( function() { + this._animations.length = 0; + callback.call( this ); + }, this )); +}; + + /** * Public Methods */ @@ -1070,7 +1102,7 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba * @param {Object} [sortObj] A sort object which can sort the filtered set */ Shuffle.prototype.shuffle = function( category, sortObj ) { - if ( !this.enabled ) { + if ( !this.enabled || this.isTransitioning ) { return; } @@ -1095,14 +1127,16 @@ Shuffle.prototype.shuffle = function( category, sortObj ) { * @param {Object} opts the options object for the sorted plugin */ Shuffle.prototype.sort = function( opts ) { - this._resetCols(); + if ( this.enabled && !this.isTransitioning ) { + this._resetCols(); - var sortOptions = opts || this.lastSort; - var items = this._getFilteredItems().sorted( sortOptions ); + var sortOptions = opts || this.lastSort; + var items = this._getFilteredItems().sorted( sortOptions ); - this._layout( items ); + this._layout( items ); - this.lastSort = opts; + this.lastSort = opts; + } }; /** @@ -1111,7 +1145,7 @@ Shuffle.prototype.sort = function( opts ) { * recalculated. */ Shuffle.prototype.update = function( isOnlyLayout ) { - if ( this.enabled ) { + if ( this.enabled && !this.isTransitioning ) { if ( !isOnlyLayout ) { // Get updated colCount @@ -1173,25 +1207,27 @@ Shuffle.prototype.remove = function( $collection ) { return; } - this._whenCollectionDone( $collection, TRANSITIONEND, function() { + function handleRemoved() { // Remove the collection in the callback $collection.remove(); - // Update the items, layout, count and fire off `removed` event + // Update things now that elements have been removed. this.$items = this._getItems(); - this.layout(); this._updateItemCount(); + this._fire( Shuffle.EventType.REMOVED, [ $collection, this ] ); // Let it get garbage collected $collection = null; - }); + } // Hide collection first. + this._toggleFilterClasses( $(), $collection ); this._shrink( $collection ); - // Process changes - this._processStyleQueue(); + this.sort(); + + this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) ); }; /** @@ -1257,7 +1293,8 @@ Shuffle.settings = { itemCss : { // default CSS for each item position: 'absolute', top: 0, - left: 0 + left: 0, + visibility: 'visible' }, offset: { top: 0, left: 0 }, revealAppendedDelay: 300, @@ -1266,6 +1303,7 @@ Shuffle.settings = { enabled: true, destroyed: false, initialized: false, + _animations: [], styleQueue: [] }; diff --git a/dist/jquery.shuffle.modernizr.min.js b/dist/jquery.shuffle.modernizr.min.js index bf618c7..640adff 100644 --- a/dist/jquery.shuffle.modernizr.min.js +++ b/dist/jquery.shuffle.modernizr.min.js @@ -5,4 +5,4 @@ * @license MIT license * @version 2.1.1 */ -window.Modernizr=function(a,b,c){function d(a){s.cssText=a}function e(a,b){return typeof a===b}function f(a,b){return!!~(""+a).indexOf(b)}function g(a,b){for(var d in a){var e=a[d];if(!f(e,"-")&&s[e]!==c)return"pfx"==b?e:!0}return!1}function h(a,b,d){for(var f in a){var g=b[a[f]];if(g!==c)return d===!1?a[f]:e(g,"function")?g.bind(d||b):g}return!1}function i(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),f=(a+" "+v.join(d+" ")+d).split(" ");return e(b,"string")||e(b,"undefined")?g(f,b):(f=(a+" "+w.join(d+" ")+d).split(" "),h(f,b,c))}var j,k,l,m="2.6.2",n={},o=!0,p=b.documentElement,q="modernizr",r=b.createElement(q),s=r.style,t=({}.toString," -webkit- -moz- -o- -ms- ".split(" ")),u="Webkit Moz O ms",v=u.split(" "),w=u.toLowerCase().split(" "),x={},y=[],z=y.slice,A=function(a,c,d,e){var f,g,h,i,j=b.createElement("div"),k=b.body,l=k||b.createElement("body");if(parseInt(d,10))for(;d--;)h=b.createElement("div"),h.id=e?e[d]:q+(d+1),j.appendChild(h);return f=["­",'"].join(""),j.id=q,(k?j:l).innerHTML+=f,l.appendChild(j),k||(l.style.background="",l.style.overflow="hidden",i=p.style.overflow,p.style.overflow="hidden",p.appendChild(l)),g=c(j,a),k?j.parentNode.removeChild(j):(l.parentNode.removeChild(l),p.style.overflow=i),!!g},B={}.hasOwnProperty;l=e(B,"undefined")||e(B.call,"undefined")?function(a,b){return b in a&&e(a.constructor.prototype[b],"undefined")}:function(a,b){return B.call(a,b)},Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError;var c=z.call(arguments,1),d=function(){if(this instanceof d){var e=function(){};e.prototype=b.prototype;var f=new e,g=b.apply(f,c.concat(z.call(arguments)));return Object(g)===g?g:f}return b.apply(a,c.concat(z.call(arguments)))};return d}),x.csstransforms=function(){return!!i("transform")},x.csstransforms3d=function(){var a=!!i("perspective");return a&&"webkitPerspective"in p.style&&A("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b){a=9===b.offsetLeft&&3===b.offsetHeight}),a},x.csstransitions=function(){return i("transition")};for(var C in x)l(x,C)&&(k=C.toLowerCase(),n[k]=x[C](),y.push((n[k]?"":"no-")+k));return n.addTest=function(a,b){if("object"==typeof a)for(var d in a)l(a,d)&&n.addTest(d,a[d]);else{if(a=a.toLowerCase(),n[a]!==c)return n;b="function"==typeof b?b():b,"undefined"!=typeof o&&o&&(p.className+=" "+(b?"":"no-")+a),n[a]=b}return n},d(""),r=j=null,n._version=m,n._prefixes=t,n._domPrefixes=w,n._cssomPrefixes=v,n.testProp=function(a){return g([a])},n.testAllProps=i,n.testStyles=A,n.prefixed=function(a,b,c){return b?i(a,b,c):i(a,"pfx")},p.className=p.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(o?" js "+y.join(" "):""),n}(this,this.document),function(a){"function"==typeof define&&define.amd?define(["jquery","modernizr"],a):a(window.$,window.Modernizr)}(function(a,b,c){"use strict";function d(a){return a?a.replace(/([A-Z])/g,function(a,b){return"-"+b.toLowerCase()}).replace(/^ms-/,"-ms-"):""}function e(b,c,d){var e,f,g,h=null,i=0;d=d||{};var j=function(){i=d.leading===!1?0:a.now(),h=null,g=b.apply(e,f),e=f=null};return function(){var k=a.now();i||d.leading!==!1||(i=k);var l=c-(k-i);return e=this,f=arguments,0>=l||l>c?(clearTimeout(h),h=null,i=k,g=b.apply(e,f),e=f=null):h||d.trailing===!1||(h=setTimeout(j,l)),g}}function f(a,b,c){for(var d=0,e=a.length;e>d;d++)if(b.call(c,a[d],d,a)==={})return}function g(a){var b,c,d=a.length;if(!d)return a;for(;--d;)c=Math.floor(Math.random()*(d+1)),b=a[c],a[c]=a[d],a[d]=b;return a}if("object"!=typeof b)throw new Error("Shuffle.js requires Modernizr.\nhttp://vestride.github.io/Shuffle/#dependencies");var h=b.prefixed("transition"),i=b.prefixed("transitionDelay"),j=b.prefixed("transitionDuration"),k={WebkitTransition:"webkitTransitionEnd",transition:"transitionend"}[h],l=b.prefixed("transform"),m=d(l),n=b.csstransforms&&b.csstransitions,o=b.csstransforms3d,p="shuffle",q="all",r="groups",s=1,t=.001,u=0,v=a(window),w=function(b,c){c=c||{},a.extend(this,w.options,c,w.settings),this.$el=a(b),this.element=b,this.unique="shuffle_"+u++,this._fire(w.EventType.LOADING),this._init(),setTimeout(a.proxy(function(){this.initialized=!0,this._fire(w.EventType.DONE)},this),16)};return w.EventType={LOADING:"loading",DONE:"done",SORTED:"sorted",LAYOUT:"layout",REMOVED:"removed"},w.ClassName={BASE:p,SHUFFLE_ITEM:"shuffle-item",FILTERED:"filtered",CONCEALED:"concealed"},w._getItemTransformString=function(a,b,c){return o?"translate3d("+a+"px, "+b+"px, 0) scale3d("+c+", "+c+", 1)":"translate("+a+"px, "+b+"px) scale("+c+", "+c+")"},w._getPreciseDimension=function(b,c){var d;return d=window.getComputedStyle?window.getComputedStyle(b,null)[c]:a(b).css(c),parseFloat(d)},w._getOuterWidth=function(b,c){var d=b.offsetWidth;if(c){var e=a(b).css(["marginLeft","marginRight"]),f=parseFloat(e.marginLeft)||0,g=parseFloat(e.marginRight)||0;d+=f+g}return d},w._getOuterHeight=function(b,c){var d=b.offsetHeight;if(c){var e=a(b).css(["marginTop","marginBottom"]),f=parseFloat(e.marginTop)||0,g=parseFloat(e.marginBottom)||0;d+=f+g}return d},w._skipTransition=function(a,b,c){var d=a.style[j];a.style[j]="0ms",b.call(c);var e=a.offsetWidth;e=null,a.style[j]=d},w.prototype._init=function(){this.$items=this._getItems(),this.sizer=this._getElementOption(this.sizer),this.sizer&&(this.useSizer=!0),this.$el.addClass(w.ClassName.BASE),this._initItems(),v.on("resize."+p+"."+this.unique,this._getResizeFunction());var b=this.$el.css(["paddingLeft","paddingRight","position"]),c=w._getOuterWidth(this.element);"static"===b.position&&(this.element.style.position="relative"),this.offset={left:parseInt(b.paddingLeft,10)||0,top:parseInt(b.paddingTop,10)||0},this._setColumns(parseInt(c,10)),this.shuffle(this.group,this.initialSort),this.supported&&setTimeout(a.proxy(function(){this._setTransitions(),this.element.style[h]="height "+this.speed+"ms "+this.easing},this),0)},w.prototype._getResizeFunction=function(){var b=a.proxy(this._onResize,this);return this.throttle?this.throttle(b,this.throttleTime):b},w.prototype._getElementOption=function(a){return"string"==typeof a?this.$el.find(a)[0]||null:a&&a.nodeType&&1===a.nodeType?a:a&&a.jquery?a[0]:null},w.prototype._filter=function(a,b){a=a||this.lastFilter,b=b||this.$items;var c=this._getFilteredSets(a,b);return this._toggleFilterClasses(c.filtered,c.concealed),this.lastFilter=a,"string"==typeof a&&(this.group=a),c.filtered},w.prototype._getFilteredSets=function(b,c){var d=a(),e=a();return b===q?d=c:f(c,function(c){var f=a(c);this._doesPassFilter(b,f)?d=d.add(f):e=e.add(f)},this),{filtered:d,concealed:e}},w.prototype._doesPassFilter=function(b,c){if(a.isFunction(b))return b.call(c[0],c,this);var d=c.data(r),e=this.delimeter&&!a.isArray(d)?d.split(this.delimeter):d;return a.inArray(b,e)>-1},w.prototype._toggleFilterClasses=function(a,b){a.removeClass(w.ClassName.CONCEALED).addClass(w.ClassName.FILTERED),b.removeClass(w.ClassName.FILTERED).addClass(w.ClassName.CONCEALED)},w.prototype._initItems=function(a){a=a||this.$items,a.addClass([w.ClassName.SHUFFLE_ITEM,w.ClassName.FILTERED].join(" ")),a.css(this.itemCss).data("position",{x:0,y:0})},w.prototype._updateItemCount=function(){this.visibleItems=this._getFilteredItems().length},w.prototype._setTransition=function(a){a.style[h]=m+" "+this.speed+"ms "+this.easing+", opacity "+this.speed+"ms "+this.easing},w.prototype._setTransitions=function(a){a=a||this.$items,f(a,function(a){this._setTransition(a)},this)},w.prototype._setSequentialDelay=function(a){this.supported&&f(a,function(a,b){a.style[i]="0ms,"+(b+1)*this.sequentialFadeDelay+"ms"},this)},w.prototype._getItems=function(){return this.$el.children(this.itemSelector)},w.prototype._getFilteredItems=function(){return this.$items.filter("."+w.ClassName.FILTERED)},w.prototype._getConcealedItems=function(){return this.$items.filter("."+w.ClassName.CONCEALED)},w.prototype._getColumnSize=function(b,c){var d;return d=a.isFunction(this.columnWidth)?this.columnWidth(c):this.useSizer?w._getPreciseDimension(this.sizer,"width"):this.columnWidth?this.columnWidth:this.$items.length>0?w._getOuterWidth(this.$items[0],!0):c,0===d&&(d=c),d+b},w.prototype._getGutterSize=function(b){var c;return c=a.isFunction(this.gutterWidth)?this.gutterWidth(b):this.useSizer?w._getPreciseDimension(this.sizer,"marginLeft"):this.gutterWidth},w.prototype._setColumns=function(a){var b=a||w._getOuterWidth(this.element),c=this._getGutterSize(b),d=this._getColumnSize(c,b),e=(b+c)/d;Math.abs(Math.round(e)-e)<.03&&(e=Math.round(e)),this.cols=Math.max(Math.floor(e),1),this.containerWidth=b,this.colWidth=d},w.prototype._setContainerSize=function(){this.$el.css("height",this._getContainerSize())},w.prototype._getContainerSize=function(){return Math.max.apply(Math,this.colYs)},w.prototype._fire=function(a,b){this.$el.trigger(a+"."+p,b&&b.length?b:[this])},w.prototype._layout=function(b,c){var d=0;f(b,function(b){var e=a(b),f=e.data(),g=f.position,h=this._getItemPosition(e);if(e.data("position",h),h.x!==g.x||h.y!==g.y||f.scale!==s){var i={$item:e,x:h.x,y:h.y,scale:s,opacity:c?0:1,skipTransition:!!c,callfront:function(){c||e.css("visibility","visible")},callback:function(){c&&e.css("visibility","hidden")}};d++,this.styleQueue.push(i)}},this),this._processStyleQueue(),b.length>0&&0===d&&this._layoutEnd(),this._setContainerSize()},w.prototype._resetCols=function(){var a=this.cols;for(this.colYs=[];a--;)this.colYs.push(0)},w.prototype._getItemPosition=function(a){var b=w._getOuterWidth(a[0],!0),c=b/this.colWidth;Math.abs(Math.round(c)-c)<.03&&(c=Math.round(c));var d=Math.min(Math.ceil(c),this.cols);if(1===d)return this._placeItem(a,this.colYs);var e,f,g=this.cols+1-d,h=[];for(f=0;g>f;f++)e=this.colYs.slice(f,f+d),h[f]=Math.max.apply(Math,e);return this._placeItem(a,h)},w.prototype._placeItem=function(a,b){for(var c=Math.min.apply(Math,b),d=0,e=0,f=b.length;f>e;e++)if(b[e]>=c-this.buffer&&b[e]<=c+this.buffer){d=e;break}var g={x:Math.round(this.colWidth*d+this.offset.left),y:Math.round(c+this.offset.top)},h=c+w._getOuterHeight(a[0],!0),i=this.cols+1-f;for(e=0;i>e;e++)this.colYs[d+e]=h;return g},w.prototype._shrink=function(b){var c=b||this._getConcealedItems();f(c,function(b){var c=a(b),d=c.data(),e=d.scale===t;if(!e){var f={$item:c,x:d.position.x,y:d.position.y,scale:t,opacity:0,callback:function(){c.css("visibility","hidden")}};this.styleQueue.push(f)}},this)},w.prototype._onResize=function(){if(this.enabled&&!this.destroyed){var a=w._getOuterWidth(this.element);a!==this.containerWidth&&this.update()}},w.prototype._getStylesForTransition=function(a){var b={opacity:a.opacity};return this.supported?a.x!==c&&(b[l]=w._getItemTransformString(a.x,a.y,a.scale)):(b.left=a.x,b.top=a.y),b},w.prototype._transition=function(b){b.$item.data("scale",b.scale);var c=this._getStylesForTransition(b);this._startItemAnimation(b.$item,c,b.callfront||a.noop,b.callback||a.noop)},w.prototype._startItemAnimation=function(b,c,d,e){function f(b){b.target===b.currentTarget&&(a(b.target).off(k,f),e())}d(),this.supported?(b.css(c),this.initialized?b.on(k,f):e()):b.stop(!0).animate(c,this.speed,"swing",e)},w.prototype._processStyleQueue=function(){var b=a();f(this.styleQueue,function(a){a.skipTransition?this._styleImmediately(a):(b=b.add(a.$item),this._transition(a))},this),b.length>0&&(this.initialized&&this.supported?this._whenCollectionDone(b,k,this._layoutEnd):this._layoutEnd()),this.styleQueue.length=0},w.prototype._styleImmediately=function(a){w._skipTransition(a.$item[0],function(){a.$item.css(this._getStylesForTransition(a))},this)},w.prototype._layoutEnd=function(){setTimeout(a.proxy(this._fire,this,w.EventType.LAYOUT),0)},w.prototype._addItems=function(a,b,c){this._initItems(a),this._setTransitions(a),this.$items=this._getItems(),this._shrink(a),f(this.styleQueue,function(a){a.skipTransition=!0}),this._processStyleQueue(),b?this._addItemsToEnd(a,c):this.shuffle(this.lastFilter)},w.prototype._addItemsToEnd=function(a,b){var c=this._filter(null,a),d=c.get();this._updateItemCount(),this._layout(d,!0),b&&this.supported&&this._setSequentialDelay(d),this._revealAppended(d)},w.prototype._revealAppended=function(b){setTimeout(a.proxy(function(){f(b,function(b){this._transition({$item:a(b),opacity:1,scale:s})},this),this._whenCollectionDone(a(b),k,function(){a(b).css(i,"0ms")})},this),this.revealAppendedDelay)},w.prototype._whenCollectionDone=function(b,c,d){function e(b){b.target===b.currentTarget&&(a(b.target).off(c,e),f++,f===g&&d.call(h))}var f=0,g=b.length,h=this;b.on(c,e)},w.prototype.shuffle=function(a,b){this.enabled&&(a||(a=q),this._filter(a),this._updateItemCount(),this._shrink(),this.sort(b))},w.prototype.sort=function(a){this._resetCols();var b=a||this.lastSort,c=this._getFilteredItems().sorted(b);this._layout(c),this.lastSort=a},w.prototype.update=function(a){this.enabled&&(a||this._setColumns(),this.sort())},w.prototype.layout=function(){this.update(!0)},w.prototype.appended=function(a,b,c){this._addItems(a,b===!0,c!==!1)},w.prototype.disable=function(){this.enabled=!1},w.prototype.enable=function(a){this.enabled=!0,a!==!1&&this.update()},w.prototype.remove=function(a){a.length&&a.jquery&&(this._whenCollectionDone(a,k,function(){a.remove(),this.$items=this._getItems(),this.layout(),this._updateItemCount(),this._fire(w.EventType.REMOVED,[a,this]),a=null}),this._shrink(a),this._processStyleQueue())},w.prototype.destroy=function(){v.off("."+this.unique),this.$el.removeClass(p).removeAttr("style").removeData(p),this.$items.removeAttr("style").removeData("position").removeData("scale").removeClass([w.ClassName.CONCEALED,w.ClassName.FILTERED,w.ClassName.SHUFFLE_ITEM].join(" ")),this.$items=null,this.$el=null,this.sizer=null,this.element=null,this.destroyed=!0},w.options={group:q,speed:250,easing:"ease-out",itemSelector:"",sizer:null,gutterWidth:0,columnWidth:0,delimeter:null,buffer:0,initialSort:null,throttle:e,throttleTime:300,sequentialFadeDelay:150,supported:n},w.settings={useSizer:!1,itemCss:{position:"absolute",top:0,left:0},offset:{top:0,left:0},revealAppendedDelay:300,lastSort:{},lastFilter:q,enabled:!0,destroyed:!1,initialized:!1,styleQueue:[]},a.fn.shuffle=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),e=d.data(p);e?"string"==typeof b&&e[b]&&e[b].apply(e,c):(e=new w(this,b),d.data(p,e))})},a.fn.sorted=function(b){var d=a.extend({},a.fn.sorted.defaults,b),e=this.get(),f=!1;return e.length?d.randomize?g(e):(a.isFunction(d.by)&&e.sort(function(b,e){if(f)return 0;var g=d.by(a(b)),h=d.by(a(e));return g===c&&h===c?(f=!0,0):h>g||"sortFirst"===g||"sortLast"===h?-1:g>h||"sortLast"===g||"sortFirst"===h?1:0}),f?this.get():(d.reverse&&e.reverse(),e)):[]},a.fn.sorted.defaults={reverse:!1,by:null,randomize:!1},w}); \ No newline at end of file +window.Modernizr=function(a,b,c){function d(a){s.cssText=a}function e(a,b){return typeof a===b}function f(a,b){return!!~(""+a).indexOf(b)}function g(a,b){for(var d in a){var e=a[d];if(!f(e,"-")&&s[e]!==c)return"pfx"==b?e:!0}return!1}function h(a,b,d){for(var f in a){var g=b[a[f]];if(g!==c)return d===!1?a[f]:e(g,"function")?g.bind(d||b):g}return!1}function i(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),f=(a+" "+v.join(d+" ")+d).split(" ");return e(b,"string")||e(b,"undefined")?g(f,b):(f=(a+" "+w.join(d+" ")+d).split(" "),h(f,b,c))}var j,k,l,m="2.6.2",n={},o=!0,p=b.documentElement,q="modernizr",r=b.createElement(q),s=r.style,t=({}.toString," -webkit- -moz- -o- -ms- ".split(" ")),u="Webkit Moz O ms",v=u.split(" "),w=u.toLowerCase().split(" "),x={},y=[],z=y.slice,A=function(a,c,d,e){var f,g,h,i,j=b.createElement("div"),k=b.body,l=k||b.createElement("body");if(parseInt(d,10))for(;d--;)h=b.createElement("div"),h.id=e?e[d]:q+(d+1),j.appendChild(h);return f=["­",'"].join(""),j.id=q,(k?j:l).innerHTML+=f,l.appendChild(j),k||(l.style.background="",l.style.overflow="hidden",i=p.style.overflow,p.style.overflow="hidden",p.appendChild(l)),g=c(j,a),k?j.parentNode.removeChild(j):(l.parentNode.removeChild(l),p.style.overflow=i),!!g},B={}.hasOwnProperty;l=e(B,"undefined")||e(B.call,"undefined")?function(a,b){return b in a&&e(a.constructor.prototype[b],"undefined")}:function(a,b){return B.call(a,b)},Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError;var c=z.call(arguments,1),d=function(){if(this instanceof d){var e=function(){};e.prototype=b.prototype;var f=new e,g=b.apply(f,c.concat(z.call(arguments)));return Object(g)===g?g:f}return b.apply(a,c.concat(z.call(arguments)))};return d}),x.csstransforms=function(){return!!i("transform")},x.csstransforms3d=function(){var a=!!i("perspective");return a&&"webkitPerspective"in p.style&&A("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b){a=9===b.offsetLeft&&3===b.offsetHeight}),a},x.csstransitions=function(){return i("transition")};for(var C in x)l(x,C)&&(k=C.toLowerCase(),n[k]=x[C](),y.push((n[k]?"":"no-")+k));return n.addTest=function(a,b){if("object"==typeof a)for(var d in a)l(a,d)&&n.addTest(d,a[d]);else{if(a=a.toLowerCase(),n[a]!==c)return n;b="function"==typeof b?b():b,"undefined"!=typeof o&&o&&(p.className+=" "+(b?"":"no-")+a),n[a]=b}return n},d(""),r=j=null,n._version=m,n._prefixes=t,n._domPrefixes=w,n._cssomPrefixes=v,n.testProp=function(a){return g([a])},n.testAllProps=i,n.testStyles=A,n.prefixed=function(a,b,c){return b?i(a,b,c):i(a,"pfx")},p.className=p.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(o?" js "+y.join(" "):""),n}(this,this.document),function(a){"function"==typeof define&&define.amd?define(["jquery","modernizr"],a):a(window.$,window.Modernizr)}(function(a,b,c){"use strict";function d(a){return a?a.replace(/([A-Z])/g,function(a,b){return"-"+b.toLowerCase()}).replace(/^ms-/,"-ms-"):""}function e(b,c,d){var e,f,g,h=null,i=0;d=d||{};var j=function(){i=d.leading===!1?0:a.now(),h=null,g=b.apply(e,f),e=f=null};return function(){var k=a.now();i||d.leading!==!1||(i=k);var l=c-(k-i);return e=this,f=arguments,0>=l||l>c?(clearTimeout(h),h=null,i=k,g=b.apply(e,f),e=f=null):h||d.trailing===!1||(h=setTimeout(j,l)),g}}function f(a,b,c){for(var d=0,e=a.length;e>d;d++)if(b.call(c,a[d],d,a)==={})return}function g(b,c,d){return setTimeout(a.proxy(b,c),d||0)}function h(a){var b,c,d=a.length;if(!d)return a;for(;--d;)c=Math.floor(Math.random()*(d+1)),b=a[c],a[c]=a[d],a[d]=b;return a}if("object"!=typeof b)throw new Error("Shuffle.js requires Modernizr.\nhttp://vestride.github.io/Shuffle/#dependencies");var i=b.prefixed("transition"),j=b.prefixed("transitionDelay"),k=b.prefixed("transitionDuration"),l={WebkitTransition:"webkitTransitionEnd",transition:"transitionend"}[i],m=b.prefixed("transform"),n=d(m),o=b.csstransforms&&b.csstransitions,p=b.csstransforms3d,q="shuffle",r="all",s="groups",t=1,u=.001,v=0,w=a(window),x=function(b,c){c=c||{},a.extend(this,x.options,c,x.settings),this.$el=a(b),this.element=b,this.unique="shuffle_"+v++,this._fire(x.EventType.LOADING),this._init(),g(function(){this.initialized=!0,this._fire(x.EventType.DONE)},this,16)};return x.EventType={LOADING:"loading",DONE:"done",SORTED:"sorted",LAYOUT:"layout",REMOVED:"removed"},x.ClassName={BASE:q,SHUFFLE_ITEM:"shuffle-item",FILTERED:"filtered",CONCEALED:"concealed"},x._getItemTransformString=function(a,b,c){return p?"translate3d("+a+"px, "+b+"px, 0) scale3d("+c+", "+c+", 1)":"translate("+a+"px, "+b+"px) scale("+c+", "+c+")"},x._getPreciseDimension=function(b,c){var d;return d=window.getComputedStyle?window.getComputedStyle(b,null)[c]:a(b).css(c),parseFloat(d)},x._getOuterWidth=function(b,c){var d=b.offsetWidth;if(c){var e=a(b).css(["marginLeft","marginRight"]),f=parseFloat(e.marginLeft)||0,g=parseFloat(e.marginRight)||0;d+=f+g}return d},x._getOuterHeight=function(b,c){var d=b.offsetHeight;if(c){var e=a(b).css(["marginTop","marginBottom"]),f=parseFloat(e.marginTop)||0,g=parseFloat(e.marginBottom)||0;d+=f+g}return d},x._skipTransition=function(a,b,c){var d=a.style[k];a.style[k]="0ms",b.call(c);var e=a.offsetWidth;e=null,a.style[k]=d},x.prototype._init=function(){this.$items=this._getItems(),this.sizer=this._getElementOption(this.sizer),this.sizer&&(this.useSizer=!0),this.$el.addClass(x.ClassName.BASE),this._initItems(),w.on("resize."+q+"."+this.unique,this._getResizeFunction());var a=this.$el.css(["paddingLeft","paddingRight","position"]),b=x._getOuterWidth(this.element);"static"===a.position&&(this.element.style.position="relative"),this.offset={left:parseInt(a.paddingLeft,10)||0,top:parseInt(a.paddingTop,10)||0},this._setColumns(parseInt(b,10)),this.shuffle(this.group,this.initialSort),this.supported&&g(function(){this._setTransitions(),this.element.style[i]="height "+this.speed+"ms "+this.easing},this)},x.prototype._getResizeFunction=function(){var b=a.proxy(this._onResize,this);return this.throttle?this.throttle(b,this.throttleTime):b},x.prototype._getElementOption=function(a){return"string"==typeof a?this.$el.find(a)[0]||null:a&&a.nodeType&&1===a.nodeType?a:a&&a.jquery?a[0]:null},x.prototype._filter=function(a,b){a=a||this.lastFilter,b=b||this.$items;var c=this._getFilteredSets(a,b);return this._toggleFilterClasses(c.filtered,c.concealed),this.lastFilter=a,"string"==typeof a&&(this.group=a),c.filtered},x.prototype._getFilteredSets=function(b,c){var d=a(),e=a();return b===r?d=c:f(c,function(c){var f=a(c);this._doesPassFilter(b,f)?d=d.add(f):e=e.add(f)},this),{filtered:d,concealed:e}},x.prototype._doesPassFilter=function(b,c){if(a.isFunction(b))return b.call(c[0],c,this);var d=c.data(s),e=this.delimeter&&!a.isArray(d)?d.split(this.delimeter):d;return a.inArray(b,e)>-1},x.prototype._toggleFilterClasses=function(a,b){a.removeClass(x.ClassName.CONCEALED).addClass(x.ClassName.FILTERED),b.removeClass(x.ClassName.FILTERED).addClass(x.ClassName.CONCEALED)},x.prototype._initItems=function(a){a=a||this.$items,a.addClass([x.ClassName.SHUFFLE_ITEM,x.ClassName.FILTERED].join(" ")),a.css(this.itemCss).data("position",{x:0,y:0}).data("scale",t)},x.prototype._updateItemCount=function(){this.visibleItems=this._getFilteredItems().length},x.prototype._setTransition=function(a){a.style[i]=n+" "+this.speed+"ms "+this.easing+", opacity "+this.speed+"ms "+this.easing},x.prototype._setTransitions=function(a){a=a||this.$items,f(a,function(a){this._setTransition(a)},this)},x.prototype._setSequentialDelay=function(a){this.supported&&f(a,function(a,b){a.style[j]="0ms,"+(b+1)*this.sequentialFadeDelay+"ms"},this)},x.prototype._getItems=function(){return this.$el.children(this.itemSelector)},x.prototype._getFilteredItems=function(){return this.$items.filter("."+x.ClassName.FILTERED)},x.prototype._getConcealedItems=function(){return this.$items.filter("."+x.ClassName.CONCEALED)},x.prototype._getColumnSize=function(b,c){var d;return d=a.isFunction(this.columnWidth)?this.columnWidth(c):this.useSizer?x._getPreciseDimension(this.sizer,"width"):this.columnWidth?this.columnWidth:this.$items.length>0?x._getOuterWidth(this.$items[0],!0):c,0===d&&(d=c),d+b},x.prototype._getGutterSize=function(b){var c;return c=a.isFunction(this.gutterWidth)?this.gutterWidth(b):this.useSizer?x._getPreciseDimension(this.sizer,"marginLeft"):this.gutterWidth},x.prototype._setColumns=function(a){var b=a||x._getOuterWidth(this.element),c=this._getGutterSize(b),d=this._getColumnSize(c,b),e=(b+c)/d;Math.abs(Math.round(e)-e)<.03&&(e=Math.round(e)),this.cols=Math.max(Math.floor(e),1),this.containerWidth=b,this.colWidth=d},x.prototype._setContainerSize=function(){this.$el.css("height",this._getContainerSize())},x.prototype._getContainerSize=function(){return Math.max.apply(Math,this.colYs)},x.prototype._fire=function(a,b){this.$el.trigger(a+"."+q,b&&b.length?b:[this])},x.prototype._layout=function(b,c){f(b,function(b){var d=a(b),e=d.data(),f=e.position,g=this._getItemPosition(d);if(d.data("position",g),d.data("scale",t),g.x!==f.x||g.y!==f.y||e.scale!==t){var h={$item:d,x:g.x,y:g.y,scale:t,opacity:c?0:1,skipTransition:!!c,callfront:function(){c||d.css("visibility","visible")},callback:function(){c&&d.css("visibility","hidden")}};this.styleQueue.push(h)}},this),this._processStyleQueue(),this._setContainerSize()},x.prototype._resetCols=function(){var a=this.cols;for(this.colYs=[];a--;)this.colYs.push(0)},x.prototype._getItemPosition=function(a){var b=x._getOuterWidth(a[0],!0),c=b/this.colWidth;Math.abs(Math.round(c)-c)<.03&&(c=Math.round(c));var d=Math.min(Math.ceil(c),this.cols);if(1===d)return this._placeItem(a,this.colYs);var e,f,g=this.cols+1-d,h=[];for(f=0;g>f;f++)e=this.colYs.slice(f,f+d),h[f]=Math.max.apply(Math,e);return this._placeItem(a,h)},x.prototype._placeItem=function(a,b){for(var c=Math.min.apply(Math,b),d=0,e=0,f=b.length;f>e;e++)if(b[e]>=c-this.buffer&&b[e]<=c+this.buffer){d=e;break}var g={x:Math.round(this.colWidth*d+this.offset.left),y:Math.round(c+this.offset.top)},h=c+x._getOuterHeight(a[0],!0),i=this.cols+1-f;for(e=0;i>e;e++)this.colYs[d+e]=h;return g},x.prototype._shrink=function(b){var c=b||this._getConcealedItems();f(c,function(b){var c=a(b),d=c.data(),e=d.scale===u;if(!e){c.data("scale",u);var f={$item:c,x:d.position.x,y:d.position.y,scale:u,opacity:0,callback:function(){c.css("visibility","hidden")}};this.styleQueue.push(f)}},this)},x.prototype._onResize=function(){if(this.enabled&&!this.destroyed&&!this.isTransitioning){var a=x._getOuterWidth(this.element);a!==this.containerWidth&&this.update()}},x.prototype._getStylesForTransition=function(a){var b={opacity:a.opacity};return this.supported?a.x!==c&&(b[m]=x._getItemTransformString(a.x,a.y,a.scale)):(b.left=a.x,b.top=a.y),b},x.prototype._transition=function(b){var c=this._getStylesForTransition(b);this._startItemAnimation(b.$item,c,b.callfront||a.noop,b.callback||a.noop)},x.prototype._startItemAnimation=function(b,c,d,e){function f(b){b.target===b.currentTarget&&(a(b.target).off(l,f),e())}if(d(),!this.initialized)return b.css(c),void e();if(this.supported)b.css(c),b.on(l,f);else{var g=b.stop(!0).animate(c,this.speed,"swing",e);this._animations.push(g.promise())}},x.prototype._processStyleQueue=function(b){var c=a();f(this.styleQueue,function(a){a.skipTransition?this._styleImmediately(a):(c=c.add(a.$item),this._transition(a))},this),c.length>0&&this.initialized?(this.isTransitioning=!0,this.supported?this._whenCollectionDone(c,l,this._movementFinished):this._whenAnimationsDone(this._movementFinished)):b||g(this._layoutEnd,this),this.styleQueue.length=0},x.prototype._styleImmediately=function(a){x._skipTransition(a.$item[0],function(){a.$item.css(this._getStylesForTransition(a))},this)},x.prototype._movementFinished=function(){this.isTransitioning=!1,this._layoutEnd()},x.prototype._layoutEnd=function(){this._fire(x.EventType.LAYOUT)},x.prototype._addItems=function(a,b,c){this._initItems(a),this._setTransitions(a),this.$items=this._getItems(),this._shrink(a),f(this.styleQueue,function(a){a.skipTransition=!0}),this._processStyleQueue(!0),b?this._addItemsToEnd(a,c):this.shuffle(this.lastFilter)},x.prototype._addItemsToEnd=function(a,b){var c=this._filter(null,a),d=c.get();this._updateItemCount(),this._layout(d,!0),b&&this.supported&&this._setSequentialDelay(d),this._revealAppended(d)},x.prototype._revealAppended=function(b){g(function(){f(b,function(b){this._transition({$item:a(b),opacity:1,scale:t})},this),this._whenCollectionDone(a(b),l,function(){a(b).css(j,"0ms"),this._movementFinished()})},this,this.revealAppendedDelay)},x.prototype._whenCollectionDone=function(b,c,d){function e(b){b.target===b.currentTarget&&(a(b.target).off(c,e),f++,f===g&&d.call(h))}var f=0,g=b.length,h=this;b.on(c,e)},x.prototype._whenAnimationsDone=function(b){a.when.apply(null,this._animations).always(a.proxy(function(){this._animations.length=0,b.call(this)},this))},x.prototype.shuffle=function(a,b){this.enabled&&!this.isTransitioning&&(a||(a=r),this._filter(a),this._updateItemCount(),this._shrink(),this.sort(b))},x.prototype.sort=function(a){if(this.enabled&&!this.isTransitioning){this._resetCols();var b=a||this.lastSort,c=this._getFilteredItems().sorted(b);this._layout(c),this.lastSort=a}},x.prototype.update=function(a){this.enabled&&!this.isTransitioning&&(a||this._setColumns(),this.sort())},x.prototype.layout=function(){this.update(!0)},x.prototype.appended=function(a,b,c){this._addItems(a,b===!0,c!==!1)},x.prototype.disable=function(){this.enabled=!1},x.prototype.enable=function(a){this.enabled=!0,a!==!1&&this.update()},x.prototype.remove=function(b){function c(){b.remove(),this.$items=this._getItems(),this._updateItemCount(),this._fire(x.EventType.REMOVED,[b,this]),b=null}b.length&&b.jquery&&(this._toggleFilterClasses(a(),b),this._shrink(b),this.sort(),this.$el.one(x.EventType.LAYOUT+"."+q,a.proxy(c,this)))},x.prototype.destroy=function(){w.off("."+this.unique),this.$el.removeClass(q).removeAttr("style").removeData(q),this.$items.removeAttr("style").removeData("position").removeData("scale").removeClass([x.ClassName.CONCEALED,x.ClassName.FILTERED,x.ClassName.SHUFFLE_ITEM].join(" ")),this.$items=null,this.$el=null,this.sizer=null,this.element=null,this.destroyed=!0},x.options={group:r,speed:250,easing:"ease-out",itemSelector:"",sizer:null,gutterWidth:0,columnWidth:0,delimeter:null,buffer:0,initialSort:null,throttle:e,throttleTime:300,sequentialFadeDelay:150,supported:o},x.settings={useSizer:!1,itemCss:{position:"absolute",top:0,left:0,visibility:"visible"},offset:{top:0,left:0},revealAppendedDelay:300,lastSort:{},lastFilter:r,enabled:!0,destroyed:!1,initialized:!1,_animations:[],styleQueue:[]},a.fn.shuffle=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),e=d.data(q);e?"string"==typeof b&&e[b]&&e[b].apply(e,c):(e=new x(this,b),d.data(q,e))})},a.fn.sorted=function(b){var d=a.extend({},a.fn.sorted.defaults,b),e=this.get(),f=!1;return e.length?d.randomize?h(e):(a.isFunction(d.by)&&e.sort(function(b,e){if(f)return 0;var g=d.by(a(b)),h=d.by(a(e));return g===c&&h===c?(f=!0,0):h>g||"sortFirst"===g||"sortLast"===h?-1:g>h||"sortLast"===g||"sortFirst"===h?1:0}),f?this.get():(d.reverse&&e.reverse(),e)):[]},a.fn.sorted.defaults={reverse:!1,by:null,randomize:!1},x}); \ No newline at end of file diff --git a/src/shuffle.js b/src/shuffle.js index df90b83..8d3521f 100644 --- a/src/shuffle.js +++ b/src/shuffle.js @@ -91,6 +91,11 @@ function each(obj, iterator, context) { } } +function defer(fn, context, wait) { + return setTimeout( $.proxy( fn, context ), wait || 0 ); +} + + // Used for unique instance variables var id = 0; var $window = $( window ); @@ -116,10 +121,10 @@ var Shuffle = function( element, options ) { // Dispatch the done event asynchronously so that people can bind to it after // Shuffle has been initialized. - setTimeout( $.proxy(function() { + defer(function() { this.initialized = true; this._fire( Shuffle.EventType.DONE ); - }, this), 16 ); + }, this, 16); }; @@ -300,10 +305,10 @@ Shuffle.prototype._init = function() { // The shuffle items haven't had transitions set on them yet // so the user doesn't see the first layout. Set them now that the first layout is done. if ( this.supported ) { - setTimeout($.proxy(function() { + defer(function() { this._setTransitions(); this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing; - }, this), 0); + }, this); } }; @@ -457,7 +462,7 @@ Shuffle.prototype._initItems = function( $items ) { Shuffle.ClassName.SHUFFLE_ITEM, Shuffle.ClassName.FILTERED ].join(' ')); - $items.css( this.itemCss ).data('position', {x: 0, y: 0}); + $items.css( this.itemCss ).data('position', {x: 0, y: 0}).data('scale', DEFAULT_SCALE); }; /** @@ -638,8 +643,6 @@ Shuffle.prototype._fire = function( name, args ) { * @param {boolean} isOnlyPosition If true this will position the items with zero opacity. */ Shuffle.prototype._layout = function( items, isOnlyPosition ) { - var added = 0; - each(items, function( item ) { var $item = $(item); var itemData = $item.data(); @@ -648,6 +651,7 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { // Save data for shrink $item.data( 'position', pos ); + $item.data( 'scale', DEFAULT_SCALE ); // If the item will not change its position, do not add it to the render // queue. Transitions don't fire when setting a property to the same value. @@ -674,7 +678,6 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { } }; - added++; this.styleQueue.push( transitionObj ); }, this); @@ -682,12 +685,6 @@ Shuffle.prototype._layout = function( items, isOnlyPosition ) { // queue here with styles from the shrink method this._processStyleQueue(); - // A call to layout happened, but none of the newly filtered items will - // change position. Asynchronously fire the callback here. - if ( items.length > 0 && added === 0 ) { - this._layoutEnd(); - } - // Adjust the height of the container this._setContainerSize(); }; @@ -796,6 +793,8 @@ Shuffle.prototype._shrink = function( $collection ) { return; } + $item.data( 'scale', CONCEALED_SCALE ); + var transitionObj = { $item: $item, x: itemData.position.x, @@ -817,7 +816,7 @@ Shuffle.prototype._shrink = function( $collection ) { */ Shuffle.prototype._onResize = function() { // If shuffle is disabled, destroyed, don't do anything - if ( !this.enabled || this.destroyed ) { + if ( !this.enabled || this.destroyed || this.isTransitioning ) { return; } @@ -870,16 +869,13 @@ Shuffle.prototype._getStylesForTransition = function( opts ) { * @private */ Shuffle.prototype._transition = function( opts ) { - opts.$item.data('scale', opts.scale); - var styles = this._getStylesForTransition( opts ); this._startItemAnimation( opts.$item, styles, opts.callfront || $.noop, opts.callback || $.noop ); }; Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) { - callfront(); - + // Transition end handler removes its listener. function handleTransitionEnd( evt ) { // Make sure this event handler has not bubbled up from a child. if ( evt.target === evt.currentTarget ) { @@ -888,29 +884,34 @@ Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, call } } + callfront(); + + // Transitions are not set until shuffle has loaded to avoid the initial transition. + if ( !this.initialized ) { + $item.css( styles ); + callback(); + return; + } + // Use CSS Transforms if we have them if ( this.supported ) { - $item.css( styles ); - // Transitions are not set until shuffle has loaded to avoid the initial transition. - if ( this.initialized ) { - // Namespaced because the reveal appended function also wants to know - // about the transition end event. - $item.on( TRANSITIONEND, handleTransitionEnd ); - } else { - callback(); - } + // Namespaced because the reveal appended function also wants to know + // about the transition end event. + $item.on( TRANSITIONEND, handleTransitionEnd ); // Use jQuery to animate left/top } else { - $item.stop( true ).animate( styles, this.speed, 'swing', callback ); + var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback ); + this._animations.push( anim.promise() ); } }; -Shuffle.prototype._processStyleQueue = function() { +Shuffle.prototype._processStyleQueue = function( noLayout ) { var $transitions = $(); + // Iterate over the queue and keep track of ones that use transitions. each(this.styleQueue, function( transitionObj ) { if ( transitionObj.skipTransition ) { this._styleImmediately( transitionObj ); @@ -920,13 +921,24 @@ Shuffle.prototype._processStyleQueue = function() { } }, this); - if ( $transitions.length > 0 ) { - if ( this.initialized && this.supported ) { - // TODO: Transitioning flag. - this._whenCollectionDone( $transitions, TRANSITIONEND, this._layoutEnd ); + + if ( $transitions.length > 0 && this.initialized ) { + // Set flag that shuffle is currently in motion. + this.isTransitioning = true; + + if ( this.supported ) { + this._whenCollectionDone( $transitions, TRANSITIONEND, this._movementFinished ); + + // The _transition function appends a promise to the animations array. + // When they're all complete, do things. } else { - this._layoutEnd(); + this._whenAnimationsDone( this._movementFinished ); } + + // A call to layout happened, but none of the newly filtered items will + // change position. Asynchronously fire the callback here. + } else if ( !noLayout ) { + defer( this._layoutEnd, this ); } // Remove everything in the style queue @@ -939,8 +951,13 @@ Shuffle.prototype._styleImmediately = function( opts ) { }, this); }; +Shuffle.prototype._movementFinished = function() { + this.isTransitioning = false; + this._layoutEnd(); +}; + Shuffle.prototype._layoutEnd = function() { - setTimeout($.proxy( this._fire, this, Shuffle.EventType.LAYOUT ), 0); + this._fire( Shuffle.EventType.LAYOUT ); }; Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { @@ -958,7 +975,9 @@ Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { each(this.styleQueue, function( transitionObj ) { transitionObj.skipTransition = true; }); - this._processStyleQueue(); + + // Apply shrink positions, but do not cause a layout event. + this._processStyleQueue( true ); if ( addToEnd ) { this._addItemsToEnd( $newItems, isSequential ); @@ -991,7 +1010,7 @@ Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) { * @private */ Shuffle.prototype._revealAppended = function( newFilteredItems ) { - setTimeout($.proxy(function() { + defer(function() { each(newFilteredItems, function( el ) { this._transition({ $item: $(el), @@ -1002,8 +1021,9 @@ Shuffle.prototype._revealAppended = function( newFilteredItems ) { this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() { $(newFilteredItems).css( TRANSITION_DELAY, '0ms' ); + this._movementFinished(); }); - }, this), this.revealAppendedDelay); + }, this, this.revealAppendedDelay); }; @@ -1022,7 +1042,6 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba function handleEventName( evt ) { if ( evt.target === evt.currentTarget ) { $( evt.target ).off( eventName, handleEventName ); - done++; // Execute callback if all items have emitted the correct event. @@ -1037,6 +1056,19 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba }; +/** + * Execute a callback after jQuery `animate` for a collection has finished. + * @param {Function} callback Callback to execute when they're done. + * @private + */ +Shuffle.prototype._whenAnimationsDone = function( callback ) { + $.when.apply( null, this._animations ).always( $.proxy( function() { + this._animations.length = 0; + callback.call( this ); + }, this )); +}; + + /** * Public Methods */ @@ -1047,7 +1079,7 @@ Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callba * @param {Object} [sortObj] A sort object which can sort the filtered set */ Shuffle.prototype.shuffle = function( category, sortObj ) { - if ( !this.enabled ) { + if ( !this.enabled || this.isTransitioning ) { return; } @@ -1072,14 +1104,16 @@ Shuffle.prototype.shuffle = function( category, sortObj ) { * @param {Object} opts the options object for the sorted plugin */ Shuffle.prototype.sort = function( opts ) { - this._resetCols(); + if ( this.enabled && !this.isTransitioning ) { + this._resetCols(); - var sortOptions = opts || this.lastSort; - var items = this._getFilteredItems().sorted( sortOptions ); + var sortOptions = opts || this.lastSort; + var items = this._getFilteredItems().sorted( sortOptions ); - this._layout( items ); + this._layout( items ); - this.lastSort = opts; + this.lastSort = opts; + } }; /** @@ -1088,7 +1122,7 @@ Shuffle.prototype.sort = function( opts ) { * recalculated. */ Shuffle.prototype.update = function( isOnlyLayout ) { - if ( this.enabled ) { + if ( this.enabled && !this.isTransitioning ) { if ( !isOnlyLayout ) { // Get updated colCount @@ -1150,25 +1184,27 @@ Shuffle.prototype.remove = function( $collection ) { return; } - this._whenCollectionDone( $collection, TRANSITIONEND, function() { + function handleRemoved() { // Remove the collection in the callback $collection.remove(); - // Update the items, layout, count and fire off `removed` event + // Update things now that elements have been removed. this.$items = this._getItems(); - this.layout(); this._updateItemCount(); + this._fire( Shuffle.EventType.REMOVED, [ $collection, this ] ); // Let it get garbage collected $collection = null; - }); + } // Hide collection first. + this._toggleFilterClasses( $(), $collection ); this._shrink( $collection ); - // Process changes - this._processStyleQueue(); + this.sort(); + + this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) ); }; /** @@ -1234,7 +1270,8 @@ Shuffle.settings = { itemCss : { // default CSS for each item position: 'absolute', top: 0, - left: 0 + left: 0, + visibility: 'visible' }, offset: { top: 0, left: 0 }, revealAppendedDelay: 300, @@ -1243,6 +1280,7 @@ Shuffle.settings = { enabled: true, destroyed: false, initialized: false, + _animations: [], styleQueue: [] }; diff --git a/test/_SpecRunner.html b/test/_SpecRunner.html index f4e6601..34412b1 100644 --- a/test/_SpecRunner.html +++ b/test/_SpecRunner.html @@ -21,7 +21,7 @@ - + diff --git a/test/specs.js b/test/specs.js index a4e648b..9e50ece 100644 --- a/test/specs.js +++ b/test/specs.js @@ -6,12 +6,15 @@ describe('Shuffle.js', function() { describe('regular markup', function() { + var $shuffle; beforeEach(function() { loadFixtures('regular.html'); + $shuffle = $('#regular-shuffle'); }); afterEach(function(done) { - var shuffle = $('#regular-shuffle').data('shuffle'); + var shuffle = $shuffle.data('shuffle'); + $shuffle = null; if (!shuffle) { return done(); @@ -27,6 +30,7 @@ describe('Shuffle.js', function() { } else { shuffle.$el.one('done.shuffle', finish); } + }); it('should get default options', function() { @@ -46,7 +50,6 @@ describe('Shuffle.js', function() { expect(shuffle.throttleTime).toBe(300); expect(shuffle.sequentialFadeDelay).toBe(150); expect(shuffle.useSizer).toBe(false); - expect(shuffle.supported).toBe(Modernizr.csstransforms && Modernizr.csstransitions); expect(shuffle.unique).toBe('shuffle_0'); }); @@ -68,7 +71,6 @@ describe('Shuffle.js', function() { it('should be 3 columns with gutters', function() { - var $shuffle = $('#regular-shuffle'); $shuffle.css({ width: '1000px' }); @@ -90,7 +92,6 @@ describe('Shuffle.js', function() { it('can have a function for columns and gutters', function() { - var $shuffle = $('#regular-shuffle'); $shuffle.css({ width: '1000px' }); @@ -118,7 +119,6 @@ describe('Shuffle.js', function() { }); it('can filter by the data attribute', function(done) { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -156,7 +156,6 @@ describe('Shuffle.js', function() { }); it('can shuffle by function', function(done) { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -174,6 +173,34 @@ describe('Shuffle.js', function() { var $filteredItems = $('#item1, #item2, #item3, #item4, #item5'); expect($concealedItems).toHaveClass('concealed'); expect($filteredItems).toHaveClass('filtered'); + expect(shuffle.isTransitioning).toBe(false); + done(); + } + + // First shuffle is async. + $shuffle.one('done.shuffle', first); + }); + + it('can shuffle by function without transitions', function(done) { + var shuffle = $shuffle.shuffle({ + speed: 100, + supported: false + }).data('shuffle'); + + function first() { + shuffle.shuffle(function($el) { + return parseInt($el.attr('id').substring(4), 10) <= 5; + }); + $shuffle.one('layout.shuffle', second); + } + + function second() { + expect(shuffle.visibleItems).toBe(5); + var $concealedItems = $('#item6, #item7, #item8, #item9, #item10'); + var $filteredItems = $('#item1, #item2, #item3, #item4, #item5'); + expect($concealedItems).toHaveClass('concealed'); + expect($filteredItems).toHaveClass('filtered'); + expect(shuffle.isTransitioning).toBe(false); done(); } @@ -182,7 +209,6 @@ describe('Shuffle.js', function() { }); it('can initialize filtered and the category parameter is optional', function(done) { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100, group: 'design' @@ -200,7 +226,6 @@ describe('Shuffle.js', function() { }); it('can initialize sorted', function() { - var $shuffle = $('#regular-shuffle'); var sortObj = { by: function($el) { @@ -216,14 +241,11 @@ describe('Shuffle.js', function() { expect(shuffle.lastSort).toEqual(sortObj); }); - it('can add items', function(done) { - var $shuffle = $('#regular-shuffle'); - var shuffle = $shuffle.shuffle({ - speed: 100, - group: 'black' - }).data('shuffle'); + describe('inserting elements', function() { - function first() { + var $collection; + + beforeEach(function() { var $eleven = $('
', { 'class': 'item', 'data-age': 36, @@ -239,23 +261,64 @@ describe('Shuffle.js', function() { text: 'Person 12' }); - var $collection = $eleven.add($twelve); - $shuffle.append($collection); - shuffle.appended($collection); + $collection = $eleven.add($twelve); + }); - // Already 2 in the items, plus number 11. - expect(shuffle.visibleItems).toBe(3); + afterEach(function() { + $collection = null; + }); - done(); - } + it('can add items', function(done) { + var shuffle = $shuffle.shuffle({ + speed: 100, + group: 'black' + }).data('shuffle'); + + function first() { + $shuffle.append($collection); + shuffle.appended($collection); + + // Already 2 in the items, plus number 11. + expect(shuffle.visibleItems).toBe(3); + + + $shuffle.one('layout.shuffle', function() { + expect(shuffle.isTransitioning).toBe(false); + done(); + }); + } + + $shuffle.one('done.shuffle', first); + }); + + it('can add items without transitions', function(done) { + var shuffle = $shuffle.shuffle({ + speed: 100, + group: 'black', + supported: false + }).data('shuffle'); + + function first() { + $shuffle.append($collection); + shuffle.appended($collection); + + // Already 2 in the items, plus number 11. + expect(shuffle.visibleItems).toBe(3); + + $shuffle.one('layout.shuffle', function() { + expect(shuffle.isTransitioning).toBe(false); + done(); + }); + } + + $shuffle.one('done.shuffle', first); + }); - $shuffle.one('done.shuffle', first); }); it('can call appended with different options', function() { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -273,36 +336,67 @@ describe('Shuffle.js', function() { }); - it('can remove items', function(done) { - var $shuffle = $('#regular-shuffle'); - var shuffle = $shuffle.shuffle({ - speed: 100 - }).data('shuffle'); + describe('removing elements', function() { + var shuffle; + var $itemsToRemove; + var onDone; + var onRemoved; - function first() { - var $itemsToRemove = $shuffle.children().slice(0, 2); + beforeEach(function() { + $itemsToRemove = $shuffle.children().slice(0, 2); + onDone = function() { + shuffle.remove($itemsToRemove); + $shuffle.one('removed.shuffle', onRemoved); + }; + }); + afterEach(function() { + $itemsToRemove = null; + shuffle = null; + onDone = null; + onRemoved = null; + }); - shuffle.remove($itemsToRemove); - $shuffle.one('removed.shuffle', second); - $shuffle.one('layout.shuffle', third); - } + it('can remove items', function(done) { + shuffle = $shuffle.shuffle({ + speed: 100 + }).data('shuffle'); - function second(evt, $items, instance) { - expect(instance.visibleItems).toBe(8); - expect($items[0].id).toBe('item1'); - expect($items[1].id).toBe('item2'); - expect($shuffle.children().length).toBe(8); - } + onRemoved = function(evt, $items, instance) { + expect(instance.visibleItems).toBe(8); + expect($items[0].id).toBe('item1'); + expect($items[1].id).toBe('item2'); + expect($shuffle.children().length).toBe(8); + expect(instance.isTransitioning).toBe(false); - function third() { - done(); - } + done(); + }; - $shuffle.one('done.shuffle', first); + $shuffle.one('done.shuffle', onDone); + }); + + + it('can remove items without transitions', function(done) { + shuffle = $shuffle.shuffle({ + speed: 100, + supported: false + }).data('shuffle'); + + onRemoved = function(evt, $items, instance) { + expect(instance.visibleItems).toBe(8); + expect($items[0].id).toBe('item1'); + expect($items[1].id).toBe('item2'); + expect($shuffle.children().length).toBe(8); + expect(instance.isTransitioning).toBe(false); + + done(); + }; + + $shuffle.one('done.shuffle', onDone); + }); }); + it('can get an element option', function() { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -321,7 +415,6 @@ describe('Shuffle.js', function() { }); it('can test elements against filters', function() { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -339,7 +432,6 @@ describe('Shuffle.js', function() { }); it('can initialize a collection of items', function() { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -372,7 +464,6 @@ describe('Shuffle.js', function() { it('should reset columns', function() { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -388,7 +479,6 @@ describe('Shuffle.js', function() { }); it('should destroy properly', function(done) { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -417,7 +507,6 @@ describe('Shuffle.js', function() { }); it('should not update or shuffle when disabled or destroyed', function(done) { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle'); @@ -445,7 +534,6 @@ describe('Shuffle.js', function() { }); it('should not update when the container is the same size', function(done) { - var $shuffle = $('#regular-shuffle'); var shuffle = $shuffle.shuffle({ speed: 100 }).data('shuffle');