masonry-rails 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 );