masonry-rails 0.1.0

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.
Files changed (54) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +109 -0
  5. data/LICENSE.txt +20 -0
  6. data/Masonry.mdown +51 -0
  7. data/README.md +53 -0
  8. data/Rakefile +49 -0
  9. data/VERSION +1 -0
  10. data/_config.yml +6 -0
  11. data/lib/masonry-rails.rb +7 -0
  12. data/masonry-rails.gemspec +107 -0
  13. data/minify.sh +16 -0
  14. data/spec/_layouts/default.html +75 -0
  15. data/spec/_posts/demos/2011-05-01-basic-single-column.html +206 -0
  16. data/spec/_posts/demos/2011-05-02-basic-multi-column.html +213 -0
  17. data/spec/_posts/demos/2011-05-03-images.html +57 -0
  18. data/spec/_posts/demos/2011-05-04-tumblelog.html +226 -0
  19. data/spec/_posts/demos/2011-05-05-animating-jquery.html +144 -0
  20. data/spec/_posts/demos/2011-05-06-animating-css-transitions.html +132 -0
  21. data/spec/_posts/demos/2011-05-07-animating-modernizr.html +136 -0
  22. data/spec/_posts/demos/2011-05-08-adding-items.html +89 -0
  23. data/spec/_posts/demos/2011-05-09-infinite-scroll.html +268 -0
  24. data/spec/_posts/demos/2011-05-10-gutters.html +209 -0
  25. data/spec/_posts/demos/2011-05-11-right-to-left.html +135 -0
  26. data/spec/_posts/demos/2011-05-17-centered.html +137 -0
  27. data/spec/_posts/demos/2011-07-26-fluid.html +219 -0
  28. data/spec/_posts/demos/2011-10-07-corner-stamp.html +249 -0
  29. data/spec/_posts/docs/2011-05-01-intro.mdown +149 -0
  30. data/spec/_posts/docs/2011-05-02-options.mdown +157 -0
  31. data/spec/_posts/docs/2011-05-03-methods.mdown +155 -0
  32. data/spec/_posts/docs/2011-05-04-animating.mdown +96 -0
  33. data/spec/_posts/docs/2011-05-30-help.mdown +139 -0
  34. data/spec/_posts/pages/2011-05-02-2.html +189 -0
  35. data/spec/_posts/pages/2011-05-03-3.html +174 -0
  36. data/spec/_posts/pages/2011-05-04-4.html +159 -0
  37. data/spec/_posts/pages/2011-05-05-5.html +167 -0
  38. data/spec/_posts/tests/2011-01-01-index.html +14 -0
  39. data/spec/_posts/tests/2011-05-14-lots-of-bricks.html +1438 -0
  40. data/spec/_posts/tests/2011-05-17-dual.html +247 -0
  41. data/spec/_posts/tests/2011-05-18-first-child-no-width.html +217 -0
  42. data/spec/_posts/tests/2011-05-19-empty.html +205 -0
  43. data/spec/_posts/tests/2011-08-08-destroy.html +214 -0
  44. data/spec/_posts/tests/2011-09-27-centered-few.html +50 -0
  45. data/spec/index.html +58 -0
  46. data/spec/spec_helper.rb +12 -0
  47. data/vendor/assets/javascripts/masonry/box-maker.js +38 -0
  48. data/vendor/assets/javascripts/masonry/jquery.event.drag-2.2.js +402 -0
  49. data/vendor/assets/javascripts/masonry/jquery.infinitescroll.min.js +47 -0
  50. data/vendor/assets/javascripts/masonry/jquery.masonry.js +682 -0
  51. data/vendor/assets/javascripts/masonry/jquery.masonry.min.js +10 -0
  52. data/vendor/assets/javascripts/masonry/modernizr-transitions.js +2 -0
  53. data/vendor/assets/stylesheets/masonry/style.css +616 -0
  54. metadata +199 -0
