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=["­",'<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.