1/** 2 * Isotope v1.5.19 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', 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 resizeTimeout; 284 285 $event.special.smartresize = { 286 setup: function() { 287 $(this).bind( "resize", $event.special.smartresize.handler ); 288 }, 289 teardown: function() { 290 $(this).unbind( "resize", $event.special.smartresize.handler ); 291 }, 292 handler: function( event, execAsap ) { 293 // Save the context 294 var context = this, 295 args = arguments; 296 297 // set correct event type 298 event.type = "smartresize"; 299 300 if ( resizeTimeout ) { clearTimeout( resizeTimeout ); } 301 resizeTimeout = setTimeout(function() { 302 jQuery.event.handle.apply( context, args ); 303 }, execAsap === "execAsap"? 0 : 100 ); 304 } 305 }; 306 307 $.fn.smartresize = function( fn ) { 308 return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] ); 309 }; 310 311 312 313// ========================= Isotope =============================== 314 315
316 // our "Widget" object constructor 317 $.Isotope = function( options, element, callback ){ 318 this.element = $( element ); 319 320 this._create( options ); 321 this._init( callback ); 322 }; 323 324 // styles of container element we want to keep track of 325 var isoContainerStyles = [ 'width', 'height' ]; 326 327 var $window = $(window); 328 329 $.Isotope.settings = { 330 resizable: true, 331 layoutMode : 'masonry', 332 containerClass : 'isotope', 333 itemClass : 'isotope-item', 334 hiddenClass : 'isotope-hidden', 335 hiddenStyle: { opacity: 0, scale: 0.001 }, 336 visibleStyle: { opacity: 1, scale: 1 }, 337 containerStyle: { 338 position: 'relative', 339 overflow: 'hidden' 340 }, 341 animationEngine: 'best-available', 342 animationOptions: { 343 queue: false, 344 duration: 800 345 }, 346 sortBy : 'original-order', 347 sortAscending : true, 348 resizesContainer : true, 349 transformsEnabled: !$.browser.opera, // disable transforms in Opera 350 itemPositionDataEnabled: false 351 }; 352 353 $.Isotope.prototype = { 354 355 // sets up widget 356 _create : function( options ) { 357 358 this.options = $.extend( {}, $.Isotope.settings, options ); 359 360 this.styleQueue = []; 361 this.elemCount = 0; 362 363 // get original styles in case we re-apply them in .destroy() 364 var elemStyle = this.element[0].style; 365 this.originalStyle = {}; 366 // keep track of container styles 367 var containerStyles = isoContainerStyles.slice(0); 368 for ( var prop in this.options.containerStyle ) { 369 containerStyles.push( prop ); 370 } 371 for ( var i=0, len = containerStyles.length; i < len; i++ ) { 372 prop = containerStyles[i]; 373 this.originalStyle[ prop ] = elemStyle[ prop ] || ''; 374 } 375 // apply container style from options 376 this.element.css( this.options.containerStyle ); 377 378 this._updateAnimationEngine(); 379 this._updateUsingTransforms(); 380 381 // sorting 382 var originalOrderSorter = { 383 'original-order' : function( $elem, instance ) { 384 instance.elemCount ++; 385 return instance.elemCount; 386 }, 387 random : function() { 388 return Math.random(); 389 } 390 }; 391 392 this.options.getSortData = $.extend( this.options.getSortData, originalOrderSorter ); 393 394 // need to get atoms 395 this.reloadItems(); 396 397 // get top left position of where the bricks should be 398 this.offset = { 399 left: parseInt( ( this.element.css('padding-left') || 0 ), 10 ), 400 top: parseInt( ( this.element.css('padding-top') || 0 ), 10 ) 401 }; 402 403 // add isotope class first time around 404 var instance = this; 405 setTimeout( function() { 406 instance.element.addClass( instance.options.containerClass ); 407 }, 0 ); 408 409 // bind resize method 410 if ( this.options.resizable ) { 411 $window.bind( 'smartresize.isotope', function() { 412 instance.resize(); 413 }); 414 } 415 416 // dismiss all click events from hidden events 417 this.element.delegate( '.' + this.options.hiddenClass, 'click', function(){ 418 return false; 419 }); 420 421 }, 422 423 _getAtoms : function( $elems ) { 424 var selector = this.options.itemSelector, 425 // filter & find 426 $atoms = selector ? $elems.filter( selector ).add( $elems.find( selector ) ) : $elems, 427 // base style for atoms 428 atomStyle = { position: 'absolute' }; 429 430 if ( this.usingTransforms ) { 431 atomStyle.left = 0; 432 atomStyle.top = 0; 433 } 434 435 $atoms.css( atomStyle ).addClass( this.options.itemClass ); 436 437 this.updateSortData( $atoms, true ); 438 439 return $atoms; 440 }, 441 442 // _init fires when your instance is first created 443 // (from the constructor above), and when you 444 // attempt to initialize the widget again (by the bridge) 445 // after it has already been initialized. 446 _init : function( callback ) { 447 448 this.$filteredAtoms = this._filter( this.$allAtoms ); 449 this._sort(); 450 this.reLayout( callback ); 451 452 }, 453 454 option : function( opts ){ 455 // change options AFTER initialization: 456 // signature: $('#foo').bar({ cool:false }); 457 if ( $.isPlainObject( opts ) ){ 458 this.options = $.extend( true, this.options, opts ); 459 460 // trigger _updateOptionName if it exists 461 var updateOptionFn; 462 for ( var optionName in opts ) { 463 updateOptionFn = '_update' + capitalize( optionName ); 464 if ( this[ updateOptionFn ] ) { 465 this[ updateOptionFn ](); 466 } 467 } 468 } 469 }, 470 471 // ====================== updaters ====================== // 472 // kind of like setters 473 474 _updateAnimationEngine : function() { 475 var animationEngine = this.options.animationEngine.toLowerCase().replace( /[ _\-]/g, ''); 476 var isUsingJQueryAnimation; 477 // set applyStyleFnName 478 switch ( animationEngine ) { 479 case 'css' : 480 case 'none' : 481 isUsingJQueryAnimation = false; 482 break; 483 case 'jquery' : 484 isUsingJQueryAnimation = true; 485 break; 486 default : // best available 487 isUsingJQueryAnimation = !Modernizr.csstransitions; 488 } 489 this.isUsingJQueryAnimation = isUsingJQueryAnimation; 490 this._updateUsingTransforms(); 491 }, 492 493 _updateTransformsEnabled : function() { 494 this._updateUsingTransforms(); 495 }, 496 497 _updateUsingTransforms : function() { 498 var usingTransforms = this.usingTransforms = this.options.transformsEnabled && 499 Modernizr.csstransforms && Modernizr.csstransitions && !this.isUsingJQueryAnimation; 500 501 // prevent scales when transforms are disabled 502 if ( !usingTransforms ) { 503 delete this.options.hiddenStyle.scale; 504 delete this.options.visibleStyle.scale; 505 } 506 507 this.getPositionStyles = usingTransforms ? this._translate : this._positionAbs; 508 }, 509 510 511 // ====================== Filtering ====================== 512 513 _filter : function( $atoms ) { 514 var filter = this.options.filter === '' ? '*' : this.options.filter; 515 516 if ( !filter ) { 517 return $atoms; 518 } 519 520 var hiddenClass = this.options.hiddenClass, 521 hiddenSelector = '.' + hiddenClass, 522 $hiddenAtoms = $atoms.filter( hiddenSelector ), 523 $atomsToShow = $hiddenAtoms; 524 525 if ( filter !== '*' ) { 526 $atomsToShow = $hiddenAtoms.filter( filter );
527 var $atomsToHide = $atoms.not( hiddenSelector ).not( filter ).addClass( hiddenClass ); 528 this.styleQueue.push({ $el: $atomsToHide, style: this.options.hiddenStyle }); 529 } 530 531 this.styleQueue.push({ $el: $atomsToShow, style: this.options.visibleStyle }); 532 $atomsToShow.removeClass( hiddenClass ); 533 534 return $atoms.filter( filter ); 535 }, 536 537 // ====================== Sorting ====================== 538 539 updateSortData : function( $atoms, isIncrementingElemCount ) { 540 var instance = this, 541 getSortData = this.options.getSortData, 542 $this, sortData; 543 $atoms.each(function(){ 544 $this = $(this); 545 sortData = {}; 546 // get value for sort data based on fn( $elem ) passed in 547 for ( var key in getSortData ) { 548 if ( !isIncrementingElemCount && key === 'original-order' ) { 549 // keep original order original 550 sortData[ key ] = $.data( this, 'isotope-sort-data' )[ key ]; 551 } else { 552 sortData[ key ] = getSortData[ key ]( $this, instance ); 553 } 554 } 555 // apply sort data to element 556 $.data( this, 'isotope-sort-data', sortData ); 557 }); 558 }, 559 560 // used on all the filtered atoms 561 _sort : function() { 562 563 var sortBy = this.options.sortBy, 564 getSorter = this._getSorter, 565 sortDir = this.options.sortAscending ? 1 : -1, 566 sortFn = function( alpha, beta ) { 567 var a = getSorter( alpha, sortBy ), 568 b = getSorter( beta, sortBy ); 569 // fall back to original order if data matches 570 if ( a === b && sortBy !== 'original-order') { 571 a = getSorter( alpha, 'original-order' ); 572 b = getSorter( beta, 'original-order' ); 573 } 574 return ( ( a > b ) ? 1 : ( a < b ) ? -1 : 0 ) * sortDir; 575 }; 576 577 this.$filteredAtoms.sort( sortFn ); 578 }, 579 580 _getSorter : function( elem, sortBy ) { 581 return $.data( elem, 'isotope-sort-data' )[ sortBy ]; 582 }, 583 584 // ====================== Layout Helpers ====================== 585 586 _translate : function( x, y ) { 587 return { translate : [ x, y ] }; 588 }, 589 590 _positionAbs : function( x, y ) { 591 return { left: x, top: y }; 592 }, 593 594 _pushPosition : function( $elem, x, y ) { 595 x = Math.round( x + this.offset.left ); 596 y = Math.round( y + this.offset.top ); 597 var position = this.getPositionStyles( x, y ); 598 this.styleQueue.push({ $el: $elem, style: position }); 599 if ( this.options.itemPositionDataEnabled ) { 600 $elem.data('isotope-item-position', {x: x, y: y} ); 601 } 602 }, 603 604 605 // ====================== General Layout ====================== 606 607 // used on collection of atoms (should be filtered, and sorted before ) 608 // accepts atoms-to-be-laid-out to start with 609 layout : function( $elems, callback ) { 610 611 var layoutMode = this.options.layoutMode; 612 613 // layout logic 614 this[ '_' + layoutMode + 'Layout' ]( $elems ); 615 616 // set the size of the container 617 if ( this.options.resizesContainer ) { 618 var containerStyle = this[ '_' + layoutMode + 'GetContainerSize' ](); 619 this.styleQueue.push({ $el: this.element, style: containerStyle }); 620 } 621 622 this._processStyleQueue( $elems, callback ); 623 624 this.isLaidOut = true; 625 }, 626 627 _processStyleQueue : function( $elems, callback ) { 628 // are we animating the layout arrangement? 629 // use plugin-ish syntax for css or animate 630 var styleFn = !this.isLaidOut ? 'css' : ( 631 this.isUsingJQueryAnimation ? 'animate' : 'css' 632 ), 633 animOpts = this.options.animationOptions, 634 onLayout = this.options.onLayout, 635 objStyleFn, processor, 636 triggerCallbackNow, callbackFn; 637 638 // default styleQueue processor, may be overwritten down below 639 processor = function( i, obj ) { 640 obj.$el[ styleFn ]( obj.style, animOpts ); 641 }; 642 643 if ( this._isInserting && this.isUsingJQueryAnimation ) { 644 // if using styleQueue to insert items 645 processor = function( i, obj ) { 646 // only animate if it not being inserted 647 objStyleFn = obj.$el.hasClass('no-transition') ? 'css' : styleFn; 648 obj.$el[ objStyleFn ]( obj.style, animOpts ); 649 }; 650 651 } else if ( callback || onLayout || animOpts.complete ) { 652 // has callback 653 var isCallbackTriggered = false, 654 // array of possible callbacks to trigger 655 callbacks = [ callback, onLayout, animOpts.complete ], 656 instance = this; 657 triggerCallbackNow = true; 658 // trigger callback only once 659 callbackFn = function() { 660 if ( isCallbackTriggered ) { 661 return; 662 } 663 var hollaback; 664 for (var i=0, len = callbacks.length; i < len; i++) { 665 hollaback = callbacks[i]; 666 if ( typeof hollaback === 'function' ) { 667 hollaback.call( instance.element, $elems, instance ); 668 } 669 } 670 isCallbackTriggered = true; 671 }; 672 673 if ( this.isUsingJQueryAnimation && styleFn === 'animate' ) { 674 // add callback to animation options 675 animOpts.complete = callbackFn; 676 triggerCallbackNow = false; 677 678 } else if ( Modernizr.csstransitions ) { 679 // detect if first item has transition 680 var i = 0, 681 firstItem = this.styleQueue[0], 682 testElem = firstItem && firstItem.$el, 683 styleObj; 684 // get first non-empty jQ object 685 while ( !testElem || !testElem.length ) { 686 styleObj = this.styleQueue[ i++ ]; 687 // HACK: sometimes styleQueue[i] is undefined 688 if ( !styleObj ) { 689 return; 690 } 691 testElem = styleObj.$el; 692 } 693 // get transition duration of the first element in that object 694 // yeah, this is inexact 695 var duration = parseFloat( getComputedStyle( testElem[0] )[ transitionDurProp ] ); 696 if ( duration > 0 ) { 697 processor = function( i, obj ) { 698 obj.$el[ styleFn ]( obj.style, animOpts ) 699 // trigger callback at transition end 700 .one( transitionEndEvent, callbackFn ); 701 }; 702 triggerCallbackNow = false; 703 } 704 } 705 } 706 707 // process styleQueue 708 $.each( this.styleQueue, processor ); 709 710 if ( triggerCallbackNow ) { 711 callbackFn(); 712 } 713 714 // clear out queue for next time 715 this.styleQueue = []; 716 }, 717 718 719 resize : function() { 720 if ( this[ '_' + this.options.layoutMode + 'ResizeChanged' ]() ) { 721 this.reLayout(); 722 } 723 }, 724 725 726 reLayout : function( callback ) { 727 728 this[ '_' + this.options.layoutMode + 'Reset' ](); 729 this.layout( this.$filteredAtoms, callback ); 730 731 }, 732 733 // ====================== Convenience methods ====================== 734 735 // ====================== Adding items ====================== 736 737 // adds a jQuery object of items to a isotope container 738 addItems : function( $content, callback ) { 739 var $newAtoms = this._getAtoms( $content ); 740 // add new atoms to atoms pools 741 this.$allAtoms = this.$allAtoms.add( $newAtoms ); 742 743 if ( callback ) { 744 callback( $newAtoms ); 745 } 746 }, 747 748 // convienence method for adding elements properly to any layout 749 // positions items, hides them, then animates them back in <--- very sezzy 750 insert : function( $content, callback ) { 751 // position items 752 this.element.append( $content ); 753 754 var instance = this; 755 this.addItems( $content, function( $newAtoms ) { 756 var $newFilteredAtoms = instance._filter( $newAtoms ); 757 instance._addHideAppended( $newFilteredAtoms ); 758 instance._sort(); 759 instance.reLayout(); 760 instance._revealAppended( $newFilteredAtoms, callback ); 761 }); 762 763 }, 764 765 // convienence method for working with Infinite Scroll 766 appended : function( $content, callback ) { 767 var instance = this; 768 this.addItems( $content, function( $newAtoms ) { 769 instance._addHideAppended( $newAtoms ); 770 instance.layout( $newAtoms ); 771 instance._revealAppended( $newAtoms, callback ); 772 }); 773 }, 774 775 // adds new atoms, then hides them before positioning 776 _addHideAppended : function( $newAtoms ) { 777 this.$filteredAtoms = this.$filteredAtoms.add( $newAtoms ); 778 $newAtoms.addClass('no-transition'); 779 780 this._isInserting = true; 781 782 // apply hidden styles 783 this.styleQueue.push({ $el: $newAtoms, style: this.options.hiddenStyle }); 784 }, 785 786 // sets visible style on new atoms 787 _revealAppended : function( $newAtoms, callback ) { 788 var instance = this; 789 // apply visible style after a sec 790 setTimeout( function() { 791 // enable animation 792 $newAtoms.removeClass('no-transition'); 793 // reveal newly inserted filtered elements 794 instance.styleQueue.push({ $el: $newAtoms, style: instance.options.visibleStyle }); 795 instance._isInserting = false; 796 instance._processStyleQueue( $newAtoms, callback ); 797 }, 10 ); 798 }, 799 800 // gathers all atoms 801 reloadItems : function() { 802 this.$allAtoms = this._getAtoms( this.element.children() ); 803 }, 804 805 // removes elements from Isotope widget 806 remove: function( $content, callback ) { 807 // remove elements from Isotope instance in callback 808 var instance = this; 809 // remove() as a callback, for after transition / animation 810 var removeContent = function() { 811 instance.$allAtoms = instance.$allAtoms.not( $content ); 812 $content.remove(); 813 if ( callback ) { 814 callback.call( instance.element ); 815 } 816 }; 817 818 if ( $content.filter( ':not(.' + this.options.hiddenClass + ')' ).length ) { 819 // if any non-hidden content needs to be removed 820 this.styleQueue.push({ $el: $content, style: this.options.hiddenStyle }); 821 this.$filteredAtoms = this.$filteredAtoms.not( $content ); 822 this._sort(); 823 this.reLayout( removeContent ); 824 } else { 825 // remove it now 826 removeContent(); 827 } 828 829 }, 830 831 shuffle : function( callback ) { 832 this.updateSortData( this.$allAtoms ); 833 this.options.sortBy = 'random'; 834 this._sort(); 835 this.reLayout( callback ); 836 }, 837 838 // destroys widget, returns elements and container back (close) to original style 839 destroy : function() { 840 841 var usingTransforms = this.usingTransforms;
842 var options = this.options; 843 844 this.$allAtoms 845 .removeClass( options.hiddenClass + ' ' + options.itemClass ) 846 .each(function(){ 847 var style = this.style; 848 style.position = ''; 849 style.top = ''; 850 style.left = ''; 851 style.opacity = ''; 852 if ( usingTransforms ) { 853 style[ transformProp ] = ''; 854 } 855 }); 856 857 // re-apply saved container styles 858 var elemStyle = this.element[0].style; 859 for ( var prop in this.originalStyle ) { 860 elemStyle[ prop ] = this.originalStyle[ prop ]; 861 } 862 863 this.element 864 .unbind('.isotope') 865 .undelegate( '.' + options.hiddenClass, 'click' ) 866 .removeClass( options.containerClass ) 867 .removeData('isotope'); 868 869 $window.unbind('.isotope'); 870 871 }, 872 873 874 // ====================== LAYOUTS ====================== 875 876 // calculates number of rows or columns 877 // requires columnWidth or rowHeight to be set on namespaced object 878 // i.e. this.masonry.columnWidth = 200 879 _getSegments : function( isRows ) { 880 var namespace = this.options.layoutMode, 881 measure = isRows ? 'rowHeight' : 'columnWidth', 882 size = isRows ? 'height' : 'width', 883 segmentsName = isRows ? 'rows' : 'cols', 884 containerSize = this.element[ size ](), 885 segments, 886 // i.e. options.masonry && options.masonry.columnWidth 887 segmentSize = this.options[ namespace ] && this.options[ namespace ][ measure ] || 888 // or use the size of the first item, i.e. outerWidth 889 this.$filteredAtoms[ 'outer' + capitalize(size) ](true) || 890 // if there's no items, use size of container 891 containerSize; 892 893 segments = Math.floor( containerSize / segmentSize ); 894 segments = Math.max( segments, 1 ); 895 896 // i.e. this.masonry.cols = .... 897 this[ namespace ][ segmentsName ] = segments; 898 // i.e. this.masonry.columnWidth = ... 899 this[ namespace ][ measure ] = segmentSize; 900 901 }, 902 903 _checkIfSegmentsChanged : function( isRows ) { 904 var namespace = this.options.layoutMode, 905 segmentsName = isRows ? 'rows' : 'cols', 906 prevSegments = this[ namespace ][ segmentsName ]; 907 // update cols/rows 908 this._getSegments( isRows ); 909 // return if updated cols/rows is not equal to previous 910 return ( this[ namespace ][ segmentsName ] !== prevSegments ); 911 }, 912 913 // ====================== Masonry ====================== 914 915 _masonryReset : function() { 916 // layout-specific props 917 this.masonry = {}; 918 // FIXME shouldn't have to call this again 919 this._getSegments(); 920 var i = this.masonry.cols; 921 this.masonry.colYs = []; 922 while (i--) { 923 this.masonry.colYs.push( 0 ); 924 } 925 }, 926 927 _masonryLayout : function( $elems ) { 928 var instance = this, 929 props = instance.masonry; 930 $elems.each(function(){ 931 var $this = $(this), 932 //how many columns does this brick span 933 colSpan = Math.ceil( $this.outerWidth(true) / props.columnWidth ); 934 colSpan = Math.min( colSpan, props.cols ); 935 936 if ( colSpan === 1 ) { 937 // if brick spans only one column, just like singleMode 938 instance._masonryPlaceBrick( $this, props.colYs ); 939 } else { 940 // brick spans more than one column 941 // how many different places could this brick fit horizontally 942 var groupCount = props.cols + 1 - colSpan, 943 groupY = [], 944 groupColY, 945 i; 946 947 // for each group potential horizontal position 948 for ( i=0; i < groupCount; i++ ) { 949 // make an array of colY values for that one group 950 groupColY = props.colYs.slice( i, i+colSpan ); 951 // and get the max value of the array 952 groupY[i] = Math.max.apply( Math, groupColY ); 953 } 954 955 instance._masonryPlaceBrick( $this, groupY ); 956 } 957 }); 958 }, 959 960 // worker method that places brick in the columnSet 961 // with the the minY 962 _masonryPlaceBrick : function( $brick, setY ) { 963 // get the minimum Y value from the columns 964 var minimumY = Math.min.apply( Math, setY ), 965 shortCol = 0; 966 967 // Find index of short column, the first from the left 968 for (var i=0, len = setY.length; i < len; i++) { 969 if ( setY[i] === minimumY ) { 970 shortCol = i; 971 break; 972 } 973 } 974 975 // position the brick 976 var x = this.masonry.columnWidth * shortCol,
977 y = minimumY; 978 this._pushPosition( $brick, x, y ); 979 980 // apply setHeight to necessary columns 981 var setHeight = minimumY + $brick.outerHeight(true), 982 setSpan = this.masonry.cols + 1 - len; 983 for ( i=0; i < setSpan; i++ ) { 984 this.masonry.colYs[ shortCol + i ] = setHeight; 985 } 986 987 }, 988 989 _masonryGetContainerSize : function() { 990 var containerHeight = Math.max.apply( Math, this.masonry.colYs ); 991 return { height: containerHeight }; 992 }, 993 994 _masonryResizeChanged : function() { 995 return this._checkIfSegmentsChanged(); 996 }, 997 998 // ====================== fitRows ====================== 999 1000 _fitRowsReset : function() { 1001 this.fitRows = { 1002 x : 0, 1003 y : 0, 1004 height : 0 1005 }; 1006 }, 1007 1008 _fitRowsLayout : function( $elems ) { 1009 var instance = this, 1010 containerWidth = this.element.width(), 1011 props = this.fitRows; 1012 1013 $elems.each( function() { 1014 var $this = $(this), 1015 atomW = $this.outerWidth(true), 1016 atomH = $this.outerHeight(true); 1017 1018 if ( props.x !== 0 && atomW + props.x > containerWidth ) { 1019 // if this element cannot fit in the current row 1020 props.x = 0; 1021 props.y = props.height; 1022 } 1023 1024 // position the atom 1025 instance._pushPosition( $this, props.x, props.y ); 1026 1027 props.height = Math.max( props.y + atomH, props.height ); 1028 props.x += atomW; 1029 1030 }); 1031 }, 1032 1033 _fitRowsGetContainerSize : function () { 1034 return { height : this.fitRows.height }; 1035 }, 1036 1037 _fitRowsResizeChanged : function() { 1038 return true; 1039 }, 1040 1041 1042 // ====================== cellsByRow ====================== 1043 1044 _cellsByRowReset : function() { 1045 this.cellsByRow = { 1046 index : 0 1047 }; 1048 // get this.cellsByRow.columnWidth 1049 this._getSegments(); 1050 // get this.cellsByRow.rowHeight 1051 this._getSegments(true); 1052 }, 1053 1054 _cellsByRowLayout : function( $elems ) { 1055 var instance = this, 1056 props = this.cellsByRow; 1057 $elems.each( function(){ 1058 var $this = $(this), 1059 col = props.index % props.cols, 1060 row = Math.floor( props.index / props.cols ), 1061 x = ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2, 1062 y = ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2; 1063 instance._pushPosition( $this, x, y ); 1064 props.index ++; 1065 }); 1066 }, 1067 1068 _cellsByRowGetContainerSize : function() { 1069 return { height : Math.ceil( this.$filteredAtoms.length / this.cellsByRow.cols ) * this.cellsByRow.rowHeight + this.offset.top }; 1070 }, 1071 1072 _cellsByRowResizeChanged : function() { 1073 return this._checkIfSegmentsChanged(); 1074 }, 1075 1076 1077 // ====================== straightDown ====================== 1078 1079 _straightDownReset : function() { 1080 this.straightDown = { 1081 y : 0 1082 }; 1083 }, 1084 1085 _straightDownLayout : function( $elems ) { 1086 var instance = this; 1087 $elems.each( function( i ){ 1088 var $this = $(this); 1089 instance._pushPosition( $this, 0, instance.straightDown.y ); 1090 instance.straightDown.y += $this.outerHeight(true); 1091 }); 1092 }, 1093 1094 _straightDownGetContainerSize : function() { 1095 return { height : this.straightDown.y }; 1096 }, 1097 1098 _straightDownResizeChanged : function() { 1099 return true; 1100 }, 1101 1102 1103 // ====================== masonryHorizontal ====================== 1104 1105 _masonryHorizontalReset : function() { 1106 // layout-specific props 1107 this.masonryHorizontal = {}; 1108 // FIXME shouldn't have to call this again 1109 this._getSegments( true ); 1110 var i = this.masonryHorizontal.rows; 1111 this.masonryHorizontal.rowXs = []; 1112 while (i--) { 1113 this.masonryHorizontal.rowXs.push( 0 ); 1114 } 1115 }, 1116 1117 _masonryHorizontalLayout : function( $elems ) { 1118 var instance = this, 1119 props = instance.masonryHorizontal; 1120 $elems.each(function(){ 1121 var $this = $(this), 1122 //how many rows does this brick span 1123 rowSpan = Math.ceil( $this.outerHeight(true) / props.rowHeight ); 1124 rowSpan = Math.min( rowSpan, props.rows ); 1125 1126 if ( rowSpan === 1 ) { 1127 // if brick spans only one column, just like singleMode 1128 instance._masonryHorizontalPlaceBrick( $this, props.rowXs ); 1129 } else { 1130 // brick spans more than one row 1131 // how many different places could this brick fit horizontally 1132 var groupCount = props.rows + 1 - rowSpan, 1133 groupX = [], 1134 groupRowX, i; 1135 1136 // for each group potential horizontal position 1137 for ( i=0; i < groupCount; i++ ) { 1138 // make an array of colY values for that one group 1139 groupRowX = props.rowXs.slice( i, i+rowSpan ); 1140 // and get the max value of the array 1141 groupX[i] = Math.max.apply( Math, groupRowX ); 1142 } 1143 1144 instance._masonryHorizontalPlaceBrick( $this, groupX ); 1145 } 1146 }); 1147 }, 1148 1149 _masonryHorizontalPlaceBrick : function( $brick, setX ) { 1150 // get the minimum Y value from the columns 1151 var minimumX = Math.min.apply( Math, setX ), 1152 smallRow = 0; 1153 // Find index of smallest row, the first from the top 1154 for (var i=0, len = setX.length; i < len; i++) { 1155 if ( setX[i] === minimumX ) { 1156 smallRow = i; 1157 break; 1158 } 1159 } 1160 1161 // position the brick 1162 var x = minimumX, 1163 y = this.masonryHorizontal.rowHeight * smallRow; 1164 this._pushPosition( $brick, x, y ); 1165 1166 // apply setHeight to necessary columns
1167 var setWidth = minimumX + $brick.outerWidth(true), 1168 setSpan = this.masonryHorizontal.rows + 1 - len; 1169 for ( i=0; i < setSpan; i++ ) { 1170 this.masonryHorizontal.rowXs[ smallRow + i ] = setWidth; 1171 } 1172 }, 1173 1174 _masonryHorizontalGetContainerSize : function() { 1175 var containerWidth = Math.max.apply( Math, this.masonryHorizontal.rowXs ); 1176 return { width: containerWidth }; 1177 }, 1178 1179 _masonryHorizontalResizeChanged : function() { 1180 return this._checkIfSegmentsChanged(true); 1181 }, 1182 1183 1184 // ====================== fitColumns ====================== 1185 1186 _fitColumnsReset : function() { 1187 this.fitColumns = { 1188 x : 0, 1189 y : 0, 1190 width : 0 1191 }; 1192 }, 1193 1194 _fitColumnsLayout : function( $elems ) { 1195 var instance = this, 1196 containerHeight = this.element.height(), 1197 props = this.fitColumns; 1198 $elems.each( function() { 1199 var $this = $(this), 1200 atomW = $this.outerWidth(true), 1201 atomH = $this.outerHeight(true); 1202 1203 if ( props.y !== 0 && atomH + props.y > containerHeight ) { 1204 // if this element cannot fit in the current column 1205 props.x = props.width; 1206 props.y = 0; 1207 } 1208 1209 // position the atom 1210 instance._pushPosition( $this, props.x, props.y ); 1211 1212 props.width = Math.max( props.x + atomW, props.width ); 1213 props.y += atomH; 1214 1215 }); 1216 }, 1217 1218 _fitColumnsGetContainerSize : function () { 1219 return { width : this.fitColumns.width }; 1220 }, 1221 1222 _fitColumnsResizeChanged : function() { 1223 return true; 1224 }, 1225 1226 1227 1228 // ====================== cellsByColumn ====================== 1229 1230 _cellsByColumnReset : function() { 1231 this.cellsByColumn = { 1232 index : 0 1233 }; 1234 // get this.cellsByColumn.columnWidth 1235 this._getSegments(); 1236 // get this.cellsByColumn.rowHeight 1237 this._getSegments(true); 1238 }, 1239 1240 _cellsByColumnLayout : function( $elems ) { 1241 var instance = this, 1242 props = this.cellsByColumn; 1243 $elems.each( function(){ 1244 var $this = $(this), 1245 col = Math.floor( props.index / props.rows ), 1246 row = props.index % props.rows, 1247 x = ( col + 0.5 ) * props.columnWidth - $this.outerWidth(true) / 2, 1248 y = ( row + 0.5 ) * props.rowHeight - $this.outerHeight(true) / 2; 1249 instance._pushPosition( $this, x, y ); 1250 props.index ++; 1251 }); 1252 }, 1253 1254 _cellsByColumnGetContainerSize : function() { 1255 return { width : Math.ceil( this.$filteredAtoms.length / this.cellsByColumn.rows ) * this.cellsByColumn.columnWidth }; 1256 }, 1257 1258 _cellsByColumnResizeChanged : function() { 1259 return this._checkIfSegmentsChanged(true); 1260 }, 1261 1262 // ====================== straightAcross ====================== 1263 1264 _straightAcrossReset : function() { 1265 this.straightAcross = { 1266 x : 0 1267 }; 1268 }, 1269 1270 _straightAcrossLayout : function( $elems ) { 1271 var instance = this; 1272 $elems.each( function( i ){ 1273 var $this = $(this); 1274 instance._pushPosition( $this, instance.straightAcross.x, 0 ); 1275 instance.straightAcross.x += $this.outerWidth(true); 1276 }); 1277 }, 1278 1279 _straightAcrossGetContainerSize : function() { 1280 return { width : this.straightAcross.x }; 1281 }, 1282 1283 _straightAcrossResizeChanged : function() { 1284 return true; 1285 } 1286 1287 }; 1288 1289 1290 // ======================= imagesLoaded Plugin =============================== 1291 /*! 1292 * jQuery imagesLoaded plugin v1.1.0 1293 * http://github.com/desandro/imagesloaded 1294 * 1295 * MIT License. by Paul Irish et al. 1296 */ 1297 1298 1299 // $('#my-container').imagesLoaded(myFunction) 1300 // or 1301 // $('img').imagesLoaded(myFunction) 1302 1303 // execute a callback when all images have loaded. 1304 // needed because .load() doesn't work on cached images 1305 1306 // callback function gets image collection as argument 1307 // `this` is the container 1308 1309 $.fn.imagesLoaded = function( callback ) { 1310 var $this = this, 1311 $images = $this.find('img').add( $this.filter('img') ), 1312 len = $images.length, 1313 blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', 1314 loaded = []; 1315 1316 function triggerCallback() { 1317 callback.call( $this, $images ); 1318 } 1319 1320 function imgLoaded( event ) { 1321 var img = event.target; 1322 if ( img.src !== blank && $.inArray( img, loaded ) === -1 ){ 1323 loaded.push( img ); 1324 if ( --len <= 0 ){ 1325 setTimeout( triggerCallback ); 1326 $images.unbind( '.imagesLoaded', imgLoaded ); 1327 } 1328 } 1329 } 1330 1331 // if no images, trigger immediately 1332 if ( !len ) { 1333 triggerCallback(); 1334 } 1335 1336 $images.bind( 'load.imagesLoaded error.imagesLoaded', imgLoaded ).each( function() { 1337 // cached images don't fire load sometimes, so we reset src. 1338 var src = this.src; 1339 // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f 1340 // data uri bypasses webkit log warning (thx doug jones) 1341 this.src = blank; 1342 this.src = src; 1343 });
1344 1345 return $this; 1346 }; 1347 1348 1349 // helper function for logging errors 1350 // $.error breaks jQuery chaining 1351 var logError = function( message ) { 1352 if ( window.console ) { 1353 window.console.error( message ); 1354 } 1355 }; 1356 1357 // ======================= Plugin bridge =============================== 1358 // leverages data method to either create or return $.Isotope constructor 1359 // A bit from jQuery UI 1360 // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js 1361 // A bit from jcarousel 1362 // https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js 1363 1364 $.fn.isotope = function( options, callback ) { 1365 if ( typeof options === 'string' ) { 1366 // call method 1367 var args = Array.prototype.slice.call( arguments, 1 ); 1368 1369 this.each(function(){ 1370 var instance = $.data( this, 'isotope' ); 1371 if ( !instance ) { 1372 logError( "cannot call methods on isotope prior to initialization; " + 1373 "attempted to call method '" + options + "'" ); 1374 return; 1375 } 1376 if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) { 1377 logError( "no such method '" + options + "' for isotope instance" ); 1378 return; 1379 } 1380 // apply method 1381 instance[ options ].apply( instance, args ); 1382 }); 1383 } else { 1384 this.each(function() { 1385 var instance = $.data( this, 'isotope' ); 1386 if ( instance ) { 1387 // apply options & init 1388 instance.option( options ); 1389 instance._init( callback ); 1390 } else { 1391 // initialize new instance 1392 $.data( this, 'isotope', new $.Isotope( options, this, callback ) ); 1393 } 1394 }); 1395 } 1396 // return jQuery object 1397 // so plugin methods do not have to 1398 return this; 1399 }; 1400 1401})( 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.