@@ -0,0 +1,47 @@
1
+ /*
2
+ --------------------------------
3
+ Infinite Scroll
4
+ --------------------------------
5
+ + https://github.com/paulirish/infinitescroll
6
+ + version 2.0b2.110713
7
+ + Copyright 2011 Paul Irish & Luke Shumard
8
+ + Licensed under the MIT license
9
+
10
+ + Documentation: http://infinite-scroll.com/
11
+
12
+ */
13
+
14
+ (function(window,$,undefined){$.infinitescroll=function infscr(options,callback,element){this.element=$(element);this._create(options,callback);};$.infinitescroll.defaults={loading:{finished:undefined,finishedMsg:"<em>Congratulations, you've reached the end of the internet.</em>",img:"http://www.infinite-scroll.com/loading.gif",msg:null,msgText:"<em>Loading the next set of posts...</em>",selector:null,speed:'fast',start:undefined},state:{isDuringAjax:false,isInvalidPage:false,isDestroyed:false,isDone:false,isPaused:false,currPage:1},callback:undefined,debug:false,behavior:undefined,binder:$(window),nextSelector:"div.navigation a:first",navSelector:"div.navigation",contentSelector:null,extraScrollPx:150,itemSelector:"div.post",animate:false,pathParse:undefined,dataType:'html',appendCallback:true,bufferPx:40,errorCallback:function(){},infid:0,pixelsFromNavToBottom:undefined,path:undefined};$.infinitescroll.prototype={_binding:function infscr_binding(binding){var instance=this,opts=instance.options;if(!!opts.behavior&&this['_binding_'+opts.behavior]!==undefined){this['_binding_'+opts.behavior].call(this);return;}
15
+ if(binding!=='bind'&&binding!=='unbind'){this._debug('Binding value '+binding+' not valid')
16
+ return false;}
17
+ if(binding=='unbind'){(this.options.binder).unbind('smartscroll.infscr.'+instance.options.infid);}else{(this.options.binder)[binding]('smartscroll.infscr.'+instance.options.infid,function(){instance.scroll();});};this._debug('Binding',binding);},_create:function infscr_create(options,callback){if(!this._validate(options)){return false;}
18
+ var opts=this.options=$.extend(true,{},$.infinitescroll.defaults,options),relurl=/(.*?\/\/).*?(\/.*)/,path=$(opts.nextSelector).attr('href');opts.contentSelector=opts.contentSelector||this.element;opts.loading.selector=opts.loading.selector||opts.contentSelector;if(!path){this._debug('Navigation selector not found');return;}
19
+ opts.path=this._determinepath(path);opts.loading.msg=$('<div id="infscr-loading"><img alt="Loading..." src="'+opts.loading.img+'" /><div>'+opts.loading.msgText+'</div></div>');(new Image()).src=opts.loading.img;opts.pixelsFromNavToBottom=$(document).height()-$(opts.navSelector).offset().top;opts.loading.start=opts.loading.start||function(){$(opts.navSelector).hide();opts.loading.msg.appendTo(opts.loading.selector).show(opts.loading.speed,function(){beginAjax(opts);});};opts.loading.finished=opts.loading.finished||function(){opts.loading.msg.fadeOut('normal');};opts.callback=function(instance,data){if(!!opts.behavior&&instance['_callback_'+opts.behavior]!==undefined){instance['_callback_'+opts.behavior].call($(opts.contentSelector)[0],data);}
20
+ if(callback){callback.call($(opts.contentSelector)[0],data);}};this._setup();},_debug:function infscr_debug(){if(this.options.debug){return window.console&&console.log.call(console,arguments);}},_determinepath:function infscr_determinepath(path){var opts=this.options;if(!!opts.behavior&&this['_determinepath_'+opts.behavior]!==undefined){this['_determinepath_'+opts.behavior].call(this,path);return;}
21
+ if(!!opts.pathParse){this._debug('pathParse manual');return opts.pathParse;}else if(path.match(/^(.*?)\b2\b(.*?$)/)){path=path.match(/^(.*?)\b2\b(.*?$)/).slice(1);}else if(path.match(/^(.*?)2(.*?$)/)){if(path.match(/^(.*?page=)2(\/.*|$)/)){path=path.match(/^(.*?page=)2(\/.*|$)/).slice(1);return path;}
22
+ path=path.match(/^(.*?)2(.*?$)/).slice(1);}else{if(path.match(/^(.*?page=)1(\/.*|$)/)){path=path.match(/^(.*?page=)1(\/.*|$)/).slice(1);return path;}else{this._debug('Sorry, we couldn\'t parse your Next (Previous Posts) URL. Verify your the css selector points to the correct A tag. If you still get this error: yell, scream, and kindly ask for help at infinite-scroll.com.');opts.state.isInvalidPage=true;}}
23
+ this._debug('determinePath',path);return path;},_error:function infscr_error(xhr){var opts=this.options;if(!!opts.behavior&&this['_error_'+opts.behavior]!==undefined){this['_error_'+opts.behavior].call(this,xhr);return;}
24
+ if(xhr!=='destroy'&&xhr!=='end'){xhr='unknown';}
25
+ this._debug('Error',xhr);if(xhr=='end'){this._showdonemsg();}
26
+ opts.state.isDone=true;opts.state.currPage=1;opts.state.isPaused=false;this._binding('unbind');},_loadcallback:function infscr_loadcallback(box,data){var opts=this.options,callback=this.options.callback,result=(opts.state.isDone)?'done':(!opts.appendCallback)?'no-append':'append',frag;if(!!opts.behavior&&this['_loadcallback_'+opts.behavior]!==undefined){this['_loadcallback_'+opts.behavior].call(this,box,data);return;}
27
+ switch(result){case'done':this._showdonemsg();return false;break;case'no-append':if(opts.dataType=='html'){data='<div>'+data+'</div>';data=$(data).find(opts.itemSelector);};break;case'append':var children=box.children();if(children.length==0){return this._error('end');}
28
+ frag=document.createDocumentFragment();while(box[0].firstChild){frag.appendChild(box[0].firstChild);}
29
+ this._debug('contentSelector',$(opts.contentSelector)[0])
30
+ $(opts.contentSelector)[0].appendChild(frag);data=children.get();break;}
31
+ opts.loading.finished.call($(opts.contentSelector)[0],opts)
32
+ if(opts.animate){var scrollTo=$(window).scrollTop()+$('#infscr-loading').height()+opts.extraScrollPx+'px';$('html,body').animate({scrollTop:scrollTo},800,function(){opts.state.isDuringAjax=false;});}
33
+ if(!opts.animate)opts.state.isDuringAjax=false;callback(this,data);},_nearbottom:function infscr_nearbottom(){var opts=this.options,pixelsFromWindowBottomToBottom=0+$(document).height()-(opts.binder.scrollTop())-$(window).height();if(!!opts.behavior&&this['_nearbottom_'+opts.behavior]!==undefined){this['_nearbottom_'+opts.behavior].call(this);return;}
34
+ this._debug('math:',pixelsFromWindowBottomToBottom,opts.pixelsFromNavToBottom);return(pixelsFromWindowBottomToBottom-opts.bufferPx<opts.pixelsFromNavToBottom);},_pausing:function infscr_pausing(pause){var opts=this.options;if(!!opts.behavior&&this['_pausing_'+opts.behavior]!==undefined){this['_pausing_'+opts.behavior].call(this,pause);return;}
35
+ if(pause!=='pause'&&pause!=='resume'&&pause!==null){this._debug('Invalid argument. Toggling pause value instead');};pause=(pause&&(pause=='pause'||pause=='resume'))?pause:'toggle';switch(pause){case'pause':opts.state.isPaused=true;break;case'resume':opts.state.isPaused=false;break;case'toggle':opts.state.isPaused=!opts.state.isPaused;break;}
36
+ this._debug('Paused',opts.state.isPaused);return false;},_setup:function infscr_setup(){var opts=this.options;if(!!opts.behavior&&this['_setup_'+opts.behavior]!==undefined){this['_setup_'+opts.behavior].call(this);return;}
37
+ this._binding('bind');return false;},_showdonemsg:function infscr_showdonemsg(){var opts=this.options;if(!!opts.behavior&&this['_showdonemsg_'+opts.behavior]!==undefined){this['_showdonemsg_'+opts.behavior].call(this);return;}
38
+ opts.loading.msg.find('img').hide().parent().find('div').html(opts.loading.finishedMsg).animate({opacity:1},2000,function(){$(this).parent().fadeOut('normal');});opts.errorCallback.call($(opts.contentSelector)[0],'done');},_validate:function infscr_validate(opts){for(var key in opts){if(key.indexOf&&key.indexOf('Selector')>-1&&$(opts[key]).length===0){this._debug('Your '+key+' found no elements.');return false;}
39
+ return true;}},bind:function infscr_bind(){this._binding('bind');},destroy:function infscr_destroy(){this.options.state.isDestroyed=true;return this._error('destroy');},pause:function infscr_pause(){this._pausing('pause');},resume:function infscr_resume(){this._pausing('resume');},retrieve:function infscr_retrieve(pageNum){var instance=this,opts=instance.options,path=opts.path,box,frag,desturl,method,condition,pageNum=pageNum||null,getPage=(!!pageNum)?pageNum:opts.state.currPage;beginAjax=function infscr_ajax(opts){opts.state.currPage++;instance._debug('heading into ajax',path);box=$(opts.contentSelector).is('table')?$('<tbody/>'):$('<div/>');desturl=path.join(opts.state.currPage);method=(opts.dataType=='html'||opts.dataType=='json')?opts.dataType:'html+callback';if(opts.appendCallback&&opts.dataType=='html')method+='+callback'
40
+ switch(method){case'html+callback':instance._debug('Using HTML via .load() method');box.load(desturl+' '+opts.itemSelector,null,function infscr_ajax_callback(responseText){instance._loadcallback(box,responseText);});break;case'html':case'json':instance._debug('Using '+(method.toUpperCase())+' via $.ajax() method');$.ajax({url:desturl,dataType:opts.dataType,complete:function infscr_ajax_callback(jqXHR,textStatus){condition=(typeof(jqXHR.isResolved)!=='undefined')?(jqXHR.isResolved()):(textStatus==="success"||textStatus==="notmodified");(condition)?instance._loadcallback(box,jqXHR.responseText):instance._error('end');}});break;}};if(!!opts.behavior&&this['retrieve_'+opts.behavior]!==undefined){this['retrieve_'+opts.behavior].call(this,pageNum);return;}
41
+ if(opts.state.isDestroyed){this._debug('Instance is destroyed');return false;};opts.state.isDuringAjax=true;opts.loading.start.call($(opts.contentSelector)[0],opts);},scroll:function infscr_scroll(){var opts=this.options,state=opts.state;if(!!opts.behavior&&this['scroll_'+opts.behavior]!==undefined){this['scroll_'+opts.behavior].call(this);return;}
42
+ if(state.isDuringAjax||state.isInvalidPage||state.isDone||state.isDestroyed||state.isPaused)return;if(!this._nearbottom())return;this.retrieve();},toggle:function infscr_toggle(){this._pausing();},unbind:function infscr_unbind(){this._binding('unbind');},update:function infscr_options(key){if($.isPlainObject(key)){this.options=$.extend(true,this.options,key);}}}
43
+ $.fn.infinitescroll=function infscr_init(options,callback){var thisCall=typeof options;switch(thisCall){case'string':var args=Array.prototype.slice.call(arguments,1);this.each(function(){var instance=$.data(this,'infinitescroll');if(!instance){return false;}
44
+ if(!$.isFunction(instance[options])||options.charAt(0)==="_"){return false;}
45
+ instance[options].apply(instance,args);});break;case'object':this.each(function(){var instance=$.data(this,'infinitescroll');if(instance){instance.update(options);}else{$.data(this,'infinitescroll',new $.infinitescroll(options,callback,this));}});break;}
46
+ return this;};var event=$.event,scrollTimeout;event.special.smartscroll={setup:function(){$(this).bind("scroll",event.special.smartscroll.handler);},teardown:function(){$(this).unbind("scroll",event.special.smartscroll.handler);},handler:function(event,execAsap){var context=this,args=arguments;event.type="smartscroll";if(scrollTimeout){clearTimeout(scrollTimeout);}
47
+ scrollTimeout=setTimeout(function(){$.event.handle.apply(context,args);},execAsap==="execAsap"?0:100);}};$.fn.smartscroll=function(fn){return fn?this.bind("smartscroll",fn):this.trigger("smartscroll",["execAsap"]);};})(window,jQuery);
@@ -0,0 +1,682 @@
1
+ /**
2
+ * jQuery Masonry v2.1.05
3
+ * A dynamic layout plugin for jQuery
4
+ * The flip-side of CSS Floats
5
+ * http://masonry.desandro.com
6
+ *
7
+ * Licensed under the MIT license.
8
+ * Copyright 2012 David DeSandro
9
+ */
10
+
11
+ /*jshint browser: true, curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, strict: true, undef: true */
12
+ /*global jQuery: false */
13
+
14
+ (function( window, $, undefined ){
15
+
16
+ 'use strict';
17
+
18
+ /*
19
+ * smartresize: debounced resize event for jQuery
20
+ *
21
+ * latest version and complete README available on Github:
22
+ * https://github.com/louisremi/jquery.smartresize.js
23
+ *
24
+ * Copyright 2011 @louis_remi
25
+ * Licensed under the MIT license.
26
+ */
27
+
28
+ var $event = $.event,
29
+ resizeTimeout;
30
+
31
+ $event.special.smartresize = {
32
+ setup: function() {
33
+ $(this).bind( "resize", $event.special.smartresize.handler );
34
+ },
35
+ teardown: function() {
36
+ $(this).unbind( "resize", $event.special.smartresize.handler );
37
+ },
38
+ handler: function( event, execAsap ) {
39
+ // Save the context
40
+ var context = this,
41
+ args = arguments;
42
+
43
+ // set correct event type
44
+ event.type = "smartresize";
45
+
46
+ if ( resizeTimeout ) { clearTimeout( resizeTimeout ); }
47
+ resizeTimeout = setTimeout(function() {
48
+ $.event.handle.apply( context, args );
49
+ }, execAsap === "execAsap"? 0 : 100 );
50
+ }
51
+ };
52
+
53
+ $.fn.smartresize = function( fn ) {
54
+ return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] );
55
+ };
56
+
57
+
58
+
59
+ // ========================= Masonry ===============================
60
+
61
+
62
+ // our "Widget" object constructor
63
+ $.Mason = function( options, element ){
64
+ this.element = $( element );
65
+
66
+ this._create( options );
67
+ this._init();
68
+ };
69
+
70
+ $.Mason.settings = {
71
+ isResizable: true,
72
+ isDraggable: false,
73
+ dragHandleSelector: null,
74
+ dragClass: null,
75
+ isAnimated: false,
76
+ animationOptions: {
77
+ queue: false,
78
+ duration: 500
79
+ },
80
+ gutterWidth: 0,
81
+ isRTL: false,
82
+ isFitWidth: false,
83
+ containerStyle: {
84
+ position: 'relative'
85
+ }
86
+ };
87
+
88
+ $.Mason.prototype = {
89
+
90
+ _filterFindBricks: function( $elems ) {
91
+ var selector = this.options.itemSelector;
92
+ // if there is a selector
93
+ // filter/find appropriate item elements
94
+ return !selector ? $elems : $elems.filter( selector ).add( $elems.find( selector ) );
95
+ },
96
+
97
+ _getBricks: function( $elems ) {
98
+ var $bricks = this._filterFindBricks( $elems )
99
+ .css({ position: 'absolute' })
100
+ .addClass('masonry-brick');
101
+ return $bricks;
102
+ },
103
+
104
+ // sets up widget
105
+ _create : function( options ) {
106
+
107
+ this.options = $.extend( true, {}, $.Mason.settings, options );
108
+ this.styleQueue = [];
109
+
110
+ // get original styles in case we re-apply them in .destroy()
111
+ var elemStyle = this.element[0].style;
112
+ this.originalStyle = {
113
+ // get height
114
+ height: elemStyle.height || ''
115
+ };
116
+ // get other styles that will be overwritten
117
+ var containerStyle = this.options.containerStyle;
118
+ for ( var prop in containerStyle ) {
119
+ this.originalStyle[ prop ] = elemStyle[ prop ] || '';
120
+ }
121
+
122
+ this.element.css( containerStyle );
123
+
124
+ this.horizontalDirection = this.options.isRTL ? 'right' : 'left';
125
+
126
+ this.offset = {
127
+ x: parseInt( this.element.css( 'padding-' + this.horizontalDirection ), 10 ),
128
+ y: parseInt( this.element.css( 'padding-top' ), 10 )
129
+ };
130
+
131
+ this.isFluid = this.options.columnWidth && typeof this.options.columnWidth === 'function';
132
+
133
+ // add masonry class first time around
134
+ var instance = this;
135
+ setTimeout( function() {
136
+ instance.element.addClass('masonry');
137
+ }, 0 );
138
+
139
+ // bind resize method
140
+ if ( this.options.isResizable ) {
141
+ $(window).bind( 'smartresize.masonry', function() {
142
+ instance.resize();
143
+ });
144
+ }
145
+
146
+
147
+ // need to get bricks
148
+ this.reloadItems();
149
+
150
+ // set up dragging
151
+ if (this.options.isDraggable ){
152
+ this._initDrag(this.$bricks);
153
+ }
154
+
155
+ },
156
+
157
+
158
+ // _init fires when instance is first created
159
+ // and when instance is triggered again -> $el.masonry();
160
+ _init : function( callback ) {
161
+ this._getColumns();
162
+ this._reLayout( callback );
163
+ },
164
+
165
+ option: function( key, value ){
166
+ // set options AFTER initialization:
167
+ // signature: $('#foo').bar({ cool:false });
168
+ if ( $.isPlainObject( key ) ){
169
+ this.options = $.extend(true, this.options, key);
170
+ }
171
+ },
172
+
173
+ // ====================== General Layout ======================
174
+
175
+ // used on collection of atoms (should be filtered, and sorted before )
176
+ // accepts atoms-to-be-laid-out to start with
177
+ layout : function( $bricks, callback ) {
178
+
179
+ // place each brick
180
+ for (var i=0, len = $bricks.length; i < len; i++) {
181
+ this._placeBrick( $bricks[i] );
182
+ }
183
+
184
+ // set the size of the container
185
+ var containerSize = {};
186
+ containerSize.height = Math.max.apply( Math, this.colYs );
187
+ if ( this.options.isFitWidth ) {
188
+ var unusedCols = 0;
189
+ i = this.cols;
190
+ // count unused columns
191
+ while ( --i ) {
192
+ if ( this.colYs[i] !== 0 ) {
193
+ break;
194
+ }
195
+ unusedCols++;
196
+ }
197
+ // fit container to columns that have been used;
198
+ containerSize.width = (this.cols - unusedCols) * this.columnWidth - this.options.gutterWidth;
199
+ }
200
+ this.styleQueue.push({ $el: this.element, style: containerSize });
201
+
202
+ // are we animating the layout arrangement?
203
+ // use plugin-ish syntax for css or animate
204
+ var styleFn = !this.isLaidOut ? 'css' : (
205
+ this.options.isAnimated ? 'animate' : 'css'
206
+ ),
207
+ animOpts = this.options.animationOptions;
208
+
209
+ // process styleQueue
210
+ var obj;
211
+ for (i=0, len = this.styleQueue.length; i < len; i++) {
212
+ obj = this.styleQueue[i];
213
+ obj.$el[ styleFn ]( obj.style, animOpts );
214
+ }
215
+
216
+ // clear out queue for next time
217
+ this.styleQueue = [];
218
+
219
+ // provide $elems as context for the callback
220
+ if ( callback ) {
221
+ callback.call( $bricks );
222
+ }
223
+
224
+ this.isLaidOut = true;
225
+ },
226
+
227
+ // calculates number of columns
228
+ // i.e. this.columnWidth = 200
229
+ _getColumns : function() {
230
+ var container = this.options.isFitWidth ? this.element.parent() : this.element,
231
+ containerWidth = container.width();
232
+
233
+ // use fluid columnWidth function if there
234
+ this.columnWidth = this.isFluid ? this.options.columnWidth( containerWidth ) :
235
+ // if not, how about the explicitly set option?
236
+ this.options.columnWidth ||
237
+ // or use the size of the first item
238
+ this.$bricks.outerWidth(true) ||
239
+ // if there's no items, use size of container
240
+ containerWidth;
241
+
242
+ this.columnWidth += this.options.gutterWidth;
243
+
244
+ this.cols = Math.floor( ( containerWidth + this.options.gutterWidth ) / this.columnWidth );
245
+ this.cols = Math.max( this.cols, 1 );
246
+
247
+ },
248
+
249
+ // layout logic
250
+ _placeBrick: function( brick ) {
251
+ var $brick = $(brick),
252
+ colSpan, groupCount, groupY, groupColY, j;
253
+
254
+ //how many columns does this brick span
255
+ colSpan = Math.ceil( $brick.outerWidth(true) / this.columnWidth );
256
+ colSpan = Math.min( colSpan, this.cols );
257
+
258
+ if ( colSpan === 1 ) {
259
+ // if brick spans only one column, just like singleMode
260
+ groupY = this.colYs;
261
+ } else {
262
+ // brick spans more than one column
263
+ // how many different places could this brick fit horizontally
264
+ groupCount = this.cols + 1 - colSpan;
265
+ groupY = [];
266
+
267
+ // for each group potential horizontal position
268
+ for ( j=0; j < groupCount; j++ ) {
269
+ // make an array of colY values for that one group
270
+ groupColY = this.colYs.slice( j, j+colSpan );
271
+ // and get the max value of the array
272
+ groupY[j] = Math.max.apply( Math, groupColY );
273
+ }
274
+
275
+ }
276
+
277
+ // get the minimum Y value from the columns
278
+ var minimumY = Math.min.apply( Math, groupY ),
279
+ shortCol = 0;
280
+
281
+ // Find index of short column, the first from the left
282
+ for (var i=0, len = groupY.length; i < len; i++) {
283
+ if ( groupY[i] === minimumY ) {
284
+ shortCol = i;
285
+ break;
286
+ }
287
+ }
288
+
289
+ // position the brick
290
+ var position = {
291
+ top: minimumY + this.offset.y
292
+ };
293
+ // position.left or position.right
294
+ position[ this.horizontalDirection ] = this.columnWidth * shortCol + this.offset.x;
295
+ this.styleQueue.push({ $el: $brick, style: position });
296
+
297
+ // apply setHeight to necessary columns
298
+ var setHeight = minimumY + $brick.outerHeight(true),
299
+ setSpan = this.cols + 1 - len;
300
+ for ( i=0; i < setSpan; i++ ) {
301
+ this.colYs[ shortCol + i ] = setHeight;
302
+ }
303
+
304
+ },
305
+
306
+
307
+ resize: function() {
308
+ var prevColCount = this.cols;
309
+ // get updated colCount
310
+ this._getColumns();
311
+ if ( this.isFluid || this.cols !== prevColCount ) {
312
+ // if column count has changed, trigger new layout
313
+ this._reLayout();
314
+ }
315
+ },
316
+
317
+
318
+ _reLayout : function( callback ) {
319
+ // reset columns
320
+ var i = this.cols;
321
+ this.colYs = [];
322
+ while (i--) {
323
+ this.colYs.push( 0 );
324
+ }
325
+ // apply layout logic to all bricks
326
+ this.layout( this.$bricks, callback );
327
+ },
328
+
329
+ // ====================== Convenience methods ======================
330
+
331
+ // goes through all children again and gets bricks in proper order
332
+ reloadItems : function() {
333
+ this.$bricks = this._getBricks( this.element.children() );
334
+ },
335
+
336
+
337
+ reload : function( callback ) {
338
+ this.reloadItems();
339
+ this._init( callback );
340
+ },
341
+
342
+
343
+ // convienence method for working with Infinite Scroll
344
+ appended : function( $content, isAnimatedFromBottom, callback ) {
345
+ if ( isAnimatedFromBottom ) {
346
+ // set new stuff to the bottom
347
+ this._filterFindBricks( $content ).css({ top: this.element.height() });
348
+ var instance = this;
349
+ setTimeout( function(){
350
+ instance._appended( $content, callback );
351
+ }, 1 );
352
+ } else {
353
+ this._appended( $content, callback );
354
+ }
355
+ },
356
+
357
+ _appended : function( $content, callback ) {
358
+ var $newBricks = this._getBricks( $content );
359
+ // add new bricks to brick pool
360
+ this.$bricks = this.$bricks.add( $newBricks );
361
+ this.layout( $newBricks, callback );
362
+ },
363
+
364
+ // removes elements from Masonry widget
365
+ remove : function( $content ) {
366
+ this.$bricks = this.$bricks.not( $content );
367
+ $content.remove();
368
+ },
369
+
370
+ // destroys widget, returns elements and container back (close) to original style
371
+ destroy : function() {
372
+
373
+ this.$bricks
374
+ .removeClass('masonry-brick')
375
+ .each(function(){
376
+ this.style.position = '';
377
+ this.style.top = '';
378
+ this.style.left = '';
379
+ });
380
+
381
+ // re-apply saved container styles
382
+ var elemStyle = this.element[0].style;
383
+ for ( var prop in this.originalStyle ) {
384
+ elemStyle[ prop ] = this.originalStyle[ prop ];
385
+ }
386
+
387
+ this.element
388
+ .unbind('.masonry')
389
+ .removeClass('masonry')
390
+ .removeData('masonry');
391
+
392
+ $(window).unbind('.masonry');
393
+
394
+ },
395
+
396
+ _getDistanceBetween : function(point1, point2){
397
+ var dx = point1.x - point2.x,
398
+ dy = point1.y - point2.y;
399
+ return Math.sqrt(dx*dx + dy*dy);
400
+ },
401
+
402
+ _getBrickPoint : function($brick){
403
+ var offset = $brick.offset();
404
+ return {
405
+ $brick: $brick,
406
+ x : offset.left + ($brick.outerWidth()/2),
407
+ y : offset.top + ($brick.outerHeight()/2)
408
+ };
409
+ },
410
+
411
+ _getClosestBrick : function(brick){
412
+
413
+ var $brick = $(brick),
414
+ _this = this,
415
+ dPoint = this._getBrickPoint($brick),
416
+ closest = null,
417
+ point, dist, $b, last, midpoint;
418
+
419
+ // find the index of the closest brick
420
+ this.$bricks.each(function(i, b){
421
+
422
+ $b = $(b);
423
+ point = _this._getBrickPoint($b);
424
+
425
+ // first check the distance from the brick center
426
+ dist = _this._getDistanceBetween(dPoint, point);
427
+
428
+ if(closest === null || dist < closest.dist){
429
+ closest = {
430
+ dist: dist,
431
+ $brick: $b,
432
+ index: (point.x > dPoint.x) ? i : i+1
433
+ };
434
+ }
435
+
436
+ // get the distance between the center of the line
437
+ // connecting the current and last block
438
+ if(typeof last !== 'undefined'){
439
+ midpoint = {
440
+ x : (last.x + point.x) / 2,
441
+ y : (last.y + point.y) / 2
442
+ };
443
+ dist = _this._getDistanceBetween(dPoint, midpoint);
444
+ if(dist < closest.dist){
445
+ closest = {
446
+ dist: dist,
447
+ $brick: $b,
448
+ index: (point.x > dPoint.x) ? i : i+1
449
+ };
450
+ }
451
+ }
452
+
453
+ last = point;
454
+
455
+ });
456
+
457
+ return closest;
458
+ },
459
+
460
+ _createGhostBrick : function($brick){
461
+
462
+ var closest = this._getClosestBrick($brick),
463
+ $ghost = $brick.clone().html('').addClass('drag-ghost'),
464
+ pos = $brick.position();
465
+
466
+ $ghost.css({
467
+ width : $brick.width(),
468
+ height : $brick.height()
469
+ });
470
+
471
+ this.$bricks.splice(closest.index, 0, $ghost[0]);
472
+ this.element.append($ghost);
473
+ this._reLayout();
474
+
475
+ return closest.index;
476
+ },
477
+
478
+ _initDrag : function($bricks){
479
+
480
+ var _this = this,
481
+ dragged = null,
482
+ pos, closest,
483
+ ghostInterval = null,
484
+ ghostIndex = -1,
485
+ ghostTime, ghostReset;
486
+
487
+ $bricks.bind('dragstart', function(e) {
488
+
489
+ // make sure we're dragging by the right thing
490
+ if(_this.options.dragHandleSelector !== null
491
+ && !$(e.target).is(_this.options.dragHandleSelector)){
492
+ return false;
493
+ }
494
+
495
+ pos = $(this).position();
496
+
497
+ // add the dragClass
498
+ if(_this.options.dragClass !== null){
499
+ $(this).addClass(_this.options.dragClass);
500
+ }
501
+
502
+ // remove the brick being dragged from the array
503
+ dragged = this;
504
+ _this.$bricks = _this.$bricks.not(this);
505
+ _this._reLayout();
506
+
507
+ ghostTime = ghostReset = new Date().getTime();
508
+
509
+ ghostInterval = setInterval(function(){
510
+ if(ghostReset !== ghostTime){
511
+ if(ghostIndex !== -1){
512
+
513
+ // this kinda sucks because it doesn't use the original
514
+ // position of the bricks
515
+ if(_this._getClosestBrick($(dragged)).index === ghostIndex){
516
+ return;
517
+ }
518
+
519
+ $(_this.$bricks.splice(ghostIndex,1)).fadeOut(function(){
520
+ $(this).remove();
521
+ });
522
+ ghostIndex = -1;
523
+ _this._reLayout();
524
+ }
525
+ if(new Date().getTime() - ghostReset > 200){
526
+ ghostIndex = _this._createGhostBrick($(dragged));
527
+ ghostTime = ghostReset;
528
+ }
529
+ }
530
+ }, 100);
531
+
532
+ }).bind('drag', function(e, dd) {
533
+
534
+ $(this).css({
535
+ top : pos.top + dd.deltaY,
536
+ left : pos.left + dd.deltaX
537
+ });
538
+
539
+ ghostReset = new Date().getTime();
540
+
541
+ }).bind('dragend', function(e) {
542
+
543
+ // clear the ghosting interval
544
+ clearInterval(ghostInterval);
545
+
546
+ // remove the dragClass
547
+ if(_this.options.dragClass !== null){
548
+ $(this).removeClass(_this.options.dragClass);
549
+ }
550
+
551
+ // insert the brick back into the array
552
+ if(ghostIndex !== -1) {
553
+ var $ghost = $(_this.$bricks[ghostIndex]);
554
+ $ghost.fadeOut(function(){
555
+ $ghost.remove();
556
+ });
557
+ _this.$bricks[ghostIndex] = dragged;
558
+ ghostIndex = -1;
559
+ } else {
560
+ closest = _this._getClosestBrick(dragged);
561
+ _this.$bricks.splice(closest.index , 0, dragged);
562
+ }
563
+ dragged = null;
564
+
565
+
566
+ _this._reLayout();
567
+ });
568
+ }
569
+
570
+ };
571
+
572
+
573
+ // ======================= imagesLoaded Plugin ===============================
574
+ /*!
575
+ * jQuery imagesLoaded plugin v1.1.0
576
+ * http://github.com/desandro/imagesloaded
577
+ *
578
+ * MIT License. by Paul Irish et al.
579
+ */
580
+
581
+
582
+ // $('#my-container').imagesLoaded(myFunction)
583
+ // or
584
+ // $('img').imagesLoaded(myFunction)
585
+
586
+ // execute a callback when all images have loaded.
587
+ // needed because .load() doesn't work on cached images
588
+
589
+ // callback function gets image collection as argument
590
+ // `this` is the container
591
+
592
+ $.fn.imagesLoaded = function( callback ) {
593
+ var $this = this,
594
+ $images = $this.find('img').add( $this.filter('img') ),
595
+ len = $images.length,
596
+ blank = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==',
597
+ loaded = [];
598
+
599
+ function triggerCallback() {
600
+ callback.call( $this, $images );
601
+ }
602
+
603
+ function imgLoaded( event ) {
604
+ var img = event.target;
605
+ if ( img.src !== blank && $.inArray( img, loaded ) === -1 ){
606
+ loaded.push( img );
607
+ if ( --len <= 0 ){
608
+ setTimeout( triggerCallback );
609
+ $images.unbind( '.imagesLoaded', imgLoaded );
610
+ }
611
+ }
612
+ }
613
+
614
+ // if no images, trigger immediately
615
+ if ( !len ) {
616
+ triggerCallback();
617
+ }
618
+
619
+ $images.bind( 'load.imagesLoaded error.imagesLoaded', imgLoaded ).each( function() {
620
+ // cached images don't fire load sometimes, so we reset src.
621
+ var src = this.src;
622
+ // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
623
+ // data uri bypasses webkit log warning (thx doug jones)
624
+ this.src = blank;
625
+ this.src = src;
626
+ });
627
+
628
+ return $this;
629
+ };
630
+
631
+
632
+ // helper function for logging errors
633
+ // $.error breaks jQuery chaining
634
+ var logError = function( message ) {
635
+ if ( window.console ) {
636
+ window.console.error( message );
637
+ }
638
+ };
639
+
640
+ // ======================= Plugin bridge ===============================
641
+ // leverages data method to either create or return $.Mason constructor
642
+ // A bit from jQuery UI
643
+ // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js
644
+ // A bit from jcarousel
645
+ // https://github.com/jsor/jcarousel/blob/master/lib/jquery.jcarousel.js
646
+
647
+ $.fn.masonry = function( options ) {
648
+ if ( typeof options === 'string' ) {
649
+ // call method
650
+ var args = Array.prototype.slice.call( arguments, 1 );
651
+
652
+ this.each(function(){
653
+ var instance = $.data( this, 'masonry' );
654
+ if ( !instance ) {
655
+ logError( "cannot call methods on masonry prior to initialization; " +
656
+ "attempted to call method '" + options + "'" );
657
+ return;
658
+ }
659
+ if ( !$.isFunction( instance[options] ) || options.charAt(0) === "_" ) {
660
+ logError( "no such method '" + options + "' for masonry instance" );
661
+ return;
662
+ }
663
+ // apply method
664
+ instance[ options ].apply( instance, args );
665
+ });
666
+ } else {
667
+ this.each(function() {
668
+ var instance = $.data( this, 'masonry' );
669
+ if ( instance ) {
670
+ // apply options & init
671
+ instance.option( options || {} );
672
+ instance._init();
673
+ } else {
674
+ // initialize new instance
675
+ $.data( this, 'masonry', new $.Mason( options, this ) );
676
+ }
677
+ });
678
+ }
679
+ return this;
680
+ };
681
+
682
+ })( window, jQuery );