PageSourceSearch

https://www.cop.es/IPsyNet/js/jquery.isotope.js

js cop.es collected 2026-10-02 05:50:28 UTC 44,657 bytes, 1,407 lines download raw bytes

1/**
2 * Isotope v1.5.25
3 * An exquisite jQuery plugin for magical layouts
4 * http://isotope.metafizzy.co
5 *
6 * Commercial use requires one-time license fee
7 * http://metafizzy.co/#licenses
8 *
9 * Copyright 2012 David DeSandro / Metafizzy
10 */
11
12/*jshint asi: true, browser: true, curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, strict: true, undef: true */
13/*global jQuery: false */
14
15(function( window, $, undefined ){
16
17  'use strict';
18
19  // get global vars
20  var document = window.document;
21  var Modernizr = window.Modernizr;
22
23  // helper function
24  var capitalize = function( str ) {
25    return str.charAt(0).toUpperCase() + str.slice(1);
26  };
27
28  // ========================= getStyleProperty by kangax ===============================
29  // http://perfectionkills.com/feature-testing-css-properties/
30
31  var prefixes = 'Moz Webkit O Ms'.split(' ');
32
33  var getStyleProperty = function( propName ) {
34    var style = document.documentElement.style,
35        prefixed;
36
37    // test standard property first
38    if ( typeof style[propName] === 'string' ) {
39      return propName;
40    }
41
42    // capitalize
43    propName = capitalize( propName );
44
45    // test vendor specific properties
46    for ( var i=0, len = prefixes.length; i < len; i++ ) {
47      prefixed = prefixes[i] + propName;
48      if ( typeof style[ prefixed ] === 'string' ) {
49        return prefixed;
50      }
51    }
52  };
53
54  var transformProp = getStyleProperty('transform'),
55      transitionProp = getStyleProperty('transitionProperty');
56
57
58  // ========================= miniModernizr ===============================
59  // <3<3<3 and thanks to Faruk and Paul for doing the heavy lifting
60
61  /*!
62   * Modernizr v1.6ish: miniModernizr for Isotope
63   * http://www.modernizr.com
64   *
65   * Developed by:
66   * - Faruk Ates  http://farukat.es/
67   * - Paul Irish  http://paulirish.com/
68   *
69   * Copyright (c) 2009-2010
70   * Dual-licensed under the BSD or MIT licenses.
71   * http://www.modernizr.com/license/
72   */
73
74  /*
75   * This version whittles down the script just to check support for
76   * CSS transitions, transforms, and 3D transforms.
77  */
78
79  var tests = {
80    csstransforms: function() {
81      return !!transformProp;
82    },
83
84    csstransforms3d: function() {
85      var test = !!getStyleProperty('perspective');
86      // double check for Chrome's false positive
87      if ( test ) {
88        var vendorCSSPrefixes = ' -o- -moz- -ms- -webkit- -khtml- '.split(' '),
89            mediaQuery = '@media (' + vendorCSSPrefixes.join('transform-3d),(') + 'modernizr)',
90            $style = $('<style>' + mediaQuery + '{#modernizr{height:3px}}' + '</style>')
91                        .appendTo('head'),
92            $div = $('<div id="modernizr" />').appendTo('html');
93
94        test = $div.height() === 3;
95
96        $div.remove();
97        $style.remove();
98      }
99      return test;
100    },
101
102    csstransitions: function() {
103      return !!transitionProp;
104    }
105  };
106
107  var testName;
108
109  if ( Modernizr ) {
110    // if there's a previous Modernzir, check if there are necessary tests
111    for ( testName in tests) {
112      if ( !Modernizr.hasOwnProperty( testName ) ) {
113        // if test hasn't been run, use addTest to run it
114        Modernizr.addTest( testName, tests[ testName ] );
115      }
116    }
117  } else {
118    // or create new mini Modernizr that just has the 3 tests
119    Modernizr = window.Modernizr = {
120      _version : '1.6ish: miniModernizr for Isotope'
121    };
122
123    var classes = ' ';
124    var result;
125
126    // Run through tests
127    for ( testName in tests) {
128      result = tests[ testName ]();
129      Modernizr[ testName ] = result;
130      classes += ' ' + ( result ?  '' : 'no-' ) + testName;
131    }
132
133    // Add the new classes to the <html> element.
134    $('html').addClass( classes );
135  }
136
137
138  // ========================= isoTransform ===============================
139
140  /**
141   *  provides hooks for .css({ scale: value, translate: [x, y] })
142   *  Progressively enhanced CSS transforms
143   *  Uses hardware accelerated 3D transforms for Safari
144   *  or falls back to 2D transforms.
145   */
146
147  if ( Modernizr.csstransforms ) {
148
149        // i.e. transformFnNotations.scale(0.5) >> 'scale3d( 0.5, 0.5, 1)'
150    var transformFnNotations = Modernizr.csstransforms3d ?
151      { // 3D transform functions
152        translate : function ( position ) {
153          return 'translate3d(' + position[0] + 'px, ' + position[1] + 'px, 0) ';
154        },
155        scale : function ( scale ) {
156          return 'scale3d(' + scale + ', ' + scale + ', 1) ';
157        }
158      } :
159      { // 2D transform functions
160        translate : function ( position ) {
161          return 'translate(' + position[0] + 'px, ' + position[1] + 'px) ';
162        },
163        scale : function ( scale ) {
164          return 'scale(' + scale + ') ';
165        }
166      }
167    ;
168
169    var setIsoTransform = function ( elem, name, value ) {
170          // unpack current transform data
171      var data =  $.data( elem, 'isoTransform' ) || {},
172          newData = {},
173          fnName,
174          transformObj = {},
175          transformValue;
176
177      // i.e. newData.scale = 0.5
178      newData[ name ] = value;
179      // extend new value over current data
180      $.extend( data, newData );
181
182      for ( fnName in data ) {
183        transformValue = data[ fnName ];
184        transformObj[ fnName ] = transformFnNotations[ fnName ]( transformValue );
185      }
186
187      // get proper order
188      // ideally, we could loop through this give an array, but since we only have
189      // a couple transforms we're keeping track of, we'll do it like so
190      var translateFn = transformObj.translate || '',
191          scaleFn = transformObj.scale || '',
192          // sorting so translate always comes first
193          valueFns = translateFn + scaleFn;
194
195      // set data back in elem
196      $.data( elem, 'isoTransform', data );
197
198      // set name to vendor specific property
199      elem.style[ transformProp ] = valueFns;
200    };
201
202    // ==================== scale ===================
203
204    $.cssNumber.scale = true;
205
206    $.cssHooks.scale = {
207      set: function( elem, value ) {
208        // uncomment this bit if you want to properly parse strings
209        // if ( typeof value === 'string' ) {
210        //   value = parseFloat( value );
211        // }
212        setIsoTransform( elem, 'scale', value );
213      },
214      get: function( elem, computed ) {
215        var transform = $.data( elem, 'isoTransform' );
216        return transform && transform.scale ? transform.scale : 1;
217      }
218    };
219
220    $.fx.step.scale = function( fx ) {
221      $.cssHooks.scale.set( fx.elem, fx.now+fx.unit );
222    };
223
224
225    // ==================== translate ===================
226
227    $.cssNumber.translate = true;
228
229    $.cssHooks.translate = {
230      set: function( elem, value ) {
231
232        // uncomment this bit if you want to properly parse strings
233        // if ( typeof value === 'string' ) {
234        //   value = value.split(' ');
235        // }
236        //
237        // var i, val;
238        // for ( i = 0; i < 2; i++ ) {
239        //   val = value[i];
240        //   if ( typeof val === 'string' ) {
241        //     val = parseInt( val );
242        //   }
243        // }
244
245        setIsoTransform( elem, 'translate', value );
246      },
247
248      get: function( elem, computed ) {
249        var transform = $.data( elem, 'isoTransform' );
250        return transform && transform.translate ? transform.translate : [ 0, 0 ];
251      }
252    };
253
254  }
255
256  // ========================= get transition-end event ===============================
257  var transitionEndEvent, transitionDurProp;
258
259  if ( Modernizr.csstransitions ) {
260    transitionEndEvent = {
261      WebkitTransitionProperty: 'webkitTransitionEnd',  // webkit
262      MozTransitionProperty: 'transitionend',
263      OTransitionProperty: 'oTransitionEnd otransitionend',
264      transitionProperty: 'transitionend'
265    }[ transitionProp ];
266
267    transitionDurProp = getStyleProperty('transitionDuration');
268  }
269
270  // ========================= smartresize ===============================
271
272  /*
273   * smartresize: debounced resize event for jQuery
274   *
275   * latest version and complete README available on Github:
276   * https://github.com/louisremi/jquery.smartresize.js
277   *
278   * Copyright 2011 @louis_remi
279   * Licensed under the MIT license.
280   */
281
282  var $event = $.event,
283      dispatchMethod = $.event.handle ? 'handle' : 'dispatch',
284      resizeTimeout;
285
286  $event.special.smartresize = {
287    setup: function() {
288      $(this).bind( "resize", $event.special.smartresize.handler );
289    },
290    teardown: function() {
291      $(this).unbind( "resize", $event.special.smartresize.handler );
292    },
293    handler: function( event, execAsap ) {
294      // Save the context
295      var context = this,
296          args = arguments;
297
298      // set correct event type
299      event.type = "smartresize";
300
301      if ( resizeTimeout ) { clearTimeout( resizeTimeout ); }
302      resizeTimeout = setTimeout(function() {
303        $event[ dispatchMethod ].apply( context, args );
304      }, execAsap === "execAsap"? 0 : 100 );
305    }
306  };
307
308  $.fn.smartresize = function( fn ) {
309    return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] );
310  };
311
312
313
314// ========================= Isotope ===============================
315
316
317  // our "Widget" object constructor
318  $.Isotope = function( options, element, callback ){
319    this.element = $( element );
320
321    this._create( options );
322    this._init( callback );
323  };
324
325  // styles of container element we want to keep track of
326  var isoContainerStyles = [ 'width', 'height' ];
327
328  var $window = $(window);
329
330  $.Isotope.settings = {
331    resizable: true,
332    layoutMode : 'masonry',
333    containerClass : 'isotope',
334    itemClass : 'isotope-item',
335    hiddenClass : 'isotope-hidden',
336    hiddenStyle: { opacity: 0, scale: 0.001 },
337    visibleStyle: { opacity: 1, scale: 1 },
338    containerStyle: {
339      position: 'relative',
340      overflow: 'hidden'
341    },
342    animationEngine: 'best-available',
343    animationOptions: {
344      queue: false,
345      duration: 800
346    },
347    sortBy : 'original-order',
348    sortAscending : true,
349    resizesContainer : true,
350    transformsEnabled: true,
351    itemPositionDataEnabled: false
352  };
353
354  $.Isotope.prototype = {
355
356    // sets up widget
357    _create : function( options ) {
358
359      this.options = $.extend( {}, $.Isotope.settings, options );
360
361      this.styleQueue = [];
362      this.elemCount = 0;
363
364      // get original styles in case we re-apply them in .destroy()
365      var elemStyle = this.element[0].style;
366      this.originalStyle = {};
367      // keep track of container styles
368      var containerStyles = isoContainerStyles.slice(0);
369      for ( var prop in this.options.containerStyle ) {
370        containerStyles.push( prop );
371      }
372      for ( var i=0, len = containerStyles.length; i < len; i++ ) {
373        prop = containerStyles[i];
374        this.originalStyle[ prop ] = elemStyle[ prop ] || '';
375      }
376      // apply container style from options
377      this.element.css( this.options.containerStyle );
378
379      this._updateAnimationEngine();
380      this._updateUsingTransforms();
381
382      // sorting
383      var originalOrderSorter = {
384        'original-order' : function( $elem, instance ) {
385          instance.elemCount ++;
386          return instance.elemCount;
387        },
388        random : function() {
389          return Math.random();
390        }
391      };
392
393      this.options.getSortData = $.extend( this.options.getSortData, originalOrderSorter );
394
395      // need to get atoms
396      this.reloadItems();
397
398      // get top left position of where the bricks should be
399      this.offset = {
400        left: parseInt( ( this.element.css('padding-left') || 0 ), 10 ),
401        top: parseInt( ( this.element.css('padding-top') || 0 ), 10 )
402      };
403
404      // add isotope class first time around
405      var instance = this;
406      setTimeout( function() {
407        instance.element.addClass( instance.options.containerClass );
408      }, 0 );
409
410      // bind resize method
411      if ( this.options.resizable ) {
412        $window.bind( 'smartresize.isotope', function() {
413          instance.resize();
414        });
415      }
416
417      // dismiss all click events from hidden events
418      this.element.delegate( '.' + this.options.hiddenClass, 'click', function(){
419        return false;
420      });
421
422    },
423
424    _getAtoms : function( $elems ) {
425      var selector = this.options.itemSelector,
426          // filter & find
427          $atoms = selector ? $elems.filter( selector ).add( $elems.find( selector ) ) : $elems,
428          // base style for atoms
429          atomStyle = { position: 'absolute' };
430
431      // filter out text nodes
432      $atoms = $atoms.filter( function( i, atom ) {
433        return atom.nodeType === 1;
434      });
435
436      if ( this.usingTransforms ) {
437        atomStyle.left = 0;
438        atomStyle.top = 0;
439      }
440
441      $atoms.css( atomStyle ).addClass( this.options.itemClass );
442
443      this.updateSortData( $atoms, true );
444
445      return $atoms;
446    },
447
448    // _init fires when your instance is first created
449    // (from the constructor above), and when you
450    // attempt to initialize the widget again (by the bridge)
451    // after it has already been initialized.
452    _init : function( callback ) {
453
454      this.$filteredAtoms = this._filter( this.$allAtoms );
455      this._sort();
456      this.reLayout( callback );
457
458    },
459
460    option : function( opts ){
461      // change options AFTER initialization:
462      // signature: $('#foo').bar({ cool:false });
463      if ( $.isPlainObject( opts ) ){
464        this.options = $.extend( true, this.options, opts );
465
466        // trigger _updateOptionName if it exists
467        var updateOptionFn;
468        for ( var optionName in opts ) {
469          updateOptionFn = '_update' + capitalize( optionName );
470          if ( this[ updateOptionFn ] ) {
471            this[ updateOptionFn ]();
472          }
473        }
474      }
475    },
476
477    // ====================== updaters ====================== //
478    // kind of like setters
479
480    _updateAnimationEngine : function() {
481      var animationEngine = this.options.animationEngine.toLowerCase().replace( /[ _\-]/g, '');
482      var isUsingJQueryAnimation;
483      // set applyStyleFnName
484      switch ( animationEngine ) {
485        case 'css' :
486        case 'none' :
487          isUsingJQueryAnimation = false;
488          break;
489        case 'jquery' :
490          isUsingJQueryAnimation = true;
491          break;
492        default : // best available
493          isUsingJQueryAnimation = !Modernizr.csstransitions;
494      }
495      this.isUsingJQueryAnimation = isUsingJQueryAnimation;
496      this._updateUsingTransforms();
497    },
498
499    _updateTransformsEnabled : function() {
500      this._updateUsingTransforms();
501    },
502
503    _updateUsingTransforms : function() {
504      var usingTransforms = this.usingTransforms = this.options.transformsEnabled &&
505        Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation;
506
507      // prevent scales when transforms are disabled
508      if ( !usingTransforms ) {
509        delete this.options.hiddenStyle.scale;
510        delete this.options.visibleStyle.scale;
511      }
512
513      this.getPositionStyles = usingTransforms ? this._translate : this._positionAbs;
514    },
515
516
517    // ====================== Filtering ======================
518
519    _filter : function( $atoms ) {
520      var filter = this.options.filter === '' ? '*' : this.options.filter;
521
522      if ( !filter ) {
523        return $atoms;
524      }
525
526      var hiddenClass    = this.options.hiddenClass,
527          hiddenSelector = '.' + hiddenClass,
528          $hiddenAtoms   = $atoms.filter( hiddenSelector ),
529          $atomsToShow   = $hiddenAtoms;
530
531      if ( filter !== '*' ) {
532        $atomsToShow = $hiddenAtoms.filter( filter );
533        var $atomsToHide = $atoms.not( hiddenSelector ).not( filter ).addClass( hiddenClass );
534        this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle });
535      }
536
537      this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle });
538      $atomsToShow.removeClass( hiddenClass );
539
540      return $atoms.filter( filter );
541    },
542
543    // ====================== Sorting ======================
544
545    updateSortData : function( $atoms, isIncrementingElemCount ) {
546      var instance = this,
547          getSortData = this.options.getSortData,
548          $this, sortData;
549      $atoms.each(function(){
550        $this = $(this);
551        sortData = {};
552        // get value for sort data based on fn( $elem ) passed in
553        for ( var key in getSortData ) {
554          if ( !isIncrementingElemCount && key === 'original-order' ) {
555            // keep original order original
556            sortData[ key ] = $.data( this, 'isotope-sort-data' )[ key ];
557          } else {
558            sortData[ key ] = getSortData[ key ]( $this, instance );
559          }
560        }
561        // apply sort data to element
562        $.data( this, 'isotope-sort-data', sortData );
563      });
564    },
565
566    // used on all the filtered atoms
567    _sort : function() {
568
569      var sortBy = this.options.sortBy,
570          getSorter = this._getSorter,
571          sortDir = this.options.sortAscending ? 1 : -1,
572          sortFn = function( alpha, beta ) {
573            var a = getSorter( alpha, sortBy ),
574                b = getSorter( beta, sortBy );
575            // fall back to original order if data matches
576            if ( a === b && sortBy !== 'original-order') {
577              a = getSorter( alpha, 'original-order' );
578              b = getSorter( beta, 'original-order' );
579            }
580            return ( ( a > b ) ? 1 : ( a < b ) ? -1 : 0 ) * sortDir;
581          };
582
583      this.$filteredAtoms.sort( sortFn );
584    },
585
586    _getSorter : function( elem, sortBy ) {
587      return $.data( elem, 'isotope-sort-data' )[ sortBy ];
588    },
589
590    // ====================== Layout Helpers ======================
591
592    _translate : function( x, y ) {
593      return { translate : [ x, y ] };
594    },
595
596    _positionAbs : function( x, y ) {
597      return { left: x, top: y };
598    },
599
600    _pushPosition : function( $elem, x, y ) {
601      x = Math.round( x + this.offset.left );
602      y = Math.round( y + this.offset.top );
603      var position = this.getPositionStyles( x, y );
604      this.styleQueue.push({ $el: $elem, style: position });
605      if ( this.options.itemPositionDataEnabled ) {
606        $elem.data('isotope-item-position', {x: x, y: y} );
607      }
608    },
609
610
611    // ====================== General Layout ======================
612
613    // used on collection of atoms (should be filtered, and sorted before )
614    // accepts atoms-to-be-laid-out to start with
615    layout : function( $elems, callback ) {
616
617      var layoutMode = this.options.layoutMode;
618
619      // layout logic
620      this[ '_' +  layoutMode + 'Layout' ]( $elems );
621
622      // set the size of the container
623      if ( this.options.resizesContainer ) {
624        var containerStyle = this[ '_' +  layoutMode + 'GetContainerSize' ]();
625        this.styleQueue.push({ $el: this.element, style: containerStyle });
626      }
627
628      this._processStyleQueue( $elems, callback );
629
630      this.isLaidOut = true;
631    },
632
633    _processStyleQueue : function( $elems, callback ) {
634      // are we animating the layout arrangement?
635      // use plugin-ish syntax for css or animate
636      var styleFn = !this.isLaidOut ? 'css' : (
637            this.isUsingJQueryAnimation ? 'animate' : 'css'
638          ),
639          animOpts = this.options.animationOptions,
640          onLayout = this.options.onLayout,
641          objStyleFn, processor,
642          triggerCallbackNow, callbackFn;
643
644      // default styleQueue processor, may be overwritten down below
645      processor = function( i, obj ) {
646        obj.$el[ styleFn ]( obj.style, animOpts );
647      };
648
649      if ( this._isInserting && this.isUsingJQueryAnimation ) {
650        // if using styleQueue to insert items
651        processor = function( i, obj ) {
652          // only animate if it not being inserted
653          objStyleFn = obj.$el.hasClass('no-transition') ? 'css' : styleFn;
654          obj.$el[ objStyleFn ]( obj.style, animOpts );
655        };
656
657      } else if ( callback || onLayout || animOpts.complete ) {
658        // has callback
659        var isCallbackTriggered = false,
660            // array of possible callbacks to trigger
661            callbacks = [ callback, onLayout, animOpts.complete ],
662            instance = this;
663        triggerCallbackNow = true;
664        // trigger callback only once
665        callbackFn = function() {
666          if ( isCallbackTriggered ) {
667            return;
668          }
669          var hollaback;
670          for (var i=0, len = callbacks.length; i < len; i++) {
671            hollaback = callbacks[i];
672            if ( typeof hollaback === 'function' ) {
673              hollaback.call( instance.element, $elems, instance );
674            }
675          }
676          isCallbackTriggered = true;
677        };
678
679        if ( this.isUsingJQueryAnimation && styleFn === 'animate' ) {
680          // add callback to animation options
681          animOpts.complete = callbackFn;
682          triggerCallbackNow = false;
683
684        } else if ( Modernizr.csstransitions ) {
685          // detect if first item has transition
686          var i = 0,
687              firstItem = this.styleQueue[0],
688              testElem = firstItem && firstItem.$el,
689              styleObj;
690          // get first non-empty jQ object
691          while ( !testElem || !testElem.length ) {
692            styleObj = this.styleQueue[ i++ ];
693            // HACK: sometimes styleQueue[i] is undefined
694            if ( !styleObj ) {
695              return;
696            }
697            testElem = styleObj.$el;
698          }
699          // get transition duration of the first element in that object
700          // yeah, this is inexact
701          var duration = parseFloat( getComputedStyle( testElem[0] )[ transitionDurProp ] );
702          if ( duration > 0 ) {
703            processor = function( i, obj ) {
704              obj.$el[ styleFn ]( obj.style, animOpts )
705                // trigger callback at transition end
706                .one( transitionEndEvent, callbackFn );
707            };
708            triggerCallbackNow = false;
709          }
710        }
711      }
712
713      // process styleQueue
714      $.each( this.styleQueue, processor );
715
716      if ( triggerCallbackNow ) {
717        callbackFn();
718      }
719
720      // clear out queue for next time
721      this.styleQueue = [];
722    },
723
724
725    resize : function() {
726      if ( this[ '_' + this.options.layoutMode + 'ResizeChanged' ]() ) {
727        this.reLayout();
728      }
729    },
730
731
732    reLayout : function( callback ) {
733
734      this[ '_' +  this.options.layoutMode + 'Reset' ]();
735      this.layout( this.$filteredAtoms, callback );
736
737    },
738
739    // ====================== Convenience methods ======================
740
741    // ====================== Adding items ======================
742
743    // adds a jQuery object of items to a isotope container
744    addItems : function( $content, callback ) {
745      var $newAtoms = this._getAtoms( $content );
746      // add new atoms to atoms pools
747      this.$allAtoms = this.$allAtoms.add( $newAtoms );
748
749      if ( callback ) {
750        callback( $newAtoms );
751      }
752    },
753
754    // convienence method for adding elements properly to any layout
755    // positions items, hides them, then animates them back in <--- very sezzy
756    insert : function( $content, callback ) {
757      // position items
758      this.element.append( $content );
759
760      var instance = this;
761      this.addItems( $content, function( $newAtoms ) {
762        var $newFilteredAtoms = instance._filter( $newAtoms );
763        instance._addHideAppended( $newFilteredAtoms );
764        instance._sort();
765        instance.reLayout();
766        instance._revealAppended( $newFilteredAtoms, callback );
767      });
768
769    },
770
771    // convienence method for working with Infinite Scroll
772    appended : function( $content, callback ) {
773      var instance = this;
774      this.addItems( $content, function( $newAtoms ) {
775        instance._addHideAppended( $newAtoms );
776        instance.layout( $newAtoms );
777        instance._revealAppended( $newAtoms, callback );
778      });
779    },
780
781    // adds new atoms, then hides them before positioning
782    _addHideAppended : function( $newAtoms ) {
783      this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms );
784      $newAtoms.addClass('no-transition');
785
786      this._isInserting = true;
787
788      // apply hidden styles
789      this.styleQueue.push({ $el: $newAtoms, style: this.options.hiddenStyle });
790    },
791
792    // sets visible style on new atoms
793    _revealAppended : function( $newAtoms, callback ) {
794      var instance = this;
795      // apply visible style after a sec
796      setTimeout( function() {
797        // enable animation
798        $newAtoms.removeClass('no-transition');
799        // reveal newly inserted filtered elements
800        instance.styleQueue.push({ $el: $newAtoms, style: instance.options.visibleStyle });
801        instance._isInserting = false;
802        instance._processStyleQueue( $newAtoms, callback );
803      }, 10 );
804    },
805
806    // gathers all atoms
807    reloadItems : function() {
808      this.$allAtoms = this._getAtoms( this.element.children() );
809    },
810
811    // removes elements from Isotope widget
812    remove: function( $content, callback ) {
813      // remove elements immediately from Isotope instance
814      this.$allAtoms = this.$allAtoms.not( $content );
815      this.$filteredAtoms = this.$filteredAtoms.not( $content );
816      // remove() as a callback, for after transition / animation
817      var instance = this;
818      var removeContent = function() {
819        $content.remove();
820        if ( callback ) {
821          callback.call( instance.element );
822        }
823      };
824
825      if ( $content.filter( ':not(.' + this.options.hiddenClass + ')' ).length ) {
826        // if any non-hidden content needs to be removed
827        this.styleQueue.push({ $el: $content, style: this.options.hiddenStyle });
828        this._sort();
829        this.reLayout( removeContent );
830      } else {
831        // remove it now
832        removeContent();
833      }
834
835    },
836
837    shuffle : function( callback ) {
838      this.updateSortData( this.$allAtoms );
839      this.options.sortBy = 'random';
840      this._sort();
841      this.reLayout( callback );
842    },
843
844    // destroys widget, returns elements and container back (close) to original style
845    destroy : function() {
846
847      var usingTransforms = this.usingTransforms;
848      var options = this.options;
849
850      this.$allAtoms
851        .removeClass( options.hiddenClass + ' ' + options.itemClass )
852        .each(function(){
853          var style = this.style;
854          style.position = '';
855          style.top = '';
856          style.left = '';
857          style.opacity = '';
858          if ( usingTransforms ) {
859            style[ transformProp ] = '';
860          }
861        });
862
863      // re-apply saved container styles
864      var elemStyle = this.element[0].style;
865      for ( var prop in this.originalStyle ) {
866        elemStyle[ prop ] = this.originalStyle[ prop ];
867      }
868
869      this.element
870        .unbind('.isotope')
871        .undelegate( '.' + options.hiddenClass, 'click' )
872        .removeClass( options.containerClass )
873        .removeData('isotope');
874
875      $window.unbind('.isotope');
876
877    },
878
879
880    // ====================== LAYOUTS ======================
881
882    // calculates number of rows or columns
883    // requires columnWidth or rowHeight to be set on namespaced object
884    // i.e. this.masonry.columnWidth = 200
885    _getSegments : function( isRows ) {
886      var namespace = this.options.layoutMode,
887          measure  = isRows ? 'rowHeight' : 'columnWidth',
888          size     = isRows ? 'height' : 'width',
889          segmentsName = isRows ? 'rows' : 'cols',
890          containerSize = this.element[ size ](),
891          segments,
892                    // i.e. options.masonry && options.masonry.columnWidth
893          segmentSize = this.options[ namespace ] && this.options[ namespace ][ measure ] ||
894                    // or use the size of the first item, i.e. outerWidth
895                    this.$filteredAtoms[ 'outer' + capitalize(size) ](true) ||
896                    // if there's no items, use size of container
897                    containerSize;
898
899      segments = Math.floor( containerSize / segmentSize );
900      segments = Math.max( segments, 1 );
901
902      // i.e. this.masonry.cols = ....
903      this[ namespace ][ segmentsName ] = segments;
904      // i.e. this.masonry.columnWidth = ...
905      this[ namespace ][ measure ] = segmentSize;
906
907    },
908
909    _checkIfSegmentsChanged : function( isRows ) {
910      var namespace = this.options.layoutMode,
911          segmentsName = isRows ? 'rows' : 'cols',
912          prevSegments = this[ namespace ][ segmentsName ];
913      // update cols/rows
914      this._getSegments( isRows );
915      // return if updated cols/rows is not equal to previous
916      return ( this[ namespace ][ segmentsName ] !== prevSegments );
917    },
918
919    // ====================== Masonry ======================
920
921    _masonryReset : function() {
922      // layout-specific props
923      this.masonry = {};
924      // FIXME shouldn't have to call this again
925      this._getSegments();
926      var i = this.masonry.cols;
927      this.masonry.colYs = [];
928      while (i--) {
929        this.masonry.colYs.push( 0 );
930      }
931    },
932
933    _masonryLayout : function( $elems ) {
934      var instance = this,
935          props = instance.masonry;
936      $elems.each(function(){
937        var $this  = $(this),
938            //how many columns does this brick span
939            colSpan = Math.ceil( $this.outerWidth(true) / props.columnWidth );
940        colSpan = Math.min( colSpan, props.cols );
941
942        if ( colSpan === 1 ) {
943          // if brick spans only one column, just like singleMode
944          instance._masonryPlaceBrick( $this, props.colYs );
945        } else {
946          // brick spans more than one column
947          // how many different places could this brick fit horizontally
948          var groupCount = props.cols + 1 - colSpan,
949              groupY = [],
950              groupColY,
951              i;
952
953          // for each group potential horizontal position
954          for ( i=0; i < groupCount; i++ ) {
955            // make an array of colY values for that one group
956            groupColY = props.colYs.slice( i, i+colSpan );
957            // and get the max value of the array
958            groupY[i] = Math.max.apply( Math, groupColY );
959          }
960
961          instance._masonryPlaceBrick( $this, groupY );
962        }
963      });
964    },
965
966    // worker method that places brick in the columnSet
967    //   with the the minY
968    _masonryPlaceBrick : function( $brick, setY ) {
969      // get the minimum Y value from the columns
970      var minimumY = Math.min.apply( Math, setY ),
971          shortCol = 0;
972
973      // Find index of short column, the first from the left
974      for (var i=0, len = setY.length; i < len; i++) {
975        if ( setY[i] === minimumY ) {
976          shortCol = i;
977          break;
978        }
979      }
980
981      // position the brick
982      var x = this.masonry.columnWidth * shortCol,
983          y = minimumY;
984      this._pushPosition( $brick, x, y );
985
986      // apply setHeight to necessary columns
987      var setHeight = minimumY + $brick.outerHeight(true),
988          setSpan = this.masonry.cols + 1 - len;
989      for ( i=0; i < setSpan; i++ ) {
990        this.masonry.colYs[ shortCol + i ] = setHeight;
991      }
992
993    },
994
995    _masonryGetContainerSize : function() {
996      var containerHeight = Math.max.apply( Math, this.masonry.colYs );
997      return { height: containerHeight };
998    },
999
1000    _masonryResizeChanged : function() {
1001      return this._checkIfSegmentsChanged();
1002    },
1003
1004    // ====================== fitRows ======================
1005
1006    _fitRowsReset : function() {
1007      this.fitRows = {
1008        x : 0,
1009        y : 0,
1010        height : 0
1011      };
1012    },
1013
1014    _fitRowsLayout : function( $elems ) {
1015      var instance = this,
1016          containerWidth = this.element.width(),
1017          props = this.fitRows;
1018
1019      $elems.each( function() {
1020        var $this = $(this),
1021            atomW = $this.outerWidth(true),
1022            atomH = $this.outerHeight(true);
1023
1024        if ( props.x !== 0 && atomW + props.x > containerWidth ) {
1025          // if this element cannot fit in the current row
1026          props.x = 0;
1027          props.y = props.height;
1028        }
1029
1030        // position the atom
1031        instance._pushPosition( $this, props.x, props.y );
1032
1033        props.height = Math.max( props.y + atomH, props.height );
1034        props.x += atomW;
1035
1036      });
1037    },
1038
1039    _fitRowsGetContainerSize : function () {
1040      return { height : this.fitRows.height };
1041    },
1042
1043    _fitRowsResizeChanged : function() {
1044      return true;
1045    },
1046
1047
1048    // ====================== cellsByRow ======================
1049
1050    _cellsByRowReset : function() {
1051      this.cellsByRow = {
1052        index : 0
1053      };
1054      // get this.cellsByRow.columnWidth
1055      this._getSegments();
1056      // get this.cellsByRow.rowHeight
1057      this._getSegments(true);
1058    },
1059
1060    _cellsByRowLayout : function( $elems ) {
1061      var instance = this,
1062          props = this.cellsByRow;
1063      $elems.each( function(){
1064        var $this = $(this),
1065            col = props.index % props.cols,
1066            row = Math.floor( props.index / props.cols ),
1067            x = ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2,
1068            y = ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2;
1069        instance._pushPosition( $this, x, y );
1070        props.index ++;
1071      });
1072    },
1073
1074    _cellsByRowGetContainerSize : function() {
1075      return { height : Math.ceil( this.$filteredAtoms.length / this.cellsByRow.cols ) * this.cellsByRow.rowHeight + this.offset.top };
1076    },
1077
1078    _cellsByRowResizeChanged : function() {
1079      return this._checkIfSegmentsChanged();
1080    },
1081
1082
1083    // ====================== straightDown ======================
1084
1085    _straightDownReset : function() {
1086      this.straightDown = {
1087        y : 0
1088      };
1089    },
1090
1091    _straightDownLayout : function( $elems ) {
1092      var instance = this;
1093      $elems.each( function( i ){
1094        var $this = $(this);
1095        instance._pushPosition( $this, 0, instance.straightDown.y );
1096        instance.straightDown.y += $this.outerHeight(true);
1097      });
1098    },
1099
1100    _straightDownGetContainerSize : function() {
1101      return { height : this.straightDown.y };
1102    },
1103
1104    _straightDownResizeChanged : function() {
1105      return true;
1106    },
1107
1108
1109    // ====================== masonryHorizontal ======================
1110
1111    _masonryHorizontalReset : function() {
1112      // layout-specific props
1113      this.masonryHorizontal = {};
1114      // FIXME shouldn't have to call this again
1115      this._getSegments( true );
1116      var i = this.masonryHorizontal.rows;
1117      this.masonryHorizontal.rowXs = [];
1118      while (i--) {
1119        this.masonryHorizontal.rowXs.push( 0 );
1120      }
1121    },
1122
1123    _masonryHorizontalLayout : function( $elems ) {
1124      var instance = this,
1125          props = instance.masonryHorizontal;
1126      $elems.each(function(){
1127        var $this  = $(this),
1128            //how many rows does this brick span
1129            rowSpan = Math.ceil( $this.outerHeight(true) / props.rowHeight );
1130        rowSpan = Math.min( rowSpan, props.rows );
1131
1132        if ( rowSpan === 1 ) {
1133          // if brick spans only one column, just like singleMode
1134          instance._masonryHorizontalPlaceBrick( $this, props.rowXs );
1135        } else {
1136          // brick spans more than one row
1137          // how many different places could this brick fit horizontally
1138          var groupCount = props.rows + 1 - rowSpan,
1139              groupX = [],
1140              groupRowX, i;
1141
1142          // for each group potential horizontal position
1143          for ( i=0; i < groupCount; i++ ) {
1144            // make an array of colY values for that one group
1145            groupRowX = props.rowXs.slice( i, i+rowSpan );
1146            // and get the max value of the array
1147            groupX[i] = Math.max.apply( Math, groupRowX );
1148          }
1149
1150          instance._masonryHorizontalPlaceBrick( $this, groupX );
1151        }
1152      });
1153    },
1154
1155    _masonryHorizontalPlaceBrick : function( $brick, setX ) {
1156      // get the minimum Y value from the columns
1157      var minimumX  = Math.min.apply( Math, setX ),
1158          smallRow  = 0;
1159      // Find index of smallest row, the first from the top
1160      for (var i=0, len = setX.length; i < len; i++) {
1161        if ( setX[i] === minimumX ) {
1162          smallRow = i;
1163          break;
1164        }
1165      }
1166
1167      // position the brick
1168      var x = minimumX,
1169          y = this.masonryHorizontal.rowHeight * smallRow;
1170      this._pushPosition( $brick, x, y );
1171
1172      // apply setHeight to necessary columns
1173      var setWidth = minimumX + $brick.outerWidth(true),
1174          setSpan = this.masonryHorizontal.rows + 1 - len;
1175      for ( i=0; i < setSpan; i++ ) {
1176        this.masonryHorizontal.rowXs[ smallRow + i ] = setWidth;
1177      }
1178    },
1179
1180    _masonryHorizontalGetContainerSize : function() {
1181      var containerWidth = Math.max.apply( Math, this.masonryHorizontal.rowXs );
1182      return { width: containerWidth };
1183    },
1184
1185    _masonryHorizontalResizeChanged : function() {
1186      return this._checkIfSegmentsChanged(true);
1187    },
1188
1189
1190    // ====================== fitColumns ======================
1191
1192    _fitColumnsReset : function() {
1193      this.fitColumns = {
1194        x : 0,
1195        y : 0,
1196        width : 0
1197      };
1198    },
1199
1200    _fitColumnsLayout : function( $elems ) {
1201      var instance = this,
1202          containerHeight = this.element.height(),
1203          props = this.fitColumns;
1204      $elems.each( function() {
1205        var $this = $(this),
1206            atomW = $this.outerWidth(true),
1207            atomH = $this.outerHeight(true);
1208
1209        if ( props.y !== 0 && atomH + props.y > containerHeight ) {
1210          // if this element cannot fit in the current column
1211          props.x = props.width;
1212          props.y = 0;
1213        }
1214
1215        // position the atom
1216        instance._pushPosition( $this, props.x, props.y );
1217
1218        props.width = Math.max( props.x + atomW, props.width );
1219        props.y += atomH;
1220
1221      });
1222    },
1223
1224    _fitColumnsGetContainerSize : function () {
1225      return { width : this.fitColumns.width };
1226    },
1227
1228    _fitColumnsResizeChanged : function() {
1229      return true;
1230    },
1231
1232
1233
1234    // ====================== cellsByColumn ======================
1235
1236    _cellsByColumnReset : function() {
1237      this.cellsByColumn = {
1238        index : 0
1239      };
1240      // get this.cellsByColumn.columnWidth
1241      this._getSegments();
1242      // get this.cellsByColumn.rowHeight
1243      this._getSegments(true);
1244    },
1245
1246    _cellsByColumnLayout : function( $elems ) {
1247      var instance = this,
1248          props = this.cellsByColumn;
1249      $elems.each( function(){
1250        var $this = $(this),
1251            col = Math.floor( props.index / props.rows ),
1252            row = props.index % props.rows,
1253            x = ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2,
1254            y = ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2;
1255        instance._pushPosition( $this, x, y );
1256        props.index ++;
1257      });
1258    },
1259
1260    _cellsByColumnGetContainerSize : function() {
1261      return { width : Math.ceil( this.$filteredAtoms.length / this.cellsByColumn.rows ) * this.cellsByColumn.columnWidth };
1262    },
1263
1264    _cellsByColumnResizeChanged : function() {
1265      return this._checkIfSegmentsChanged(true);
1266    },
1267
1268    // ====================== straightAcross ======================
1269
1270    _straightAcrossReset : function() {
1271      this.straightAcross = {
1272        x : 0
1273      };
1274    },
1275
1276    _straightAcrossLayout : function( $elems ) {
1277      var instance = this;
1278      $elems.each( function( i ){
1279        var $this = $(this);
1280        instance._pushPosition( $this, instance.straightAcross.x, 0 );
1281        instance.straightAcross.x += $this.outerWidth(true);
1282      });
1283    },
1284
1285    _straightAcrossGetContainerSize : function() {
1286      return { width : this.straightAcross.x };
1287    },
1288
1289    _straightAcrossResizeChanged : function() {
1290      return true;
1291    }
1292
1293  };
1294
1295
1296  // ======================= imagesLoaded Plugin ===============================
1297  /*!
1298   * jQuery imagesLoaded plugin v1.1.0
1299   * http://github.com/desandro/imagesloaded
1300   *
1301   * MIT License. by Paul Irish et al.
1302   */
1303
1304
1305  // $('#my-container').imagesLoaded(myFunction)
1306  // or
1307  // $('img').imagesLoaded(myFunction)
1308
1309  // execute a callback when all images have loaded.
1310  // needed because .load() doesn't work on cached images
1311
1312  // callback function gets image collection as argument
1313  //  `this` is the container
1314
1315  $.fn.imagesLoaded = function( callback ) {
1316    var $this = this,
1317        $images = $this.find('img').add( $this.filter('img') ),
1318        len = $images.length,
1319        blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==',
1320        loaded = [];
1321
1322    function triggerCallback() {
1323      callback.call( $this, $images );
1324    }
1325
1326    function imgLoaded( event ) {
1327      var img = event.target;
1328      if ( img.src !== blank && $.inArray( img, loaded ) === -1 ){
1329        loaded.push( img );
1330        if ( --len <= 0 ){
1331          setTimeout( triggerCallback );
1332          $images.unbind( '.imagesLoaded', imgLoaded );
1333        }
1334      }
1335    }
1336
1337    // if no images, trigger immediately
1338    if ( !len ) {
1339      triggerCallback();
1340    }
1341
1342    $images.bind( 'load.imagesLoaded error.imagesLoaded',  imgLoaded ).each( function() {
1343      // cached images don't fire load sometimes, so we reset src.
1344      var src = this.src;
1345      // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
1346      // data uri bypasses webkit log warning (thx doug jones)
1347      this.src = blank;
1348      this.src = src;
1349    });
1350
1351    return $this;
1352  };
1353
1354
1355  // helper function for logging errors
1356  // $.error breaks jQuery chaining
1357  var logError = function( message ) {
1358    if ( window.console ) {
1359      window.console.error( message );
1360    }
1361  };
1362
1363  // =======================  Plugin bridge  ===============================
1364  // leverages data method to either create or return $.Isotope constructor
1365  // A bit from jQuery UI
1366  //   https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
1367  // A bit from jcarousel
1368  //   https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js
1369
1370  $.fn.isotope = function( options, callback ) {
1371    if ( typeof options === 'string' ) {
1372      // call method
1373      var args = Array.prototype.slice.call( arguments, 1 );
1374
1375      this.each(function(){
1376        var instance = $.data( this, 'isotope' );
1377        if ( !instance ) {
1378          logError( "cannot call methods on isotope prior to initialization; " +
1379              "attempted to call method '" + options + "'" );
1380          return;
1381        }
1382        if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
1383          logError( "no such method '" + options + "' for isotope instance" );
1384          return;
1385        }
1386        // apply method
1387        instance[ options ].apply( instance, args );
1388      });
1389    } else {
1390      this.each(function() {
1391        var instance = $.data( this, 'isotope' );
1392        if ( instance ) {
1393          // apply options & init
1394          instance.option( options );
1395          instance._init( callback );
1396        } else {
1397          // initialize new instance
1398          $.data( this, 'isotope', new $.Isotope( options, this, callback ) );
1399        }
1400      });
1401    }
1402    // return jQuery object
1403    // so plugin methods do not have to
1404    return this;
1405  };
1406
1407})( window, jQuery );

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.