PageSourceSearch

https://www.carnot-ifpen-re.fr/modules/custom/dexp/modules/dexp_grid/assets/js/jquery.shuffle.min.js?v=1

js carnot-ifpen-re.fr collected 2026-10-04 11:14:02 UTC 51,401 bytes, 1,693 lines download raw bytes

vendor: 5,760 bytes, lines 1-89
1/*!
2 * Shuffle.js by @Vestride
3 * Categorize, sort, and filter a responsive grid of items.
4 * Dependencies: jQuery 1.9+, Modernizr 2.6.2+
5 * @license MIT license
6 * @version 3.1.1
7 */
8
9/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
10 * Build: http://modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-cssclasses-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes
11 */
12window.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=["&#173;",'<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);
13
14(function (factory) {
15  if (typeof define === 'function' && define.amd) {
16    define(['jquery', 'modernizr'], factory);
17  } else if (typeof exports === 'object') {
18    module.exports = factory(require('jquery'), window.Modernizr);
19  } else {
20    window.Shuffle = factory(window.jQuery, window.Modernizr);
21  }
22})(function($, Modernizr, undefined) {
23
24'use strict';
25
26
27// Validate Modernizr exists.
28// Shuffle requires `csstransitions`, `csstransforms`, `csstransforms3d`,
29// and `prefixed` to exist on the Modernizr object.
30if (typeof Modernizr !== 'object') {
31  throw new Error('Shuffle.js requires Modernizr.\n' +
32      'http://vestride.github.io/Shuffle/#dependencies');
33}
34
35
36/**
37 * Returns css prefixed properties like `-webkit-transition` or `box-sizing`
38 * from `transition` or `boxSizing`, respectively.
39 * @param {(string|boolean)} prop Property to be prefixed.
40 * @return {string} The prefixed css property.
41 */
42function dashify( prop ) {
43  if (!prop) {
44    return '';
45  }
46
47  // Replace upper case with dash-lowercase,
48  // then fix ms- prefixes because they're not capitalized.
49  return prop.replace(/([A-Z])/g, function( str, m1 ) {
50    return '-' + m1.toLowerCase();
51  }).replace(/^ms-/,'-ms-');
52}
53
54// Constant, prefixed variables.
55var TRANSITION = Modernizr.prefixed('transition');
56var TRANSITION_DELAY = Modernizr.prefixed('transitionDelay');
57var TRANSITION_DURATION = Modernizr.prefixed('transitionDuration');
58
59// Note(glen): Stock Android 4.1.x browser will fail here because it wrongly
60// says it supports non-prefixed transitions.
61// https://github.com/Modernizr/Modernizr/issues/897
62var TRANSITIONEND = {
63  'WebkitTransition' : 'webkitTransitionEnd',
64  'transition' : 'transitionend'
65}[ TRANSITION ];
66
67var TRANSFORM = Modernizr.prefixed('transform');
68var CSS_TRANSFORM = dashify(TRANSFORM);
69
70// Constants
71var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions;
72var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d;
73var HAS_COMPUTED_STYLE = !!window.getComputedStyle;
74var SHUFFLE = 'shuffle';
75
76// Configurable. You can change these constants to fit your application.
77// The default scale and concealed scale, however, have to be different values.
78var ALL_ITEMS = 'all';
79var FILTER_ATTRIBUTE_KEY = 'groups';
80var DEFAULT_SCALE = 1;
81var CONCEALED_SCALE = 0.001;
82
83// Underscore's throttle function.
84function throttle(func, wait, options) {
85  var context, args, result;
86  var timeout = null;
87  var previous = 0;
88  options = options || {};
89  var later = function() {
90    previous = options.leading === false ? 0 : $.now();
91    timeout = null;
92    result = func.apply(context, args);
93    context = args = null;
94  };
95  return function() {
96    var now = $.now();
97    if (!previous && options.leading === false) {
98      previous = now;
99    }
100    var remaining = wait - (now - previous);
101    context = this;
102    args = arguments;
103    if (remaining <= 0 || remaining > wait) {
104      clearTimeout(timeout);
105      timeout = null;
106      previous = now;
107      result = func.apply(context, args);
108      context = args = null;
109    } else if (!timeout && options.trailing !== false) {
110      timeout = setTimeout(later, remaining);
111    }
112    return result;
113  };
114}
115
116function each(obj, iterator, context) {
117  for (var i = 0, length = obj.length; i < length; i++) {
118    if (iterator.call(context, obj[i], i, obj) === {}) {
119      return;
120    }
121  }
122}
123
124function defer(fn, context, wait) {
125  return setTimeout( $.proxy( fn, context ), wait );
126}
127
128function arrayMax( array ) {
129  return Math.max.apply( Math, array );
130}
131
132function arrayMin( array ) {
133  return Math.min.apply( Math, array );
134}
135
136
137/**
138 * Always returns a numeric value, given a value.
139 * @param {*} value Possibly numeric value.
140 * @return {number} `value` or zero if `value` isn't numeric.
141 * @private
142 */
143function getNumber(value) {
144  return $.isNumeric(value) ? value : 0;
145}
146
147var getStyles = window.getComputedStyle || function() {};
148
149/**
150 * Represents a coordinate pair.
151 * @param {number} [x=0] X.
152 * @param {number} [y=0] Y.
153 */
154var Point = function(x, y) {
155  this.x = getNumber( x );
156  this.y = getNumber( y );
157};
158
159
160/**
161 * Whether two points are equal.
162 * @param {Point} a Point A.
163 * @param {Point} b Point B.
164 * @return {boolean}
165 */
166Point.equals = function(a, b) {
167  return a.x === b.x && a.y === b.y;
168};
169
170var COMPUTED_SIZE_INCLUDES_PADDING = (function() {
171  if (!HAS_COMPUTED_STYLE) {
172    return false;
173  }
174
175  var parent = document.body || document.documentElement;
176  var e = document.createElement('div');
177  e.style.cssText = 'width:10px;padding:2px;' +
178    '-webkit-box-sizing:border-box;box-sizing:border-box;';
179  parent.appendChild(e);
180
181  var width = getStyles(e, null).width;
182  var ret = width === '10px';
183
184  parent.removeChild(e);
185
186  return ret;
187}());
188
189
190// Used for unique instance variables
191var id = 0;
192var $window = $( window );
193
194
195/**
196 * Categorize, sort, and filter a responsive grid of items.
197 *
198 * @param {Element} element An element which is the parent container for the grid items.
199 * @param {Object} [options=Shuffle.options] Options object.
200 * @constructor
201 */
202var Shuffle = function( element, options ) {
203  options = options || {};
204  $.extend( this, Shuffle.options, options, Shuffle.settings );
205
206  this.$el = $(element);
207  this.element = element;
208  this.unique = 'shuffle_' + id++;
209
210  this._fire( Shuffle.EventType.LOADING );
211  this._init();
212
213  // Dispatch the done event asynchronously so that people can bind to it after
214  // Shuffle has been initialized.
215  defer(function() {
216    this.initialized = true;
217    this._fire( Shuffle.EventType.DONE );
218  }, this, 16);
219};
220
221
222/**
223 * Events the container element emits with the .shuffle namespace.
224 * For example, "done.shuffle".
225 * @enum {string}
226 */
227Shuffle.EventType = {
228  LOADING: 'loading',
229  DONE: 'done',
230  LAYOUT: 'layout',
231  REMOVED: 'removed'
232};
233
234
235/** @enum {string} */
236Shuffle.ClassName = {
237  BASE: SHUFFLE,
238  SHUFFLE_ITEM: 'shuffle-item',
239  FILTERED: 'filtered',
240  CONCEALED: 'concealed'
241};
242
243
244// Overrideable options
245Shuffle.options = {
246  group: ALL_ITEMS, // Initial filter group.
247  speed: 250, // Transition/animation speed (milliseconds).
248  easing: 'ease-out', // CSS easing function to use.
249  itemSelector: '', // e.g. '.picture-item'.
250  sizer: null, // Sizer element. Use an element to determine the size of columns and gutters.
251  gutterWidth: 0, // A static number or function that tells the plugin how wide the gutters between columns are (in pixels).
252  columnWidth: 0, // A static number or function that returns a number which tells the plugin how wide the columns are (in pixels).
253  delimeter: null, // If your group is not json, and is comma delimeted, you could set delimeter to ','.
254  buffer: 0, // Useful for percentage based heights when they might not always be exactly the same (in pixels).
255  columnThreshold: HAS_COMPUTED_STYLE ? 0.01 : 0.1, // Reading the width of elements isn't precise enough and can cause columns to jump between values.
256  initialSort: null, // Shuffle can be initialized with a sort object. It is the same object given to the sort method.
257  throttle: throttle, // By default, shuffle will throttle resize events. This can be changed or removed.
258  throttleTime: 300, // How often shuffle can be called on resize (in milliseconds).
259  sequentialFadeDelay: 150, // Delay between each item that fades in when adding items.
260  supported: CAN_TRANSITION_TRANSFORMS // Whether to use transforms or absolute positioning.
261};
262
263
264// Not overrideable
265Shuffle.settings = {
266  useSizer: false,
267  itemCss : { // default CSS for each item
268    position: 'absolute',
269    top: 0,
270    left: 0,
271    visibility: 'visible'
272  },
273  revealAppendedDelay: 300,
274  lastSort: {},
275  lastFilter: ALL_ITEMS,
276  enabled: true,
277  destroyed: false,
278  initialized: false,
279  _animations: [],
280  _transitions: [],
281  _isMovementCanceled: false,
282  styleQueue: []
283};
284
285
286// Expose for testing.
287Shuffle.Point = Point;
288
289
290/**
291 * Static methods.
292 */
293
294/**
295 * If the browser has 3d transforms available, build a string with those,
296 * otherwise use 2d transforms.
297 * @param {Point} point X and Y positions.
298 * @param {number} scale Scale amount.
299 * @return {string} A normalized string which can be used with the transform style.
300 * @private
301 */
302Shuffle._getItemTransformString = function(point, scale) {
303  if ( HAS_TRANSFORMS_3D ) {
304    return 'translate3d(' + point.x + 'px, ' + point.y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)';
305  } else {
306    return 'translate(' + point.x + 'px, ' + point.y + 'px) scale(' + scale + ')';
307  }
308};
309
310
311/**
312 * Retrieve the computed style for an element, parsed as a float.
313 * @param {Element} element Element to get style for.
314 * @param {string} style Style property.
315 * @param {CSSStyleDeclaration} [styles] Optionally include clean styles to
316 *     use instead of asking for them again.
317 * @return {number} The parsed computed value or zero if that fails because IE
318 *     will return 'auto' when the element doesn't have margins instead of
319 *     the computed style.
320 * @private
321 */
322Shuffle._getNumberStyle = function( element, style, styles ) {
323  if ( HAS_COMPUTED_STYLE ) {
324    styles = styles || getStyles( element, null );
325    var value = Shuffle._getFloat( styles[ style ] );
326
327    // Support IE<=11 and W3C spec.
328    if ( !COMPUTED_SIZE_INCLUDES_PADDING && style === 'width' ) {
329      value += Shuffle._getFloat( styles.paddingLeft ) +
330        Shuffle._getFloat( styles.paddingRight ) +
331        Shuffle._getFloat( styles.borderLeftWidth ) +
332        Shuffle._getFloat( styles.borderRightWidth );
333    } else if ( !COMPUTED_SIZE_INCLUDES_PADDING && style === 'height' ) {
334      value += Shuffle._getFloat( styles.paddingTop ) +
335        Shuffle._getFloat( styles.paddingBottom ) +
336        Shuffle._getFloat( styles.borderTopWidth ) +
337        Shuffle._getFloat( styles.borderBottomWidth );
338    }
339
340    return value;
341  } else {
342    return Shuffle._getFloat( $( element ).css( style )  );
343  }
344};
345
346
347/**
348 * Parse a string as an float.
349 * @param {string} value String float.
350 * @return {number} The string as an float or zero.
351 * @private
352 */
353Shuffle._getFloat = function(value) {
354  return getNumber( parseFloat( value ) );
355};
356
357
358/**
359 * Returns the outer width of an element, optionally including its margins.
360 *
361 * There are a few different methods for getting the width of an element, none of
362 * which work perfectly for all Shuffle's use cases.
363 *
364 * 1. getBoundingClientRect() `left` and `right` properties.
365 *   - Accounts for transform scaled elements, making it useless for Shuffle
366 *   elements which have shrunk.
367 * 2. The `offsetWidth` property (or jQuery's CSS).
368 *   - This value stays the same regardless of the elements transform property,
369 *   however, it does not return subpixel values.
370 * 3. getComputedStyle()
371 *   - This works great Chrome, Firefox, Safari, but IE<=11 does not include
372 *   padding and border when box-sizing: border-box is set, requiring a feature
373 *   test and extra work to add the padding back for IE and other browsers which
374 *   follow the W3C spec here.
375 *
376 * @param {Element} element The element.
377 * @param {boolean} [includeMargins] Whether to include margins. Default is false.
378 * @return {number} The width.
379 */
380Shuffle._getOuterWidth = function( element, includeMargins ) {
381  // Store the styles so that they can be used by others without asking for it again.
382  var styles = getStyles( element, null );
383  var width = Shuffle._getNumberStyle( element, 'width', styles );
384
385  // Use jQuery here because it uses getComputedStyle internally and is
386  // cross-browser. Using the style property of the element will only work
387  // if there are inline styles.
388  if ( includeMargins ) {
389    var marginLeft = Shuffle._getNumberStyle( element, 'marginLeft', styles );
390    var marginRight = Shuffle._getNumberStyle( element, 'marginRight', styles );
391    width += marginLeft + marginRight;
392  }
393
394  return width;
395};
396
397
398/**
399 * Returns the outer height of an element, optionally including its margins.
400 * @param {Element} element The element.
401 * @param {boolean} [includeMargins] Whether to include margins. Default is false.
402 * @return {number} The height.
403 */
404Shuffle._getOuterHeight = function( element, includeMargins ) {
405  var styles = getStyles( element, null );
406  var height = Shuffle._getNumberStyle( element, 'height', styles );
407
408  if ( includeMargins ) {
409    var marginTop = Shuffle._getNumberStyle( element, 'marginTop', styles );
410    var marginBottom = Shuffle._getNumberStyle( element, 'marginBottom', styles );
411    height += marginTop + marginBottom;
412  }
413
414  return height;
415};
416
417
418/**
419 * Change a property or execute a function which will not have a transition
420 * @param {Element} element DOM element that won't be transitioned
421 * @param {Function} callback A function which will be called while transition
422 *     is set to 0ms.
423 * @param {Object} [context] Optional context for the callback function.
424 * @private
425 */
426Shuffle._skipTransition = function( element, callback, context ) {
427  var duration = element.style[ TRANSITION_DURATION ];
428
429  // Set the duration to zero so it happens immediately
430  element.style[ TRANSITION_DURATION ] = '0ms'; // ms needed for firefox!
431
432  callback.call( context );
433
434  // Force reflow
435  var reflow = element.offsetWidth;
436  // Avoid jshint warnings: unused variables and expressions.
437  reflow = null;
438
439  // Put the duration back
440  element.style[ TRANSITION_DURATION ] = duration;
441};
442
443
444/**
445 * Instance methods.
446 */
447
448Shuffle.prototype._init = function() {
449  this.$items = this._getItems();
450
451  this.sizer = this._getElementOption( this.sizer );
452
453  if ( this.sizer ) {
454    this.useSizer = true;
455  }
456
457  // Add class and invalidate styles
458  this.$el.addClass( Shuffle.ClassName.BASE );
459
460  // Set initial css for each item
461  this._initItems();
462
463  // Bind resize events
464  // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
465  $window.on('resize.' + SHUFFLE + '.' + this.unique, this._getResizeFunction());
466
467  // Get container css all in one request. Causes reflow
468  var containerCSS = this.$el.css(['position', 'overflow']);
469  var containerWidth = Shuffle._getOuterWidth( this.element );
470
471  // Add styles to the container if it doesn't have them.
472  this._validateStyles( containerCSS );
473
474  // We already got the container's width above, no need to cause another reflow getting it again...
475  // Calculate the number of columns there will be
476  this._setColumns( containerWidth );
477
478  // Kick off!
479  this.shuffle( this.group, this.initialSort );
480
481  // The shuffle items haven't had transitions set on them yet
482  // so the user doesn't see the first layout. Set them now that the first layout is done.
483  if ( this.supported ) {
484    defer(function() {
485      this._setTransitions();
486      this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing;
487    }, this);
488  }
489};
490
491
492/**
493 * Returns a throttled and proxied function for the resize handler.
494 * @return {Function}
495 * @private
496 */
497Shuffle.prototype._getResizeFunction = function() {
498  var resizeFunction = $.proxy( this._onResize, this );
499  return this.throttle ?
500      this.throttle( resizeFunction, this.throttleTime ) :
501      resizeFunction;
502};
503
504
505/**
506 * Retrieve an element from an option.
507 * @param {string|jQuery|Element} option The option to check.
508 * @return {?Element} The plain element or null.
509 * @private
510 */
511Shuffle.prototype._getElementOption = function( option ) {
512  // If column width is a string, treat is as a selector and search for the
513  // sizer element within the outermost container
514  if ( typeof option === 'string' ) {
515    return this.$el.find( option )[0] || null;
516
517  // Check for an element
518  } else if ( option && option.nodeType && option.nodeType === 1 ) {
519    return option;
520
521  // Check for jQuery object
522  } else if ( option && option.jquery ) {
523    return option[0];
524  }
525
526  return null;
527};
528
529
530/**
531 * Ensures the shuffle container has the css styles it needs applied to it.
532 * @param {Object} styles Key value pairs for position and overflow.
533 * @private
534 */
535Shuffle.prototype._validateStyles = function(styles) {
536  // Position cannot be static.
537  if ( styles.position === 'static' ) {
538    this.element.style.position = 'relative';
539  }
540
541  // Overflow has to be hidden
542  if ( styles.overflow !== 'hidden' ) {
543    this.element.style.overflow = 'hidden';
544  }
545};
546
547
548/**
549 * Filter the elements by a category.
550 * @param {string} [category] Category to filter by. If it's given, the last
551 *     category will be used to filter the items.
552 * @param {ArrayLike} [$collection] Optionally filter a collection. Defaults to
553 *     all the items.
554 * @return {jQuery} Filtered items.
555 * @private
556 */
557Shuffle.prototype._filter = function( category, $collection ) {
558  category = category || this.lastFilter;
559  $collection = $collection || this.$items;
560
561  var set = this._getFilteredSets( category, $collection );
562
563  // Individually add/remove concealed/filtered classes
564  this._toggleFilterClasses( set.filtered, set.concealed );
565
566  // Save the last filter in case elements are appended.
567  this.lastFilter = category;
568
569  // This is saved mainly because providing a filter function (like searching)
570  // will overwrite the `lastFilter` property every time its called.
571  if ( typeof category === 'string' ) {
572    this.group = category;
573  }
574
575  return set.filtered;
576};
577
578
579/**
580 * Returns an object containing the filtered and concealed elements.
581 * @param {string|Function} category Category or function to filter by.
582 * @param {ArrayLike.<Element>} $items A collection of items to filter.
583 * @return {!{filtered: jQuery, concealed: jQuery}}
584 * @private
585 */
586Shuffle.prototype._getFilteredSets = function( category, $items ) {
587  var $filtered = $();
588  var $concealed = $();
589
590  // category === 'all', add filtered class to everything
591  if ( category === ALL_ITEMS ) {
592    $filtered = $items;
593
594  // Loop through each item and use provided function to determine
595  // whether to hide it or not.
596  } else {
597    each($items, function( el ) {
598      var $item = $(el);
599      if ( this._doesPassFilter( category, $item ) ) {
600        $filtered = $filtered.add( $item );
601      } else {
602        $concealed = $concealed.add( $item );
603      }
604    }, this);
605  }
606
607  return {
608    filtered: $filtered,
609    concealed: $concealed
610  };
611};
612
613
614/**
615 * Test an item to see if it passes a category.
616 * @param {string|Function} category Category or function to filter by.
617 * @param {jQuery} $item A single item, wrapped with jQuery.
618 * @return {boolean} Whether it passes the category/filter.
619 * @private
620 */
621Shuffle.prototype._doesPassFilter = function( category, $item ) {
622  if ( $.isFunction( category ) ) {
623    return category.call( $item[0], $item, this );
624
625  // Check each element's data-groups attribute against the given category.
626  } else {
627    var groups = $item.data( FILTER_ATTRIBUTE_KEY );
628    var keys = this.delimeter && !$.isArray( groups ) ?
629        groups.split( this.delimeter ) :
630        groups;
631    return $.inArray(category, keys) > -1;
632  }
633};
634
635
636/**
637 * Toggles the filtered and concealed class names.
638 * @param {jQuery} $filtered Filtered set.
639 * @param {jQuery} $concealed Concealed set.
640 * @private
641 */
642Shuffle.prototype._toggleFilterClasses = function( $filtered, $concealed ) {
643  $filtered
644    .removeClass( Shuffle.ClassName.CONCEALED )
645    .addClass( Shuffle.ClassName.FILTERED );
646  $concealed
647    .removeClass( Shuffle.ClassName.FILTERED )
648    .addClass( Shuffle.ClassName.CONCEALED );
649};
650
651
652/**
653 * Set the initial css for each item
654 * @param {jQuery} [$items] Optionally specifiy at set to initialize
655 */
656Shuffle.prototype._initItems = function( $items ) {
657  $items = $items || this.$items;
658  $items.addClass([
659    Shuffle.ClassName.SHUFFLE_ITEM,
660    Shuffle.ClassName.FILTERED
661  ].join(' '));
662  $items.css( this.itemCss ).data('point', new Point()).data('scale', DEFAULT_SCALE);
663};
664
665
666/**
667 * Updates the filtered item count.
668 * @private
669 */
670Shuffle.prototype._updateItemCount = function() {
671  this.visibleItems = this._getFilteredItems().length;
672};
673
674
675/**
676 * Sets css transform transition on a an element.
677 * @param {Element} element Element to set transition on.
678 * @private
679 */
680Shuffle.prototype._setTransition = function( element ) {
681  element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' +
682    this.easing + ', opacity ' + this.speed + 'ms ' + this.easing;
683};
684
685
686/**
687 * Sets css transform transition on a group of elements.
688 * @param {ArrayLike.<Element>} $items Elements to set transitions on.
689 * @private
690 */
691Shuffle.prototype._setTransitions = function( $items ) {
692  $items = $items || this.$items;
693  each($items, function( el ) {
694    this._setTransition( el );
695  }, this);
696};
697
698
699/**
700 * Sets a transition delay on a collection of elements, making each delay
701 * greater than the last.
702 * @param {ArrayLike.<Element>} $collection Array to iterate over.
703 */
704Shuffle.prototype._setSequentialDelay = function( $collection ) {
705  if ( !this.supported ) {
706    return;
707  }
708
709  // $collection can be an array of dom elements or jquery object
710  each($collection, function( el, i ) {
711    // This works because the transition-property: transform, opacity;
712    el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * this.sequentialFadeDelay) + 'ms';
713  }, this);
714};
715
716
717Shuffle.prototype._getItems = function() {
718  return this.$el.children( this.itemSelector );
719};
720
721
722Shuffle.prototype._getFilteredItems = function() {
723  return this.$items.filter('.' + Shuffle.ClassName.FILTERED);
724};
725
726
727Shuffle.prototype._getConcealedItems = function() {
728  return this.$items.filter('.' + Shuffle.ClassName.CONCEALED);
729};
730
731
732/**
733 * Returns the column size, based on column width and sizer options.
734 * @param {number} containerWidth Size of the parent container.
735 * @param {number} gutterSize Size of the gutters.
736 * @return {number}
737 * @private
738 */
739Shuffle.prototype._getColumnSize = function( containerWidth, gutterSize ) {
740  var size;
741
742  // If the columnWidth property is a function, then the grid is fluid
743  if ( $.isFunction( this.columnWidth ) ) {
744    size = this.columnWidth(containerWidth);
745
746  // columnWidth option isn't a function, are they using a sizing element?
747  } else if ( this.useSizer ) {
748    size = Shuffle._getOuterWidth(this.sizer);
749
750  // if not, how about the explicitly set option?
751  } else if ( this.columnWidth ) {
752    size = this.columnWidth;
753
754  // or use the size of the first item
755  } else if ( this.$items.length > 0 ) {
756    size = Shuffle._getOuterWidth(this.$items[0], true);
757
758  // if there's no items, use size of container
759  } else {
760    size = containerWidth;
761  }
762
763  // Don't let them set a column width of zero.
764  if ( size === 0 ) {
765    size = containerWidth;
766  }
767
768  return size + gutterSize;
769};
770
771
772/**
773 * Returns the gutter size, based on gutter width and sizer options.
774 * @param {number} containerWidth Size of the parent container.
775 * @return {number}
776 * @private
777 */
778Shuffle.prototype._getGutterSize = function( containerWidth ) {
779  var size;
780  if ( $.isFunction( this.gutterWidth ) ) {
781    size = this.gutterWidth(containerWidth);
782  } else if ( this.useSizer ) {
783    size = Shuffle._getNumberStyle(this.sizer, 'marginLeft');
784  } else {
785    size = this.gutterWidth;
786  }
787
788  return size;
789};
790
791
792/**
793 * Calculate the number of columns to be used. Gets css if using sizer element.
794 * @param {number} [theContainerWidth] Optionally specify a container width if it's already available.
795 */
796Shuffle.prototype._setColumns = function( theContainerWidth ) {
797  var containerWidth = theContainerWidth || Shuffle._getOuterWidth( this.element );
798  var gutter = this._getGutterSize( containerWidth );
799  var columnWidth = this._getColumnSize( containerWidth, gutter );
800  var calculatedColumns = (containerWidth + gutter) / columnWidth;
801
802  // Widths given from getStyles are not precise enough...
803  if ( Math.abs(Math.round(calculatedColumns) - calculatedColumns) < this.columnThreshold ) {
804    // e.g. calculatedColumns = 11.998876
805    calculatedColumns = Math.round( calculatedColumns );
806  }
807
808  this.cols = Math.max( Math.floor(calculatedColumns), 1 );
809  this.containerWidth = containerWidth;
810  this.colWidth = columnWidth;
811};
812
813/**
814 * Adjust the height of the grid
815 */
816Shuffle.prototype._setContainerSize = function() {
817  this.$el.css( 'height', this._getContainerSize() );
818};
819
820
821/**
822 * Based on the column heights, it returns the biggest one.
823 * @return {number}
824 * @private
825 */
826Shuffle.prototype._getContainerSize = function() {
827  return arrayMax( this.positions );
828};
829
830
831/**
832 * Fire events with .shuffle namespace
833 */
834Shuffle.prototype._fire = function( name, args ) {
835  this.$el.trigger( name + '.' + SHUFFLE, args && args.length ? args : [ this ] );
836};
837
838
839/**
840 * Zeros out the y columns array, which is used to determine item placement.
841 * @private
842 */
843Shuffle.prototype._resetCols = function() {
844  var i = this.cols;
845  this.positions = [];
846  while (i--) {
847    this.positions.push( 0 );
848  }
849};
850
851
852/**
853 * Loops through each item that should be shown and calculates the x, y position.
854 * @param {Array.<Element>} items Array of items that will be shown/layed out in order in their array.
855 *     Because jQuery collection are always ordered in DOM order, we can't pass a jq collection.
856 * @param {boolean} [isOnlyPosition=false] If true this will position the items with zero opacity.
857 */
858Shuffle.prototype._layout = function( items, isOnlyPosition ) {
859  each(items, function( item ) {
860    this._layoutItem( item, !!isOnlyPosition );
861  }, this);
862
863  // `_layout` always happens after `_shrink`, so it's safe to process the style
864  // queue here with styles from the shrink method.
865  this._processStyleQueue();
866
867  // Adjust the height of the container.
868  this._setContainerSize();
869};
870
871
872/**
873 * Calculates the position of the item and pushes it onto the style queue.
874 * @param {Element} item Element which is being positioned.
875 * @param {boolean} isOnlyPosition Whether to position the item, but with zero
876 *     opacity so that it can fade in later.
877 * @private
878 */
879Shuffle.prototype._layoutItem = function( item, isOnlyPosition ) {
880  var $item = $(item);
881  var itemData = $item.data();
882  var currPos = itemData.point;
883  var currScale = itemData.scale;
884  var itemSize = {
885    width: Shuffle._getOuterWidth( item, true ),
886    height: Shuffle._getOuterHeight( item, true )
887  };
888  var pos = this._getItemPosition( itemSize );
889
890  // If the item will not change its position, do not add it to the render
891  // queue. Transitions don't fire when setting a property to the same value.
892  if ( Point.equals(currPos, pos) && currScale === DEFAULT_SCALE ) {
893    return;
894  }
895
896  // Save data for shrink
897  itemData.point = pos;
898  itemData.scale = DEFAULT_SCALE;
899
900  this.styleQueue.push({
901    $item: $item,
902    point: pos,
903    scale: DEFAULT_SCALE,
904    opacity: isOnlyPosition ? 0 : 1,
905    // Set styles immediately if there is no transition speed.
906    skipTransition: isOnlyPosition || this.speed === 0,
907    callfront: function() {
908      if ( !isOnlyPosition ) {
909        $item.css( 'visibility', 'visible' );
910      }
911    },
912    callback: function() {
913      if ( isOnlyPosition ) {
914        $item.css( 'visibility', 'hidden' );
915      }
916    }
917  });
918};
919
920
921/**
922 * Determine the location of the next item, based on its size.
923 * @param {{width: number, height: number}} itemSize Object with width and height.
924 * @return {Point}
925 * @private
926 */
927Shuffle.prototype._getItemPosition = function( itemSize ) {
928  var columnSpan = this._getColumnSpan( itemSize.width, this.colWidth, this.cols );
929
930  var setY = this._getColumnSet( columnSpan, this.cols );
931
932  // Finds the index of the smallest number in the set.
933  var shortColumnIndex = this._getShortColumn( setY, this.buffer );
934
935  // Position the item
936  var point = new Point(
937    Math.round( this.colWidth * shortColumnIndex ),
938    Math.round( setY[shortColumnIndex] ));
939
940  // Update the columns array with the new values for each column.
941  // e.g. before the update the columns could be [250, 0, 0, 0] for an item
942  // which spans 2 columns. After it would be [250, itemHeight, itemHeight, 0].
943  var setHeight = setY[shortColumnIndex] + itemSize.height;
944  var setSpan = this.cols + 1 - setY.length;
945  for ( var i = 0; i < setSpan; i++ ) {
946    this.positions[ shortColumnIndex + i ] = setHeight;
947  }
948
949  return point;
950};
951
952
953/**
954 * Determine the number of columns an items spans.
955 * @param {number} itemWidth Width of the item.
956 * @param {number} columnWidth Width of the column (includes gutter).
957 * @param {number} columns Total number of columns
958 * @return {number}
959 * @private
960 */
961Shuffle.prototype._getColumnSpan = function( itemWidth, columnWidth, columns ) {
962  var columnSpan = itemWidth / columnWidth;
963
964  // If the difference between the rounded column span number and the
965  // calculated column span number is really small, round the number to
966  // make it fit.
967  if ( Math.abs(Math.round( columnSpan ) - columnSpan ) < this.columnThreshold ) {
968    // e.g. columnSpan = 4.0089945390298745
969    columnSpan = Math.round( columnSpan );
970  }
971
972  // Ensure the column span is not more than the amount of columns in the whole layout.
973  return Math.min( Math.ceil( columnSpan ), columns );
974};
975
976
977/**
978 * Retrieves the column set to use for placement.
979 * @param {number} columnSpan The number of columns this current item spans.
980 * @param {number} columns The total columns in the grid.
981 * @return {Array.<number>} An array of numbers represeting the column set.
982 * @private
983 */
984Shuffle.prototype._getColumnSet = function( columnSpan, columns ) {
985  // The item spans only one column.
986  if ( columnSpan === 1 ) {
987    return this.positions;
988
989  // The item spans more than one column, figure out how many different
990  // places it could fit horizontally.
991  // The group count is the number of places within the positions this block
992  // could fit, ignoring the current positions of items.
993  // Imagine a 2 column brick as the second item in a 4 column grid with
994  // 10px height each. Find the places it would fit:
995  // [10, 0, 0, 0]
996  //  |   |  |
997  //  *   *  *
998  //
999  // Then take the places which fit and get the bigger of the two:
1000  // max([10, 0]), max([0, 0]), max([0, 0]) = [10, 0, 0]
1001  //
1002  // Next, find the first smallest number (the short column).
1003  // [10, 0, 0]
1004  //      |
1005  //      *
1006  //
1007  // And that's where it should be placed!
1008  } else {
1009    var groupCount = columns + 1 - columnSpan;
1010    var groupY = [];
1011
1012    // For how many possible positions for this item there are.
1013    for ( var i = 0; i < groupCount; i++ ) {
1014      // Find the bigger value for each place it could fit.
1015      groupY[i] = arrayMax( this.positions.slice( i, i + columnSpan ) );
1016    }
1017
1018    return groupY;
1019  }
1020};
1021
1022
1023/**
1024 * Find index of short column, the first from the left where this item will go.
1025 *
1026 * @param {Array.<number>} positions The array to search for the smallest number.
1027 * @param {number} buffer Optional buffer which is very useful when the height
1028 *     is a percentage of the width.
1029 * @return {number} Index of the short column.
1030 * @private
1031 */
1032Shuffle.prototype._getShortColumn = function( positions, buffer ) {
1033  var minPosition = arrayMin( positions );
1034  for (var i = 0, len = positions.length; i < len; i++) {
1035    if ( positions[i] >= minPosition - buffer && positions[i] <= minPosition + buffer ) {
1036      return i;
1037    }
1038  }
1039  return 0;
1040};
1041
1042
1043/**
1044 * Hides the elements that don't match our filter.
1045 * @param {jQuery} $collection jQuery collection to shrink.
1046 * @private
1047 */
1048Shuffle.prototype._shrink = function( $collection ) {
1049  var $concealed = $collection || this._getConcealedItems();
1050
1051  each($concealed, function( item ) {
1052    var $item = $(item);
1053    var itemData = $item.data();
1054
1055    // Continuing would add a transitionend event listener to the element, but
1056    // that listener would not execute because the transform and opacity would
1057    // stay the same.
1058    if ( itemData.scale === CONCEALED_SCALE ) {
1059      return;
1060    }
1061
1062    itemData.scale = CONCEALED_SCALE;
1063
1064    this.styleQueue.push({
1065      $item: $item,
1066      point: itemData.point,
1067      scale : CONCEALED_SCALE,
1068      opacity: 0,
1069      callback: function() {
1070        $item.css( 'visibility', 'hidden' );
1071      }
1072    });
1073  }, this);
1074};
1075
1076
1077/**
1078 * Resize handler.
1079 * @private
1080 */
1081Shuffle.prototype._onResize = function() {
1082  // If shuffle is disabled, destroyed, don't do anything
1083  if ( !this.enabled || this.destroyed ) {
1084    return;
1085  }
1086
1087  // Will need to check height in the future if it's layed out horizontaly
1088  var containerWidth = Shuffle._getOuterWidth( this.element );
1089
1090  // containerWidth hasn't changed, don't do anything
1091  if ( containerWidth === this.containerWidth ) {
1092    return;
1093  }
1094
1095  this.update();
1096};
1097
1098
1099/**
1100 * Returns styles for either jQuery animate or transition.
1101 * @param {Object} opts Transition options.
1102 * @return {!Object} Transforms for transitions, left/top for animate.
1103 * @private
1104 */
1105Shuffle.prototype._getStylesForTransition = function( opts ) {
1106  var styles = {
1107    opacity: opts.opacity
1108  };
1109
1110  if ( this.supported ) {
1111    styles[ TRANSFORM ] = Shuffle._getItemTransformString( opts.point, opts.scale );
1112  } else {
1113    styles.left = opts.point.x;
1114    styles.top = opts.point.y;
1115  }
1116
1117  return styles;
1118};
1119
1120
1121/**
1122 * Transitions an item in the grid
1123 *
1124 * @param {Object} opts options.
1125 * @param {jQuery} opts.$item jQuery object representing the current item.
1126 * @param {Point} opts.point A point object with the x and y coordinates.
1127 * @param {number} opts.scale Amount to scale the item.
1128 * @param {number} opts.opacity Opacity of the item.
1129 * @param {Function} opts.callback Complete function for the animation.
1130 * @param {Function} opts.callfront Function to call before transitioning.
1131 * @private
1132 */
1133Shuffle.prototype._transition = function( opts ) {
1134  var styles = this._getStylesForTransition( opts );
1135  this._startItemAnimation( opts
1135.$item, styles, opts.callfront || $.noop, opts.callback || $.noop );
1136};
1137
1138
1139Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) {
1140  var _this = this;
1141  // Transition end handler removes its listener.
1142  function handleTransitionEnd( evt ) {
1143    // Make sure this event handler has not bubbled up from a child.
1144    if ( evt.target === evt.currentTarget ) {
1145      $( evt.target ).off( TRANSITIONEND, handleTransitionEnd );
1146      _this._removeTransitionReference(reference);
1147      callback();
1148    }
1149  }
1150
1151  var reference = {
1152    $element: $item,
1153    handler: handleTransitionEnd
1154  };
1155
1156  callfront();
1157
1158  // Transitions are not set until shuffle has loaded to avoid the initial transition.
1159  if ( !this.initialized ) {
1160    $item.css( styles );
1161    callback();
1162    return;
1163  }
1164
1165  // Use CSS Transforms if we have them
1166  if ( this.supported ) {
1167    $item.css( styles );
1168    $item.on( TRANSITIONEND, handleTransitionEnd );
1169    this._transitions.push(reference);
1170
1171  // Use jQuery to animate left/top
1172  } else {
1173    // Save the deferred object which jQuery returns.
1174    var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback );
1175    // Push the animation to the list of pending animations.
1176    this._animations.push( anim.promise() );
1177  }
1178};
1179
1180
1181/**
1182 * Execute the styles gathered in the style queue. This applies styles to elements,
1183 * triggering transitions.
1184 * @param {boolean} noLayout Whether to trigger a layout event.
1185 * @private
1186 */
1187Shuffle.prototype._processStyleQueue = function( noLayout ) {
1188  if ( this.isTransitioning ) {
1189    this._cancelMovement();
1190  }
1191
1192  var $transitions = $();
1193
1194  // Iterate over the queue and keep track of ones that use transitions.
1195  each(this.styleQueue, function( transitionObj ) {
1196    if ( transitionObj.skipTransition ) {
1197      this._styleImmediately( transitionObj );
1198    } else {
1199      $transitions = $transitions.add( transitionObj.$item );
1200      this._transition( transitionObj );
1201    }
1202  }, this);
1203
1204
1205  if ( $transitions.length > 0 && this.initialized && this.speed > 0 ) {
1206    // Set flag that shuffle is currently in motion.
1207    this.isTransitioning = true;
1208
1209    if ( this.supported ) {
1210      this._whenCollectionDone( $transitions, TRANSITIONEND, this._movementFinished );
1211
1212    // The _transition function appends a promise to the animations array.
1213    // When they're all complete, do things.
1214    } else {
1215      this._whenAnimationsDone( this._movementFinished );
1216    }
1217
1218  // A call to layout happened, but none of the newly filtered items will
1219  // change position. Asynchronously fire the callback here.
1220  } else if ( !noLayout ) {
1221    defer( this._layoutEnd, this );
1222  }
1223
1224  // Remove everything in the style queue
1225  this.styleQueue.length = 0;
1226};
1227
1228Shuffle.prototype._cancelMovement = function() {
1229  if (this.supported) {
1230    // Remove the transition end event for each listener.
1231    each(this._transitions, function( transition ) {
1232      transition.$element.off( TRANSITIONEND, transition.handler );
1233    });
1234  } else {
1235    // Even when `stop` is called on the jQuery animation, its promise will
1236    // still be resolved. Since it cannot be determine from within that callback
1237    // whether the animation was stopped or not, a flag is set here to distinguish
1238    // between the two states.
1239    this._isMovementCanceled = true;
1240    this.$items.stop(true);
1241    this._isMovementCanceled = false;
1242  }
1243
1244  // Reset the array.
1245  this._transitions.length = 0;
1246
1247  // Show it's no longer active.
1248  this.isTransitioning = false;
1249};
1250
1251Shuffle.prototype._removeTransitionReference = function(ref) {
1252  var indexInArray = $.inArray(ref, this._transitions);
1253  if (indexInArray > -1) {
1254    this._transitions.splice(indexInArray, 1);
1255  }
1256};
1257
1258
1259/**
1260 * Apply styles without a transition.
1261 * @param {Object} opts Transitions options object.
1262 * @private
1263 */
1264Shuffle.prototype._styleImmediately = function( opts ) {
1265  Shuffle._skipTransition(opts.$item[0], function() {
1266    opts.$item.css( this._getStylesForTransition( opts ) );
1267  }, this);
1268};
1269
1270Shuffle.prototype._movementFinished = function() {
1271  this.isTransitioning = false;
1272  this._layoutEnd();
1273};
1274
1275Shuffle.prototype._layoutEnd = function() {
1276  this._fire( Shuffle.EventType.LAYOUT );
1277};
1278
1279Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) {
1280  // Add classes and set initial positions.
1281  this._initItems( $newItems );
1282
1283  // Add transition to each item.
1284  this._setTransitions( $newItems );
1285
1286  // Update the list of
1287  this.$items = this._getItems();
1288
1289  // Shrink all items (without transitions).
1290  this._shrink( $newItems );
1291  each(this.styleQueue, function( transitionObj ) {
1292    transitionObj.skipTransition = true;
1293  });
1294
1295  // Apply shrink positions, but do not cause a layout event.
1296  this._processStyleQueue( true );
1297
1298  if ( addToEnd ) {
1299    this._addItemsToEnd( $newItems, isSequential );
1300  } else {
1301    this.shuffle( this.lastFilter );
1302  }
1303};
1304
1305
1306Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) {
1307  // Get ones that passed the current filter
1308  var $passed = this._filter( null, $newItems );
1309  var passed = $passed.get();
1310
1311  // How many filtered elements?
1312  this._updateItemCount();
1313
1314  this._layout( passed, true );
1315
1316  if ( isSequential && this.supported ) {
1317    this._setSequentialDelay( passed );
1318  }
1319
1320  this._revealAppended( passed );
1321};
1322
1323
1324/**
1325 * Triggers appended elements to fade in.
1326 * @param {ArrayLike.<Element>} $newFilteredItems Collection of elements.
1327 * @private
1328 */
1329Shuffle.prototype._revealAppended = function( newFilteredItems ) {
1330  defer(function() {
1331    each(newFilteredItems, function( el ) {
1332      var $item = $( el );
1333      this._transition({
1334        $item: $item,
1335        opacity: 1,
1336        point: $item.data('point'),
1337        scale: DEFAULT_SCALE
1338      });
1339    }, this);
1340
1341    this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() {
1342      $(newFilteredItems).css( TRANSITION_DELAY, '0ms' );
1343      this._movementFinished();
1344    });
1345  }, this, this.revealAppendedDelay);
1346};
1347
1348
1349/**
1350 * Execute a function when an event has been triggered for every item in a collection.
1351 * @param {jQuery} $collection Collection of elements.
1352 * @param {string} eventName Event to listen for.
1353 * @param {Function} callback Callback to execute when they're done.
1354 * @private
1355 */
1356Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callback ) {
1357  var done = 0;
1358  var items = $collection.length;
1359  var self = this;
1360
1361  function handleEventName( evt ) {
1362    if ( evt.target === evt.currentTarget ) {
1363      $( evt.target ).off( eventName, handleEventName );
1364      done++;
1365
1366      // Execute callback if all items have emitted the correct event.
1367      if ( done === items ) {
1368        self._removeTransitionReference(reference);
1369        callback.call( self );
1370      }
1371    }
1372  }
1373
1374  var reference = {
1375    $element: $collection,
1376    handler: handleEventName
1377  };
1378
1379  // Bind the event to all items.
1380  $collection.on( eventName, handleEventName );
1381
1382  // Keep track of transitionend events so they can be removed.
1383  this._transitions.push(reference);
1384};
1385
1386
1387/**
1388 * Execute a callback after jQuery `animate` for a collection has finished.
1389 * @param {Function} callback Callback to execute when they're done.
1390 * @private
1391 */
1392Shuffle.prototype._whenAnimationsDone = function( callback ) {
1393  $.when.apply( null, this._animations ).always( $.proxy( function() {
1394    this._animations.length = 0;
1395    if (!this._isMovementCanceled) {
1396      callback.call( this );
1397    }
1398  }, this ));
1399};
1400
1401
1402/**
1403 * Public Methods
1404 */
1405
1406/**
1407 * The magic. This is what makes the plugin 'shuffle'
1408 * @param {string|Function} [category] Category to filter by. Can be a function
1409 * @param {Object} [sortObj] A sort object which can sort the filtered set
1410 */
1411Shuffle.prototype.shuffle = function( category, sortObj ) {
1412  if ( !this.enabled ) {
1413    return;
1414  }
1415
1416  if ( !category ) {
1417    category = ALL_ITEMS;
1418  }
1419
1420  this._filter( category );
1421
1422  // How many filtered elements?
1423  this._updateItemCount();
1424
1425  // Shrink each concealed item
1426  this._shrink();
1427
1428  // Update transforms on .filtered elements so they will animate to their new positions
1429  this.sort( sortObj );
1430};
1431
1432
1433/**
1434 * Gets the .filtered elements, sorts them, and passes them to layout.
1435 * @param {Object} opts the options object for the sorted plugin
1436 */
1437Shuffle.prototype.sort = function( opts ) {
1438  if ( this.enabled ) {
1439    this._resetCols();
1440
1441    var sortOptions = opts || this.lastSort;
1442    var items = this._getFilteredItems().sorted( sortOptions );
1443
1444    this._layout( items );
1445
1446    this.lastSort = sortOptions;
1447  }
1448};
1449
1450
1451/**
1452 * Reposition everything.
1453 * @param {boolean} isOnlyLayout If true, column and gutter widths won't be
1454 *     recalculated.
1455 */
1456Shuffle.prototype.update = function( isOnlyLayout ) {
1457  if ( this.enabled ) {
1458
1459    if ( !isOnlyLayout ) {
1460      // Get updated colCount
1461      this._setColumns();
1462    }
1463
1464    // Layout items
1465    this.sort();
1466  }
1467};
1468
1469
1470/**
1471 * Use this instead of `update()` if you don't need the columns and gutters updated
1472 * Maybe an image inside `shuffle` loaded (and now has a height), which means calculations
1473 * could be off.
1474 */
1475Shuffle.prototype.layout = function() {
1476  this.update( true );
1477};
1478
1479
1480/**
1481 * New items have been appended to shuffle. Fade them in sequentially
1482 * @param {jQuery} $newItems jQuery collection of new items
1483 * @param {boolean} [addToEnd=false] If true, new items will be added to the end / bottom
1484 *     of the items. If not true, items will be mixed in with the current sort order.
1485 * @param {boolean} [isSequential=true] If false, new items won't sequentially fade in
1486 */
1487Shuffle.prototype.appended = function( $newItems, addToEnd, isSequential ) {
1488  this._addItems( $newItems, addToEnd === true, isSequential !== false );
1489};
1490
1491
1492/**
1493 * Disables shuffle from updating dimensions and layout on resize
1494 */
1495Shuffle.prototype.disable = function() {
1496  this.enabled = false;
1497};
1498
1499
1500/**
1501 * Enables shuffle again
1502 * @param {boolean} [isUpdateLayout=true] if undefined, shuffle will update columns and gutters
1503 */
1504Shuffle.prototype.enable = function( isUpdateLayout ) {
1505  this.enabled = true;
1506  if ( isUpdateLayout !== false ) {
1507    this.update();
1508  }
1509};
1510
1511
1512/**
1513 * Remove 1 or more shuffle items
1514 * @param {jQuery} $collection A jQuery object containing one or more element in shuffle
1515 * @return {Shuffle} The shuffle object
1516 */
1517Shuffle.prototype.remove = function( $collection ) {
1518
1519  // If this isn't a jquery object, exit
1520  if ( !$collection.length || !$collection.jquery ) {
1521    return;
1522  }
1523
1524  function handleRemoved() {
1525    // Remove the collection in the callback
1526    $collection.remove();
1527
1528    // Update things now that elements have been removed.
1529    this.$items = this._getItems();
1530    this._updateItemCount();
1531
1532    this._fire( Shuffle.EventType.REMOVED, [ $collection, this ] );
1533
1534    // Let it get garbage collected
1535    $collection = null;
1536  }
1537
1538  // Hide collection first.
1539  this._toggleFilterClasses( $(), $collection );
1540  this._shrink( $collection );
1541
1542  this.sort();
1543
1544  this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) );
1545};
1546
1547
1548/**
1549 * Destroys shuffle, removes events, styles, and classes
1550 */
1551Shuffle.prototype.destroy = function() {
1552  // If there is more than one shuffle instance on the page,
1553  // removing the resize handler from the window would remove them
1554  // all. This is why a unique value is needed.
1555  $window.off('.' + this.unique);
1556
1557  // Reset container styles
1558  this.$el
1559      .removeClass( SHUFFLE )
1560      .removeAttr('style')
1561      .removeData( SHUFFLE );
1562
1563  // Reset individual item styles
1564  this.$items
1565      .removeAttr('style')
1566      .removeData('point')
1567      .removeData('scale')
1568      .removeClass([
1569        Shuffle.ClassName.CONCEALED,
1570        Shuffle.ClassName.FILTERED,
1571        Shuffle.ClassName.SHUFFLE_ITEM
1572      ].join(' '));
1573
1574  // Null DOM references
1575  this.$items = null;
1576  this.$el = null;
1577  this.sizer = null;
1578  this.element = null;
1579  this._transitions = null;
1580
1581  // Set a flag so if a debounced resize has been triggered,
1582  // it can first check if it is actually destroyed and not doing anything
1583  this.destroyed = true;
1584};
1585
1586
1587// Plugin definition
1588$.fn.shuffle = function( opts ) {
1589  var args = Array.prototype.slice.call( arguments, 1 );
1590  return this.each(function() {
1591    var $this = $( this );
1592    var shuffle = $this.data( SHUFFLE );
1593
1594    // If we don't have a stored shuffle, make a new one and save it
1595    if ( !shuffle ) {
1596      shuffle = new Shuffle( this, opts );
1597      $this.data( SHUFFLE, shuffle );
1598    } else if ( typeof opts === 'string' && shuffle[ opts ] ) {
1599      shuffle[ opts ].apply( shuffle, args );
1600    }
1601  });
1602};
1603
1604
1605// http://stackoverflow.com/a/962890/373422
1606function randomize( array ) {
1607  var tmp, current;
1608  var top = array.length;
1609
1610  if ( !top ) {
1611    return array;
1612  }
1613
1614  while ( --top ) {
1615    current = Math.floor( Math.random() * (top + 1) );
1616    tmp = array[ current ];
1617    array[ current ] = array[ top ];
1618    array[ top ] = tmp;
1619  }
1620
1621  return array;
1622}
1623
1624
1625// You can return `undefined` from the `by` function to revert to DOM order
1626// This plugin does NOT return a jQuery object. It returns a plain array because
1627// jQuery sorts everything in DOM order.
1628$.fn.sorted = function(options) {
1629  var opts = $.extend({}, $.fn.sorted.defaults, options);
1630  var arr = this.get();
1631  var revert = false;
1632
1633  if ( !arr.length ) {
1634    return [];
1635  }
1636
1637  if ( opts.randomize ) {
1638    return randomize( arr );
1639  }
1640
1641  // Sort the elements by the opts.by function.
1642  // If we don't have opts.by, default to DOM order
1643  if ( $.isFunction( opts.by ) ) {
1644    arr.sort(function(a, b) {
1645
1646      // Exit early if we already know we want to revert
1647      if ( revert ) {
1648        return 0;
1649      }
1650
1651      var valA = opts.by($(a));
1652      var valB = opts.by($(b));
1653
1654      // If both values are undefined, use the DOM order
1655      if ( valA === undefined && valB === undefined ) {
1656        revert = true;
1657        return 0;
1658      }
1659
1660      if ( valA < valB || valA === 'sortFirst' || valB === 'sortLast' ) {
1661        return -1;
1662      }
1663
1664      if ( valA > valB || valA === 'sortLast' || valB === 'sortFirst' ) {
1665        return 1;
1666      }
1667
1668      return 0;
1669    });
1670  }
1671
1672  // Revert to the original array if necessary
1673  if ( revert ) {
1674    return this.get();
1675  }
1676
1677  if ( opts.reverse ) {
1678    arr.reverse();
1679  }
1680
1681  return arr;
1682};
1683
1684
1685$.fn.sorted.defaults = {
1686  reverse: false, // Use array.reverse() to reverse the results
1687  by: null, // Sorting function
1688  randomize: false // If true, this will skip the sorting and return a randomized order in the array
1689};
1690
1691return Shuffle;
1692
1693});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.