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