masonry-rails 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -13,11 +13,28 @@ This gem includes:
13
13
  * jquery infinitescroll
14
14
  * jquery event drag
15
15
 
16
+ Now [Isotope](http://isotope.metafizzy.co/index.html) is also included. Masonry and Isotope can even be used in combination!
17
+
16
18
  For random content generation
17
19
 
18
20
  * box maker
19
21
  * loremimages
20
22
 
23
+ ## Isotope assets
24
+
25
+ Javascript
26
+
27
+ * jquery.isotope.js
28
+ * jquery.isotope.min.js
29
+
30
+ Stylesheets
31
+
32
+ * isotope.css
33
+
34
+ More stylesheets in `isotope` folder in `vendor/assets
35
+
36
+ Please see Isotope [License](http://isotope.metafizzy.co/docs/license.html) for commercial use.
37
+
21
38
  ## Usage
22
39
 
23
40
  In Gemfile
@@ -300,7 +317,7 @@ Put a `<nav>` under your main container. This will trigger loading the next page
300
317
  </nav>
301
318
  ```
302
319
 
303
- The link should load in a page containing elements that match `.block`
320
+ The link should load in a page containing elements that match `.box`
304
321
 
305
322
  ```html
306
323
  <div class="box col3">
@@ -381,9 +398,28 @@ Or whichever animated loader icon you want to use ;)
381
398
 
382
399
  You can combine masonry infinite scroll with Kaminari using suggestions here:
383
400
 
384
- [Create-Infinite-Scrolling-with-jQuery](https://github.com/amatsuda/kaminari/wiki/How-To:-Create-Infinite-Scrolling-with-jQuery)
401
+ In the above example, to enable paging simply have the `pages/2.html` link respond with the next page and infinite scroll will automatically ajax-retrieve and render the elements indicated by `itemSelector` into the container.
402
+
403
+ `$container.masonry( 'appended', $newElems, true );`
385
404
 
386
- [Sausage](https://github.com/christophercliff/sausage), used in this example is also included with this gem ;)
405
+ In the page returned, ensure it contains a `#pagenav` element with a link to the next page.
406
+
407
+ ```javascript
408
+ function( newElements ) {
409
+ // `this` matches the element you called the plugin on (fx. #container)
410
+
411
+ // get new #page-nav
412
+ var nexPageNav = $(this).find('#page-nav');
413
+
414
+ // substitute current #page-nav with new #page-nav from page loaded
415
+ $('#page-nav').replaceWith(nexPageNav);
416
+
417
+ # ...
418
+ ```
419
+
420
+ See [Create-Infinite-Scrolling-with-jQuery](https://github.com/amatsuda/kaminari/wiki/How-To:-Create-Infinite-Scrolling-with-jQuery) for another example.
421
+
422
+ [Sausage](https://github.com/christophercliff/sausage) which used in this example is also included with this gem for convenience ;)
387
423
 
388
424
  Stylesheets: `sausage/sausage.css`and `sausage/sausage.reset.css`
389
425
 
@@ -391,6 +427,70 @@ Javascript: `masonry/jquery.sausagemin.min.js`
391
427
 
392
428
  For more see [sausage info](http://christophercliff.github.com/sausage)
393
429
 
430
+ The div with a class of 'page' is important for sausage. It is used to determine the different pages for the navigation on the right.
431
+
432
+ ```html
433
+ <div id="container" class="transitions-enabled infinite-scroll clearfix">
434
+ <div class='page'>
435
+ <%= render @articles %>
436
+ </div>
437
+ </div>
438
+ ```
439
+
440
+ ```html
441
+ <div class='article box col<%= article.size %>'>
442
+ <h3><%= article.title %></h3>
443
+ <div class='author'>by <%= article.author %></div>
444
+ <div class='date'>by <%= article.created_at.to_s(:long) %></div>
445
+ <p>
446
+ <%= article.body %>
447
+ </p>
448
+ </div>
449
+ ```
450
+
451
+ Where `article.size` returns a valid col size, fx 1 to 5.
452
+
453
+ ```javascript
454
+ (function() {
455
+ var page = 1,
456
+ loading = false;
457
+
458
+ function nearBottomOfPage() {
459
+ return $(window).scrollTop() > $(document).height() - $(window).height() - 200;
460
+ }
461
+
462
+ $(window).scroll(function(){
463
+ if (loading) {
464
+ return;
465
+ }
466
+
467
+ if(nearBottomOfPage()) {
468
+ loading=true;
469
+ page++;
470
+ $.ajax({
471
+ url: '/articles?page=' + page,
472
+ type: 'get',
473
+ dataType: 'script',
474
+ success: function() {
475
+ $(window).sausage('draw');
476
+ loading=false;
477
+ }
478
+ });
479
+ }
480
+ });
481
+
482
+ $(window).sausage();
483
+ }());
484
+ ```
485
+
486
+ It checks if the user scrolled to the bottom of the page. If that is the case, an ajax-request is sent to the `ArticlesController requesting the next page. After the ajax-request is completed successfully the sausage-navigation is redrawn.
487
+
488
+ When the ajax-request is sent to the ArticlesController we need to append the next page of articles. We need to create a file named `index.js.erb` to achieve this goal.
489
+
490
+ ```javascript
491
+ $("#container").append("<div class='page'><%= escape_javascript(render(@articles)) %></div>");
492
+ ```
493
+
394
494
  Note: You need to configure Jquery UI to use sausage.
395
495
 
396
496
  See: [railscast-endless-page](http://railscasts.com/episodes/114-endless-page-revised) for how to use endless pages with Rails using *will_paginate* gem.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.7
1
+ 0.1.8
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "masonry-rails"
8
- s.version = "0.1.7"
8
+ s.version = "0.1.8"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kristian Mandrup"]
12
- s.date = "2012-08-30"
12
+ s.date = "2012-09-17"
13
13
  s.description = "Masonry will rock your world!"
14
14
  s.email = "kmandrup@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -64,6 +64,8 @@ Gem::Specification.new do |s|
64
64
  "spec/index.html",
65
65
  "spec/spec_helper.rb",
66
66
  "vendor/assets/images/masonry/loader.gif",
67
+ "vendor/assets/javascripts/jquery.isotope.js",
68
+ "vendor/assets/javascripts/jquery.isotope.min.js",
67
69
  "vendor/assets/javascripts/masonry/box-maker.js",
68
70
  "vendor/assets/javascripts/masonry/jquery.event-drag.js",
69
71
  "vendor/assets/javascripts/masonry/jquery.imagesloaded.js",
@@ -77,6 +79,11 @@ Gem::Specification.new do |s|
77
79
  "vendor/assets/javascripts/masonry/jquery.sausage.js",
78
80
  "vendor/assets/javascripts/masonry/jquery.sausage.min.js",
79
81
  "vendor/assets/javascripts/masonry/modernizr-transitions.js",
82
+ "vendor/assets/stylesheets/isotope.css",
83
+ "vendor/assets/stylesheets/isotope/base.css",
84
+ "vendor/assets/stylesheets/isotope/colorshapes.css",
85
+ "vendor/assets/stylesheets/isotope/elements.css",
86
+ "vendor/assets/stylesheets/isotope/options.css",
80
87
  "vendor/assets/stylesheets/masonry/basic.css",
81
88
  "vendor/assets/stylesheets/masonry/centered.css",
82
89
  "vendor/assets/stylesheets/masonry/fluid.css",
@@ -0,0 +1,1401 @@
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 );