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.