Dashboard sipadu mbip
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

jquery.isotope.js 44KB

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