| /*! |
| * Shuffle.js by @Vestride |
| * Categorize, sort, and filter a responsive grid of items. |
| * Dependencies: jQuery 1.9+, Modernizr 2.6.2+ |
| * @license MIT license |
| * @version 3.0.0 |
| */ |
| |
| /* Modernizr 2.6.2 (Custom Build) | MIT & BSD |
| * Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-cssclasses-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes |
| */ |
| window.Modernizr=function(a,b,c){function z(a){j.cssText=a}function A(a,b){return z(m.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a){var e=a[d];if(!C(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+o.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.csstransforms=function(){return!!F("transform")},q.csstransforms3d=function(){var a=!!F("perspective");return a&&"webkitPerspective"in g.style&&w("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},q.csstransitions=function(){return F("transition")};for(var G in q)y(q,G)&&(v=G.toLowerCase(),e[v]=q[G](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)y(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},z(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,e.prefixed=function(a,b,c){return b?F(a,b,c):F(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document); |
| |
| (function (factory) { |
| if (typeof define === 'function' && define.amd) { |
| define(['jquery', 'modernizr'], factory); |
| } else { |
| window.Shuffle = factory(window.jQuery, window.Modernizr); |
| } |
| })(function($, Modernizr, undefined) { |
| |
| 'use strict'; |
| |
| |
| // Validate Modernizr exists. |
| // Shuffle requires `csstransitions`, `csstransforms`, `csstransforms3d`, |
| // and `prefixed` to exist on the Modernizr object. |
| if (typeof Modernizr !== 'object') { |
| throw new Error('Shuffle.js requires Modernizr.\n' + |
| 'http://vestride.github.io/Shuffle/#dependencies'); |
| } |
| |
| |
| /** |
| * Returns css prefixed properties like `-webkit-transition` or `box-sizing` |
| * from `transition` or `boxSizing`, respectively. |
| * @param {(string|boolean)} prop Property to be prefixed. |
| * @return {string} The prefixed css property. |
| */ |
| function dashify( prop ) { |
| if (!prop) { |
| return ''; |
| } |
| |
| // Replace upper case with dash-lowercase, |
| // then fix ms- prefixes because they're not capitalized. |
| return prop.replace(/([A-Z])/g, function( str, m1 ) { |
| return '-' + m1.toLowerCase(); |
| }).replace(/^ms-/,'-ms-'); |
| } |
| |
| // Constant, prefixed variables. |
| var TRANSITION = Modernizr.prefixed('transition'); |
| var TRANSITION_DELAY = Modernizr.prefixed('transitionDelay'); |
| var TRANSITION_DURATION = Modernizr.prefixed('transitionDuration'); |
| |
| // Note(glen): Stock Android 4.1.x browser will fail here because it wrongly |
| // says it supports non-prefixed transitions. |
| // https://github.com/Modernizr/Modernizr/issues/897 |
| var TRANSITIONEND = { |
| 'WebkitTransition' : 'webkitTransitionEnd', |
| 'transition' : 'transitionend' |
| }[ TRANSITION ]; |
| |
| var TRANSFORM = Modernizr.prefixed('transform'); |
| var CSS_TRANSFORM = dashify(TRANSFORM); |
| |
| // Constants |
| var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions; |
| var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d; |
| var SHUFFLE = 'shuffle'; |
| var COLUMN_THRESHOLD = 0.3; |
| |
| // Configurable. You can change these constants to fit your application. |
| // The default scale and concealed scale, however, have to be different values. |
| var ALL_ITEMS = 'all'; |
| var FILTER_ATTRIBUTE_KEY = 'groups'; |
| var DEFAULT_SCALE = 1; |
| var CONCEALED_SCALE = 0.001; |
| |
| |
| // Underscore's throttle function. |
| function throttle(func, wait, options) { |
| var context, args, result; |
| var timeout = null; |
| var previous = 0; |
| options = options || {}; |
| var later = function() { |
| previous = options.leading === false ? 0 : $.now(); |
| timeout = null; |
| result = func.apply(context, args); |
| context = args = null; |
| }; |
| return function() { |
| var now = $.now(); |
| if (!previous && options.leading === false) { |
| previous = now; |
| } |
| var remaining = wait - (now - previous); |
| context = this; |
| args = arguments; |
| if (remaining <= 0 || remaining > wait) { |
| clearTimeout(timeout); |
| timeout = null; |
| previous = now; |
| result = func.apply(context, args); |
| context = args = null; |
| } else if (!timeout && options.trailing !== false) { |
| timeout = setTimeout(later, remaining); |
| } |
| return result; |
| }; |
| } |
| |
| function each(obj, iterator, context) { |
| for (var i = 0, length = obj.length; i < length; i++) { |
| if (iterator.call(context, obj[i], i, obj) === {}) { |
| return; |
| } |
| } |
| } |
| |
| function defer(fn, context, wait) { |
| return setTimeout( $.proxy( fn, context ), wait ); |
| } |
| |
| function arrayMax( array ) { |
| return Math.max.apply( Math, array ); |
| } |
| |
| function arrayMin( array ) { |
| return Math.min.apply( Math, array ); |
| } |
| |
| |
| /** |
| * Always returns a numeric value, given a value. |
| * @param {*} value Possibly numeric value. |
| * @return {number} `value` or zero if `value` isn't numeric. |
| * @private |
| */ |
| function getNumber(value) { |
| return $.isNumeric(value) ? value : 0; |
| } |
| |
| |
| /** |
| * Represents a coordinate pair. |
| * @param {number} [x=0] X. |
| * @param {number} [y=0] Y. |
| */ |
| var Point = function(x, y) { |
| this.x = getNumber( x ); |
| this.y = getNumber( y ); |
| }; |
| |
| |
| /** |
| * Whether two points are equal. |
| * @param {Point} a Point A. |
| * @param {Point} b Point B. |
| * @return {boolean} |
| */ |
| Point.equals = function(a, b) { |
| return a.x === b.x && a.y === b.y; |
| }; |
| |
| |
| // Used for unique instance variables |
| var id = 0; |
| var $window = $( window ); |
| |
| |
| /** |
| * Categorize, sort, and filter a responsive grid of items. |
| * |
| * @param {Element} element An element which is the parent container for the grid items. |
| * @param {Object} [options=Shuffle.options] Options object. |
| * @constructor |
| */ |
| var Shuffle = function( element, options ) { |
| options = options || {}; |
| $.extend( this, Shuffle.options, options, Shuffle.settings ); |
| |
| this.$el = $(element); |
| this.element = element; |
| this.unique = 'shuffle_' + id++; |
| |
| this._fire( Shuffle.EventType.LOADING ); |
| this._init(); |
| |
| // Dispatch the done event asynchronously so that people can bind to it after |
| // Shuffle has been initialized. |
| defer(function() { |
| this.initialized = true; |
| this._fire( Shuffle.EventType.DONE ); |
| }, this, 16); |
| }; |
| |
| |
| /** |
| * Events the container element emits with the .shuffle namespace. |
| * For example, "done.shuffle". |
| * @enum {string} |
| */ |
| Shuffle.EventType = { |
| LOADING: 'loading', |
| DONE: 'done', |
| LAYOUT: 'layout', |
| REMOVED: 'removed' |
| }; |
| |
| |
| /** @enum {string} */ |
| Shuffle.ClassName = { |
| BASE: SHUFFLE, |
| SHUFFLE_ITEM: 'shuffle-item', |
| FILTERED: 'filtered', |
| CONCEALED: 'concealed' |
| }; |
| |
| |
| // Overrideable options |
| Shuffle.options = { |
| group: ALL_ITEMS, // Initial filter group. |
| speed: 250, // Transition/animation speed (milliseconds). |
| easing: 'ease-out', // CSS easing function to use. |
| itemSelector: '', // e.g. '.picture-item'. |
| sizer: null, // Sizer element. Use an element to determine the size of columns and gutters. |
| gutterWidth: 0, // A static number or function that tells the plugin how wide the gutters between columns are (in pixels). |
| columnWidth: 0, // A static number or function that returns a number which tells the plugin how wide the columns are (in pixels). |
| delimeter: null, // If your group is not json, and is comma delimeted, you could set delimeter to ','. |
| buffer: 0, // Useful for percentage based heights when they might not always be exactly the same (in pixels). |
| initialSort: null, // Shuffle can be initialized with a sort object. It is the same object given to the sort method. |
| throttle: throttle, // By default, shuffle will throttle resize events. This can be changed or removed. |
| throttleTime: 300, // How often shuffle can be called on resize (in milliseconds). |
| sequentialFadeDelay: 150, // Delay between each item that fades in when adding items. |
| supported: CAN_TRANSITION_TRANSFORMS // Whether to use transforms or absolute positioning. |
| }; |
| |
| |
| // Not overrideable |
| Shuffle.settings = { |
| useSizer: false, |
| itemCss : { // default CSS for each item |
| position: 'absolute', |
| top: 0, |
| left: 0, |
| visibility: 'visible' |
| }, |
| revealAppendedDelay: 300, |
| lastSort: {}, |
| lastFilter: ALL_ITEMS, |
| enabled: true, |
| destroyed: false, |
| initialized: false, |
| _animations: [], |
| styleQueue: [] |
| }; |
| |
| |
| // Expose for testing. |
| Shuffle.Point = Point; |
| |
| |
| /** |
| * Static methods. |
| */ |
| |
| /** |
| * If the browser has 3d transforms available, build a string with those, |
| * otherwise use 2d transforms. |
| * @param {Point} point X and Y positions. |
| * @param {number} scale Scale amount. |
| * @return {string} A normalized string which can be used with the transform style. |
| * @private |
| */ |
| Shuffle._getItemTransformString = function(point, scale) { |
| if ( HAS_TRANSFORMS_3D ) { |
| return 'translate3d(' + point.x + 'px, ' + point.y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)'; |
| } else { |
| return 'translate(' + point.x + 'px, ' + point.y + 'px) scale(' + scale + ')'; |
| } |
| }; |
| |
| |
| /** |
| * Retrieve the computed style for an element, parsed as a float. This should |
| * not be used for width or height values because jQuery mangles them and they |
| * are not precise enough. |
| * @param {Element} element Element to get style for. |
| * @param {string} style Style property. |
| * @return {number} The parsed computed value or zero if that fails because IE |
| * will return 'auto' when the element doesn't have margins instead of |
| * the computed style. |
| * @private |
| */ |
| Shuffle._getNumberStyle = function( element, style ) { |
| return Shuffle._getFloat( $( element ).css( style ) ); |
| }; |
| |
| |
| /** |
| * Parse a string as an integer. |
| * @param {string} value String integer. |
| * @return {number} The string as an integer or zero. |
| * @private |
| */ |
| Shuffle._getInt = function(value) { |
| return getNumber( parseInt( value, 10 ) ); |
| }; |
| |
| /** |
| * Parse a string as an float. |
| * @param {string} value String float. |
| * @return {number} The string as an float or zero. |
| * @private |
| */ |
| Shuffle._getFloat = function(value) { |
| return getNumber( parseFloat( value ) ); |
| }; |
| |
| |
| /** |
| * Returns the outer width of an element, optionally including its margins. |
| * The `offsetWidth` property must be used because having a scale transform |
| * on the element affects the bounding box. Sadly, Firefox doesn't return an |
| * integer value for offsetWidth (yet). |
| * @param {Element} element The element. |
| * @param {boolean} [includeMargins] Whether to include margins. Default is false. |
| * @return {number} The width. |
| */ |
| Shuffle._getOuterWidth = function( element, includeMargins ) { |
| var width = element.offsetWidth; |
| |
| // Use jQuery here because it uses getComputedStyle internally and is |
| // cross-browser. Using the style property of the element will only work |
| // if there are inline styles. |
| if ( includeMargins ) { |
| var marginLeft = Shuffle._getNumberStyle( element, 'marginLeft'); |
| var marginRight = Shuffle._getNumberStyle( element, 'marginRight'); |
| width += marginLeft + marginRight; |
| } |
| |
| return width; |
| }; |
| |
| |
| /** |
| * Returns the outer height of an element, optionally including its margins. |
| * @param {Element} element The element. |
| * @param {boolean} [includeMargins] Whether to include margins. Default is false. |
| * @return {number} The height. |
| */ |
| Shuffle._getOuterHeight = function( element, includeMargins ) { |
| var height = element.offsetHeight; |
| |
| if ( includeMargins ) { |
| var marginTop = Shuffle._getNumberStyle( element, 'marginTop'); |
| var marginBottom = Shuffle._getNumberStyle( element, 'marginBottom'); |
| height += marginTop + marginBottom; |
| } |
| |
| return height; |
| }; |
| |
| |
| /** |
| * Change a property or execute a function which will not have a transition |
| * @param {Element} element DOM element that won't be transitioned |
| * @param {Function} callback A function which will be called while transition |
| * is set to 0ms. |
| * @param {Object} [context] Optional context for the callback function. |
| * @private |
| */ |
| Shuffle._skipTransition = function( element, callback, context ) { |
| var duration = element.style[ TRANSITION_DURATION ]; |
| |
| // Set the duration to zero so it happens immediately |
| element.style[ TRANSITION_DURATION ] = '0ms'; // ms needed for firefox! |
| |
| callback.call( context ); |
| |
| // Force reflow |
| var reflow = element.offsetWidth; |
| // Avoid jshint warnings: unused variables and expressions. |
| reflow = null; |
| |
| // Put the duration back |
| element.style[ TRANSITION_DURATION ] = duration; |
| }; |
| |
| |
| /** |
| * Instance methods. |
| */ |
| |
| Shuffle.prototype._init = function() { |
| this.$items = this._getItems(); |
| |
| this.sizer = this._getElementOption( this.sizer ); |
| |
| if ( this.sizer ) { |
| this.useSizer = true; |
| } |
| |
| // Add class and invalidate styles |
| this.$el.addClass( Shuffle.ClassName.BASE ); |
| |
| // Set initial css for each item |
| this._initItems(); |
| |
| // Bind resize events |
| // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer |
| $window.on('resize.' + SHUFFLE + '.' + this.unique, this._getResizeFunction()); |
| |
| // Get container css all in one request. Causes reflow |
| var containerCSS = this.$el.css(['position', 'overflow']); |
| var containerWidth = Shuffle._getOuterWidth( this.element ); |
| |
| // Add styles to the container if it doesn't have them. |
| this._validateStyles( containerCSS ); |
| |
| // We already got the container's width above, no need to cause another reflow getting it again... |
| // Calculate the number of columns there will be |
| this._setColumns( containerWidth ); |
| |
| // Kick off! |
| this.shuffle( this.group, this.initialSort ); |
| |
| // 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 ) { |
| defer(function() { |
| this._setTransitions(); |
| this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing; |
| }, this); |
| } |
| }; |
| |
| |
| /** |
| * Returns a throttled and proxied function for the resize handler. |
| * @return {Function} |
| * @private |
| */ |
| Shuffle.prototype._getResizeFunction = function() { |
| var resizeFunction = $.proxy( this._onResize, this ); |
| return this.throttle ? |
| this.throttle( resizeFunction, this.throttleTime ) : |
| resizeFunction; |
| }; |
| |
| |
| /** |
| * Retrieve an element from an option. |
| * @param {string|jQuery|Element} option The option to check. |
| * @return {?Element} The plain element or null. |
| * @private |
| */ |
| Shuffle.prototype._getElementOption = function( option ) { |
| // If column width is a string, treat is as a selector and search for the |
| // sizer element within the outermost container |
| if ( typeof option === 'string' ) { |
| return this.$el.find( option )[0] || null; |
| |
| // Check for an element |
| } else if ( option && option.nodeType && option.nodeType === 1 ) { |
| return option; |
| |
| // Check for jQuery object |
| } else if ( option && option.jquery ) { |
| return option[0]; |
| } |
| |
| return null; |
| }; |
| |
| |
| /** |
| * Ensures the shuffle container has the css styles it needs applied to it. |
| * @param {Object} styles Key value pairs for position and overflow. |
| * @private |
| */ |
| Shuffle.prototype._validateStyles = function(styles) { |
| // Position cannot be static. |
| if ( styles.position === 'static' ) { |
| this.element.style.position = 'relative'; |
| } |
| |
| // Overflow has to be hidden |
| if ( styles.overflow !== 'hidden' ) { |
| this.element.style.overflow = 'hidden'; |
| } |
| }; |
| |
| |
| /** |
| * Filter the elements by a category. |
| * @param {string} [category] Category to filter by. If it's given, the last |
| * category will be used to filter the items. |
| * @param {ArrayLike} [$collection] Optionally filter a collection. Defaults to |
| * all the items. |
| * @return {jQuery} Filtered items. |
| * @private |
| */ |
| Shuffle.prototype._filter = function( category, $collection ) { |
| category = category || this.lastFilter; |
| $collection = $collection || this.$items; |
| |
| var set = this._getFilteredSets( category, $collection ); |
| |
| // Individually add/remove concealed/filtered classes |
| this._toggleFilterClasses( set.filtered, set.concealed ); |
| |
| // Save the last filter in case elements are appended. |
| this.lastFilter = category; |
| |
| // This is saved mainly because providing a filter function (like searching) |
| // will overwrite the `lastFilter` property every time its called. |
| if ( typeof category === 'string' ) { |
| this.group = category; |
| } |
| |
| return set.filtered; |
| }; |
| |
| |
| /** |
| * Returns an object containing the filtered and concealed elements. |
| * @param {string|Function} category Category or function to filter by. |
| * @param {ArrayLike.<Element>} $items A collection of items to filter. |
| * @return {!{filtered: jQuery, concealed: jQuery}} |
| * @private |
| */ |
| Shuffle.prototype._getFilteredSets = function( category, $items ) { |
| var $filtered = $(); |
| var $concealed = $(); |
| |
| // category === 'all', add filtered class to everything |
| if ( category === ALL_ITEMS ) { |
| $filtered = $items; |
| |
| // Loop through each item and use provided function to determine |
| // whether to hide it or not. |
| } else { |
| each($items, function( el ) { |
| var $item = $(el); |
| if ( this._doesPassFilter( category, $item ) ) { |
| $filtered = $filtered.add( $item ); |
| } else { |
| $concealed = $concealed.add( $item ); |
| } |
| }, this); |
| } |
| |
| return { |
| filtered: $filtered, |
| concealed: $concealed |
| }; |
| }; |
| |
| |
| /** |
| * Test an item to see if it passes a category. |
| * @param {string|Function} category Category or function to filter by. |
| * @param {jQuery} $item A single item, wrapped with jQuery. |
| * @return {boolean} Whether it passes the category/filter. |
| * @private |
| */ |
| Shuffle.prototype._doesPassFilter = function( category, $item ) { |
| if ( $.isFunction( category ) ) { |
| return category.call( $item[0], $item, this ); |
| |
| // Check each element's data-groups attribute against the given category. |
| } else { |
| var groups = $item.data( FILTER_ATTRIBUTE_KEY ); |
| var keys = this.delimeter && !$.isArray( groups ) ? |
| groups.split( this.delimeter ) : |
| groups; |
| return $.inArray(category, keys) > -1; |
| } |
| }; |
| |
| |
| /** |
| * Toggles the filtered and concealed class names. |
| * @param {jQuery} $filtered Filtered set. |
| * @param {jQuery} $concealed Concealed set. |
| * @private |
| */ |
| Shuffle.prototype._toggleFilterClasses = function( $filtered, $concealed ) { |
| $filtered |
| .removeClass( Shuffle.ClassName.CONCEALED ) |
| .addClass( Shuffle.ClassName.FILTERED ); |
| $concealed |
| .removeClass( Shuffle.ClassName.FILTERED ) |
| .addClass( Shuffle.ClassName.CONCEALED ); |
| }; |
| |
| |
| /** |
| * Set the initial css for each item |
| * @param {jQuery} [$items] Optionally specifiy at set to initialize |
| */ |
| Shuffle.prototype._initItems = function( $items ) { |
| $items = $items || this.$items; |
| $items.addClass([ |
| Shuffle.ClassName.SHUFFLE_ITEM, |
| Shuffle.ClassName.FILTERED |
| ].join(' ')); |
| $items.css( this.itemCss ).data('point', new Point()).data('scale', DEFAULT_SCALE); |
| }; |
| |
| |
| /** |
| * Updates the filtered item count. |
| * @private |
| */ |
| Shuffle.prototype._updateItemCount = function() { |
| this.visibleItems = this._getFilteredItems().length; |
| }; |
| |
| |
| /** |
| * Sets css transform transition on a an element. |
| * @param {Element} element Element to set transition on. |
| * @private |
| */ |
| Shuffle.prototype._setTransition = function( element ) { |
| element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' + |
| this.easing + ', opacity ' + this.speed + 'ms ' + this.easing; |
| }; |
| |
| |
| /** |
| * Sets css transform transition on a group of elements. |
| * @param {ArrayLike.<Element>} $items Elements to set transitions on. |
| * @private |
| */ |
| Shuffle.prototype._setTransitions = function( $items ) { |
| $items = $items || this.$items; |
| each($items, function( el ) { |
| this._setTransition( el ); |
| }, this); |
| }; |
| |
| |
| /** |
| * Sets a transition delay on a collection of elements, making each delay |
| * greater than the last. |
| * @param {ArrayLike.<Element>} $collection Array to iterate over. |
| */ |
| Shuffle.prototype._setSequentialDelay = function( $collection ) { |
| if ( !this.supported ) { |
| return; |
| } |
| |
| // $collection can be an array of dom elements or jquery object |
| each($collection, function( el, i ) { |
| // This works because the transition-property: transform, opacity; |
| el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * this.sequentialFadeDelay) + 'ms'; |
| }, this); |
| }; |
| |
| |
| Shuffle.prototype._getItems = function() { |
| return this.$el.children( this.itemSelector ); |
| }; |
| |
| |
| Shuffle.prototype._getFilteredItems = function() { |
| return this.$items.filter('.' + Shuffle.ClassName.FILTERED); |
| }; |
| |
| |
| Shuffle.prototype._getConcealedItems = function() { |
| return this.$items.filter('.' + Shuffle.ClassName.CONCEALED); |
| }; |
| |
| |
| /** |
| * Returns the column size, based on column width and sizer options. |
| * @param {number} containerWidth Size of the parent container. |
| * @param {number} gutterSize Size of the gutters. |
| * @return {number} |
| * @private |
| */ |
| Shuffle.prototype._getColumnSize = function( containerWidth, gutterSize ) { |
| var size; |
| |
| // If the columnWidth property is a function, then the grid is fluid |
| if ( $.isFunction( this.columnWidth ) ) { |
| size = this.columnWidth(containerWidth); |
| |
| // columnWidth option isn't a function, are they using a sizing element? |
| } else if ( this.useSizer ) { |
| size = Shuffle._getOuterWidth(this.sizer); |
| |
| // if not, how about the explicitly set option? |
| } else if ( this.columnWidth ) { |
| size = this.columnWidth; |
| |
| // or use the size of the first item |
| } else if ( this.$items.length > 0 ) { |
| size = Shuffle._getOuterWidth(this.$items[0], true); |
| |
| // if there's no items, use size of container |
| } else { |
| size = containerWidth; |
| } |
| |
| // Don't let them set a column width of zero. |
| if ( size === 0 ) { |
| size = containerWidth; |
| } |
| |
| return size + gutterSize; |
| }; |
| |
| |
| /** |
| * Returns the gutter size, based on gutter width and sizer options. |
| * @param {number} containerWidth Size of the parent container. |
| * @return {number} |
| * @private |
| */ |
| Shuffle.prototype._getGutterSize = function( containerWidth ) { |
| var size; |
| if ( $.isFunction( this.gutterWidth ) ) { |
| size = this.gutterWidth(containerWidth); |
| } else if ( this.useSizer ) { |
| size = Shuffle._getNumberStyle(this.sizer, 'marginLeft'); |
| } else { |
| size = this.gutterWidth; |
| } |
| |
| return size; |
| }; |
| |
| |
| /** |
| * Calculate the number of columns to be used. Gets css if using sizer element. |
| * @param {number} [theContainerWidth] Optionally specify a container width if it's already available. |
| */ |
| Shuffle.prototype._setColumns = function( theContainerWidth ) { |
| var containerWidth = theContainerWidth || Shuffle._getOuterWidth( this.element ); |
| var gutter = this._getGutterSize( containerWidth ); |
| var columnWidth = this._getColumnSize( containerWidth, gutter ); |
| var calculatedColumns = (containerWidth + gutter) / columnWidth; |
| |
| // Widths given from getComputedStyle are not precise enough... |
| if ( Math.abs(Math.round(calculatedColumns) - calculatedColumns) < COLUMN_THRESHOLD ) { |
| // e.g. calculatedColumns = 11.998876 |
| calculatedColumns = Math.round( calculatedColumns ); |
| } |
| |
| this.cols = Math.max( Math.floor(calculatedColumns), 1 ); |
| this.containerWidth = containerWidth; |
| this.colWidth = columnWidth; |
| }; |
| |
| /** |
| * Adjust the height of the grid |
| */ |
| Shuffle.prototype._setContainerSize = function() { |
| this.$el.css( 'height', this._getContainerSize() ); |
| }; |
| |
| |
| /** |
| * Based on the column heights, it returns the biggest one. |
| * @return {number} |
| * @private |
| */ |
| Shuffle.prototype._getContainerSize = function() { |
| return arrayMax( this.positions ); |
| }; |
| |
| |
| /** |
| * Fire events with .shuffle namespace |
| */ |
| Shuffle.prototype._fire = function( name, args ) { |
| this.$el.trigger( name + '.' + SHUFFLE, args && args.length ? args : [ this ] ); |
| }; |
| |
| |
| /** |
| * Zeros out the y columns array, which is used to determine item placement. |
| * @private |
| */ |
| Shuffle.prototype._resetCols = function() { |
| var i = this.cols; |
| this.positions = []; |
| while (i--) { |
| this.positions.push( 0 ); |
| } |
| }; |
| |
| |
| /** |
| * Loops through each item that should be shown and calculates the x, y position. |
| * @param {Array.<Element>} items Array of items that will be shown/layed out in order in their array. |
| * Because jQuery collection are always ordered in DOM order, we can't pass a jq collection. |
| * @param {boolean} [isOnlyPosition=false] If true this will position the items with zero opacity. |
| */ |
| Shuffle.prototype._layout = function( items, isOnlyPosition ) { |
| each(items, function( item ) { |
| this._layoutItem( item, !!isOnlyPosition ); |
| }, this); |
| |
| // `_layout` always happens after `_shrink`, so it's safe to process the style |
| // queue here with styles from the shrink method. |
| this._processStyleQueue(); |
| |
| // Adjust the height of the container. |
| this._setContainerSize(); |
| }; |
| |
| |
| /** |
| * Calculates the position of the item and pushes it onto the style queue. |
| * @param {Element} item Element which is being positioned. |
| * @param {boolean} isOnlyPosition Whether to position the item, but with zero |
| * opacity so that it can fade in later. |
| * @private |
| */ |
| Shuffle.prototype._layoutItem = function( item, isOnlyPosition ) { |
| var $item = $(item); |
| var itemData = $item.data(); |
| var currPos = itemData.point; |
| var currScale = itemData.scale; |
| var itemSize = { |
| width: Shuffle._getOuterWidth( item, true ), |
| height: Shuffle._getOuterHeight( item, true ) |
| }; |
| var pos = this._getItemPosition( itemSize ); |
| |
| // 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. |
| if ( Point.equals(currPos, pos) && currScale === DEFAULT_SCALE ) { |
| return; |
| } |
| |
| // Save data for shrink |
| itemData.point = pos; |
| itemData.scale = DEFAULT_SCALE; |
| |
| this.styleQueue.push({ |
| $item: $item, |
| point: pos, |
| scale: DEFAULT_SCALE, |
| opacity: isOnlyPosition ? 0 : 1, |
| skipTransition: isOnlyPosition, |
| callfront: function() { |
| if ( !isOnlyPosition ) { |
| $item.css( 'visibility', 'visible' ); |
| } |
| }, |
| callback: function() { |
| if ( isOnlyPosition ) { |
| $item.css( 'visibility', 'hidden' ); |
| } |
| } |
| }); |
| }; |
| |
| |
| /** |
| * Determine the location of the next item, based on its size. |
| * @param {{width: number, height: number}} itemSize Object with width and height. |
| * @return {Point} |
| * @private |
| */ |
| Shuffle.prototype._getItemPosition = function( itemSize ) { |
| var columnSpan = this._getColumnSpan( itemSize.width, this.colWidth, this.cols ); |
| |
| var setY = this._getColumnSet( columnSpan, this.cols ); |
| |
| // Finds the index of the smallest number in the set. |
| var shortColumnIndex = this._getShortColumn( setY, this.buffer ); |
| |
| // Position the item |
| var point = new Point( |
| Math.round( this.colWidth * shortColumnIndex ), |
| Math.round( setY[shortColumnIndex] )); |
| |
| // Update the columns array with the new values for each column. |
| // e.g. before the update the columns could be [250, 0, 0, 0] for an item |
| // which spans 2 columns. After it would be [250, itemHeight, itemHeight, 0]. |
| var setHeight = setY[shortColumnIndex] + itemSize.height; |
| var setSpan = this.cols + 1 - setY.length; |
| for ( var i = 0; i < setSpan; i++ ) { |
| this.positions[ shortColumnIndex + i ] = setHeight; |
| } |
| |
| return point; |
| }; |
| |
| |
| /** |
| * Determine the number of columns an items spans. |
| * @param {number} itemWidth Width of the item. |
| * @param {number} columnWidth Width of the column (includes gutter). |
| * @param {number} columns Total number of columns |
| * @return {number} |
| * @private |
| */ |
| Shuffle.prototype._getColumnSpan = function( itemWidth, columnWidth, columns ) { |
| var columnSpan = itemWidth / columnWidth; |
| |
| // If the difference between the rounded column span number and the |
| // calculated column span number is really small, round the number to |
| // make it fit. |
| if ( Math.abs(Math.round( columnSpan ) - columnSpan ) < COLUMN_THRESHOLD ) { |
| // e.g. columnSpan = 4.0089945390298745 |
| columnSpan = Math.round( columnSpan ); |
| } |
| |
| // Ensure the column span is not more than the amount of columns in the whole layout. |
| return Math.min( Math.ceil( columnSpan ), columns ); |
| }; |
| |
| |
| /** |
| * Retrieves the column set to use for placement. |
| * @param {number} columnSpan The number of columns this current item spans. |
| * @param {number} columns The total columns in the grid. |
| * @return {Array.<number>} An array of numbers represeting the column set. |
| * @private |
| */ |
| Shuffle.prototype._getColumnSet = function( columnSpan, columns ) { |
| // The item spans only one column. |
| if ( columnSpan === 1 ) { |
| return this.positions; |
| |
| // The item spans more than one column, figure out how many different |
| // places it could fit horizontally. |
| // The group count is the number of places within the positions this block |
| // could fit, ignoring the current positions of items. |
| // Imagine a 2 column brick as the second item in a 4 column grid with |
| // 10px height each. Find the places it would fit: |
| // [10, 0, 0, 0] |
| // | | | |
| // * * * |
| // |
| // Then take the places which fit and get the bigger of the two: |
| // max([10, 0]), max([0, 0]), max([0, 0]) = [10, 0, 0] |
| // |
| // Next, find the first smallest number (the short column). |
| // [10, 0, 0] |
| // | |
| // * |
| // |
| // And that's where it should be placed! |
| } else { |
| var groupCount = columns + 1 - columnSpan; |
| var groupY = []; |
| |
| // For how many possible positions for this item there are. |
| for ( var i = 0; i < groupCount; i++ ) { |
| // Find the bigger value for each place it could fit. |
| groupY[i] = arrayMax( this.positions.slice( i, i + columnSpan ) ); |
| } |
| |
| return groupY; |
| } |
| }; |
| |
| |
| /** |
| * Find index of short column, the first from the left where this item will go. |
| * |
| * @param {Array.<number>} positions The array to search for the smallest number. |
| * @param {number} buffer Optional buffer which is very useful when the height |
| * is a percentage of the width. |
| * @return {number} Index of the short column. |
| * @private |
| */ |
| Shuffle.prototype._getShortColumn = function( positions, buffer ) { |
| var minPosition = arrayMin( positions ); |
| for (var i = 0, len = positions.length; i < len; i++) { |
| if ( positions[i] >= minPosition - buffer && positions[i] <= minPosition + buffer ) { |
| return i; |
| } |
| } |
| return 0; |
| }; |
| |
| |
| /** |
| * Hides the elements that don't match our filter. |
| * @param {jQuery} $collection jQuery collection to shrink. |
| * @private |
| */ |
| Shuffle.prototype._shrink = function( $collection ) { |
| var $concealed = $collection || this._getConcealedItems(); |
| |
| each($concealed, function( item ) { |
| var $item = $(item); |
| var itemData = $item.data(); |
| |
| // Continuing would add a transitionend event listener to the element, but |
| // that listener would not execute because the transform and opacity would |
| // stay the same. |
| if ( itemData.scale === CONCEALED_SCALE ) { |
| return; |
| } |
| |
| itemData.scale = CONCEALED_SCALE; |
| |
| this.styleQueue.push({ |
| $item: $item, |
| point: itemData.point, |
| scale : CONCEALED_SCALE, |
| opacity: 0, |
| callback: function() { |
| $item.css( 'visibility', 'hidden' ); |
| } |
| }); |
| }, this); |
| }; |
| |
| |
| /** |
| * Resize handler. |
| * @private |
| */ |
| Shuffle.prototype._onResize = function() { |
| // If shuffle is disabled, destroyed, don't do anything |
| if ( !this.enabled || this.destroyed || this.isTransitioning ) { |
| return; |
| } |
| |
| // Will need to check height in the future if it's layed out horizontaly |
| var containerWidth = Shuffle._getOuterWidth( this.element ); |
| |
| // containerWidth hasn't changed, don't do anything |
| if ( containerWidth === this.containerWidth ) { |
| return; |
| } |
| |
| this.update(); |
| }; |
| |
| |
| /** |
| * Returns styles for either jQuery animate or transition. |
| * @param {Object} opts Transition options. |
| * @return {!Object} Transforms for transitions, left/top for animate. |
| * @private |
| */ |
| Shuffle.prototype._getStylesForTransition = function( opts ) { |
| var styles = { |
| opacity: opts.opacity |
| }; |
| |
| if ( this.supported ) { |
| styles[ TRANSFORM ] = Shuffle._getItemTransformString( opts.point, opts.scale ); |
| } else { |
| styles.left = opts.point.x; |
| styles.top = opts.point.y; |
| } |
| |
| return styles; |
| }; |
| |
| |
| /** |
| * Transitions an item in the grid |
| * |
| * @param {Object} opts options. |
| * @param {jQuery} opts.$item jQuery object representing the current item. |
| * @param {Point} opts.point A point object with the x and y coordinates. |
| * @param {number} opts.scale Amount to scale the item. |
| * @param {number} opts.opacity Opacity of the item. |
| * @param {Function} opts.callback Complete function for the animation. |
| * @param {Function} opts.callfront Function to call before transitioning. |
| * @private |
| */ |
| Shuffle.prototype._transition = function( opts ) { |
| var styles = this._getStylesForTransition( opts ); |
| this._startItemAnimation( opts.$item, styles, opts.callfront || $.noop, opts.callback || $.noop ); |
| }; |
| |
| |
| Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) { |
| // 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 ) { |
| $( evt.target ).off( TRANSITIONEND, handleTransitionEnd ); |
| callback(); |
| } |
| } |
| |
| 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 ); |
| $item.on( TRANSITIONEND, handleTransitionEnd ); |
| |
| // Use jQuery to animate left/top |
| } else { |
| // Save the deferred object which jQuery returns. |
| var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback ); |
| // Push the animation to the list of pending animations. |
| this._animations.push( anim.promise() ); |
| } |
| }; |
| |
| |
| /** |
| * Execute the styles gathered in the style queue. This applies styles to elements, |
| * triggering transitions. |
| * @param {boolean} noLayout Whether to trigger a layout event. |
| * @private |
| */ |
| 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 ); |
| } else { |
| $transitions = $transitions.add( transitionObj.$item ); |
| this._transition( transitionObj ); |
| } |
| }, this); |
| |
| |
| 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._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 |
| this.styleQueue.length = 0; |
| }; |
| |
| |
| /** |
| * Apply styles without a transition. |
| * @param {Object} opts Transitions options object. |
| * @private |
| */ |
| Shuffle.prototype._styleImmediately = function( opts ) { |
| Shuffle._skipTransition(opts.$item[0], function() { |
| opts.$item.css( this._getStylesForTransition( opts ) ); |
| }, this); |
| }; |
| |
| Shuffle.prototype._movementFinished = function() { |
| this.isTransitioning = false; |
| this._layoutEnd(); |
| }; |
| |
| Shuffle.prototype._layoutEnd = function() { |
| this._fire( Shuffle.EventType.LAYOUT ); |
| }; |
| |
| Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) { |
| // Add classes and set initial positions. |
| this._initItems( $newItems ); |
| |
| // Add transition to each item. |
| this._setTransitions( $newItems ); |
| |
| // Update the list of |
| this.$items = this._getItems(); |
| |
| // Shrink all items (without transitions). |
| this._shrink( $newItems ); |
| each(this.styleQueue, function( transitionObj ) { |
| transitionObj.skipTransition = true; |
| }); |
| |
| // Apply shrink positions, but do not cause a layout event. |
| this._processStyleQueue( true ); |
| |
| if ( addToEnd ) { |
| this._addItemsToEnd( $newItems, isSequential ); |
| } else { |
| this.shuffle( this.lastFilter ); |
| } |
| }; |
| |
| |
| Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) { |
| // Get ones that passed the current filter |
| var $passed = this._filter( null, $newItems ); |
| var passed = $passed.get(); |
| |
| // How many filtered elements? |
| this._updateItemCount(); |
| |
| this._layout( passed, true ); |
| |
| if ( isSequential && this.supported ) { |
| this._setSequentialDelay( passed ); |
| } |
| |
| this._revealAppended( passed ); |
| }; |
| |
| |
| /** |
| * Triggers appended elements to fade in. |
| * @param {ArrayLike.<Element>} $newFilteredItems Collection of elements. |
| * @private |
| */ |
| Shuffle.prototype._revealAppended = function( newFilteredItems ) { |
| defer(function() { |
| each(newFilteredItems, function( el ) { |
| var $item = $( el ); |
| this._transition({ |
| $item: $item, |
| opacity: 1, |
| point: $item.data('point'), |
| scale: DEFAULT_SCALE |
| }); |
| }, this); |
| |
| this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() { |
| $(newFilteredItems).css( TRANSITION_DELAY, '0ms' ); |
| this._movementFinished(); |
| }); |
| }, this, this.revealAppendedDelay); |
| }; |
| |
| |
| /** |
| * Execute a function when an event has been triggered for every item in a collection. |
| * @param {jQuery} $collection Collection of elements. |
| * @param {string} eventName Event to listen for. |
| * @param {Function} callback Callback to execute when they're done. |
| * @private |
| */ |
| Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callback ) { |
| var done = 0; |
| var items = $collection.length; |
| var self = this; |
| |
| function handleEventName( evt ) { |
| if ( evt.target === evt.currentTarget ) { |
| $( evt.target ).off( eventName, handleEventName ); |
| done++; |
| |
| // Execute callback if all items have emitted the correct event. |
| if ( done === items ) { |
| callback.call( self ); |
| } |
| } |
| } |
| |
| // Bind the event to all items. |
| $collection.on( eventName, handleEventName ); |
| }; |
| |
| |
| /** |
| * 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 |
| */ |
| |
| /** |
| * The magic. This is what makes the plugin 'shuffle' |
| * @param {string|Function} [category] Category to filter by. Can be a function |
| * @param {Object} [sortObj] A sort object which can sort the filtered set |
| */ |
| Shuffle.prototype.shuffle = function( category, sortObj ) { |
| if ( !this.enabled || this.isTransitioning ) { |
| return; |
| } |
| |
| if ( !category ) { |
| category = ALL_ITEMS; |
| } |
| |
| this._filter( category ); |
| |
| // How many filtered elements? |
| this._updateItemCount(); |
| |
| // Shrink each concealed item |
| this._shrink(); |
| |
| // Update transforms on .filtered elements so they will animate to their new positions |
| this.sort( sortObj ); |
| }; |
| |
| |
| /** |
| * Gets the .filtered elements, sorts them, and passes them to layout. |
| * @param {Object} opts the options object for the sorted plugin |
| */ |
| Shuffle.prototype.sort = function( opts ) { |
| if ( this.enabled && !this.isTransitioning ) { |
| this._resetCols(); |
| |
| var sortOptions = opts || this.lastSort; |
| var items = this._getFilteredItems().sorted( sortOptions ); |
| |
| this._layout( items ); |
| |
| this.lastSort = sortOptions; |
| } |
| }; |
| |
| |
| /** |
| * Reposition everything. |
| * @param {boolean} isOnlyLayout If true, column and gutter widths won't be |
| * recalculated. |
| */ |
| Shuffle.prototype.update = function( isOnlyLayout ) { |
| if ( this.enabled && !this.isTransitioning ) { |
| |
| if ( !isOnlyLayout ) { |
| // Get updated colCount |
| this._setColumns(); |
| } |
| |
| // Layout items |
| this.sort(); |
| } |
| }; |
| |
| |
| /** |
| * Use this instead of `update()` if you don't need the columns and gutters updated |
| * Maybe an image inside `shuffle` loaded (and now has a height), which means calculations |
| * could be off. |
| */ |
| Shuffle.prototype.layout = function() { |
| this.update( true ); |
| }; |
| |
| |
| /** |
| * New items have been appended to shuffle. Fade them in sequentially |
| * @param {jQuery} $newItems jQuery collection of new items |
| * @param {boolean} [addToEnd=false] If true, new items will be added to the end / bottom |
| * of the items. If not true, items will be mixed in with the current sort order. |
| * @param {boolean} [isSequential=true] If false, new items won't sequentially fade in |
| */ |
| Shuffle.prototype.appended = function( $newItems, addToEnd, isSequential ) { |
| this._addItems( $newItems, addToEnd === true, isSequential !== false ); |
| }; |
| |
| |
| /** |
| * Disables shuffle from updating dimensions and layout on resize |
| */ |
| Shuffle.prototype.disable = function() { |
| this.enabled = false; |
| }; |
| |
| |
| /** |
| * Enables shuffle again |
| * @param {boolean} [isUpdateLayout=true] if undefined, shuffle will update columns and gutters |
| */ |
| Shuffle.prototype.enable = function( isUpdateLayout ) { |
| this.enabled = true; |
| if ( isUpdateLayout !== false ) { |
| this.update(); |
| } |
| }; |
| |
| |
| /** |
| * Remove 1 or more shuffle items |
| * @param {jQuery} $collection A jQuery object containing one or more element in shuffle |
| * @return {Shuffle} The shuffle object |
| */ |
| Shuffle.prototype.remove = function( $collection ) { |
| |
| // If this isn't a jquery object, exit |
| if ( !$collection.length || !$collection.jquery ) { |
| return; |
| } |
| |
| function handleRemoved() { |
| // Remove the collection in the callback |
| $collection.remove(); |
| |
| // Update things now that elements have been removed. |
| this.$items = this._getItems(); |
| 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 ); |
| |
| this.sort(); |
| |
| this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) ); |
| }; |
| |
| |
| /** |
| * Destroys shuffle, removes events, styles, and classes |
| */ |
| Shuffle.prototype.destroy = function() { |
| // If there is more than one shuffle instance on the page, |
| // removing the resize handler from the window would remove them |
| // all. This is why a unique value is needed. |
| $window.off('.' + this.unique); |
| |
| // Reset container styles |
| this.$el |
| .removeClass( SHUFFLE ) |
| .removeAttr('style') |
| .removeData( SHUFFLE ); |
| |
| // Reset individual item styles |
| this.$items |
| .removeAttr('style') |
| .removeData('point') |
| .removeData('scale') |
| .removeClass([ |
| Shuffle.ClassName.CONCEALED, |
| Shuffle.ClassName.FILTERED, |
| Shuffle.ClassName.SHUFFLE_ITEM |
| ].join(' ')); |
| |
| // Null DOM references |
| this.$items = null; |
| this.$el = null; |
| this.sizer = null; |
| this.element = null; |
| |
| // Set a flag so if a debounced resize has been triggered, |
| // it can first check if it is actually destroyed and not doing anything |
| this.destroyed = true; |
| }; |
| |
| |
| // Plugin definition |
| $.fn.shuffle = function( opts ) { |
| var args = Array.prototype.slice.call( arguments, 1 ); |
| return this.each(function() { |
| var $this = $( this ); |
| var shuffle = $this.data( SHUFFLE ); |
| |
| // If we don't have a stored shuffle, make a new one and save it |
| if ( !shuffle ) { |
| shuffle = new Shuffle( this, opts ); |
| $this.data( SHUFFLE, shuffle ); |
| } else if ( typeof opts === 'string' && shuffle[ opts ] ) { |
| shuffle[ opts ].apply( shuffle, args ); |
| } |
| }); |
| }; |
| |
| |
| // http://stackoverflow.com/a/962890/373422 |
| function randomize( array ) { |
| var tmp, current; |
| var top = array.length; |
| |
| if ( !top ) { |
| return array; |
| } |
| |
| while ( --top ) { |
| current = Math.floor( Math.random() * (top + 1) ); |
| tmp = array[ current ]; |
| array[ current ] = array[ top ]; |
| array[ top ] = tmp; |
| } |
| |
| return array; |
| } |
| |
| |
| // You can return `undefined` from the `by` function to revert to DOM order |
| // This plugin does NOT return a jQuery object. It returns a plain array because |
| // jQuery sorts everything in DOM order. |
| $.fn.sorted = function(options) { |
| var opts = $.extend({}, $.fn.sorted.defaults, options); |
| var arr = this.get(); |
| var revert = false; |
| |
| if ( !arr.length ) { |
| return []; |
| } |
| |
| if ( opts.randomize ) { |
| return randomize( arr ); |
| } |
| |
| // Sort the elements by the opts.by function. |
| // If we don't have opts.by, default to DOM order |
| if ( $.isFunction( opts.by ) ) { |
| arr.sort(function(a, b) { |
| |
| // Exit early if we already know we want to revert |
| if ( revert ) { |
| return 0; |
| } |
| |
| var valA = opts.by($(a)); |
| var valB = opts.by($(b)); |
| |
| // If both values are undefined, use the DOM order |
| if ( valA === undefined && valB === undefined ) { |
| revert = true; |
| return 0; |
| } |
| |
| if ( valA < valB || valA === 'sortFirst' || valB === 'sortLast' ) { |
| return -1; |
| } |
| |
| if ( valA > valB || valA === 'sortLast' || valB === 'sortFirst' ) { |
| return 1; |
| } |
| |
| return 0; |
| }); |
| } |
| |
| // Revert to the original array if necessary |
| if ( revert ) { |
| return this.get(); |
| } |
| |
| if ( opts.reverse ) { |
| arr.reverse(); |
| } |
| |
| return arr; |
| }; |
| |
| |
| $.fn.sorted.defaults = { |
| reverse: false, // Use array.reverse() to reverse the results |
| by: null, // Sorting function |
| randomize: false // If true, this will skip the sorting and return a randomized order in the array |
| }; |
| |
| return Shuffle; |
| |
| }); |