Roo/bootstrap/LayoutMasonryAuto.js
[roojs1] / Roo / bootstrap / LayoutMasonryAuto.js
1 /**
2  *
3  * This is based on 
4  * http://masonry.desandro.com
5  *
6  * The idea is to render all the bricks based on vertical width...
7  *
8  * The original code extends 'outlayer' - we might need to use that....
9  * 
10  */
11
12
13 /**
14  * @class Roo.bootstrap.LayoutMasonryAuto
15  * @extends Roo.bootstrap.Component
16  * Bootstrap Layout Masonry class
17  * 
18  * @constructor
19  * Create a new Element
20  * @param {Object} config The config object
21  */
22
23 Roo.bootstrap.LayoutMasonryAuto = function(config){
24     Roo.bootstrap.LayoutMasonryAuto.superclass.constructor.call(this, config);
25 };
26
27 Roo.extend(Roo.bootstrap.LayoutMasonryAuto, Roo.bootstrap.Component,  {
28     
29       /**
30      * @cfg {Boolean} isFitWidth  - resize the width..
31      */   
32     isFitWidth : false,  // options..
33     /**
34      * @cfg {Boolean} isOriginLeft = left align?
35      */   
36     isOriginLeft : true,
37     /**
38      * @cfg {Boolean} isOriginTop = top align?
39      */   
40     isOriginTop : false,
41     /**
42      * @cfg {Boolean} isLayoutInstant = no animation?
43      */   
44     isLayoutInstant : false, // needed?
45     /**
46      * @cfg {Boolean} isResizingContainer = not sure if this is used..
47      */   
48     isResizingContainer : true,
49     /**
50      * @cfg {Number} columnWidth  width of the columns 
51      */   
52     
53     columnWidth : 0,
54     /**
55      * @cfg {Number} padHeight padding below box..
56      */   
57     
58     padHeight : 10, 
59     
60     /**
61      * @cfg {Boolean} isAutoInitial defalut true
62      */   
63     
64     isAutoInitial : true, 
65     
66     // private?
67     gutter : 0,
68     
69     containerWidth: 0,
70     initialColumnWidth : 0,
71     currentSize : null,
72     
73     colYs : null, // array.
74     maxY : 0,
75     padWidth: 10,
76     
77     
78     tag: 'div',
79     cls: '',
80     bricks: null, //CompositeElement
81     cols : 0, // array?
82     // element : null, // wrapped now this.el
83     _isLayoutInited : null, 
84     
85     
86     getAutoCreate : function(){
87         
88         var cfg = {
89             tag: this.tag,
90             cls: 'blog-masonary-wrapper ' + this.cls,
91             cn : {
92                 cls : 'mas-boxes masonary'
93             }
94         };
95         
96         return cfg;
97     },
98     
99     getChildContainer: function( )
100     {
101         if (this.boxesEl) {
102             return this.boxesEl;
103         }
104         
105         this.boxesEl = this.el.select('.mas-boxes').first();
106         
107         return this.boxesEl;
108     },
109     
110     
111     initEvents : function()
112     {
113         var _this = this;
114         
115         if(this.isAutoInitial){
116             Roo.log('hook children rendered');
117             this.on('childrenrendered', function() {
118                 Roo.log('children rendered');
119                 _this.initial();
120             } ,this);
121         }
122         
123     },
124     
125     initial : function()
126     {
127         this.reloadItems();
128
129         this.currentSize = this.el.getBox(true);
130
131         /// was window resize... - let's see if this works..
132         Roo.EventManager.onWindowResize(this.resize, this); 
133
134         if(!this.isAutoInitial){
135             this.layout();
136             return;
137         }
138         
139         this.layout.defer(500,this);
140     },
141     
142     reloadItems: function()
143     {
144         return;
145         
146         this.bricks = this.el.select('.masonry-brick', true);
147         
148         this.bricks.each(function(b) {
149             //Roo.log(b.getSize());
150             if (!b.attr('originalwidth')) {
151                 b.attr('originalwidth',  b.getSize().width);
152             }
153             
154         });
155         
156         Roo.log(this.bricks.elements.length);
157     },
158     
159     resize : function()
160     {
161         Roo.log('resize');
162         var cs = this.el.getBox(true);
163         
164         if (this.currentSize.width == cs.width && this.currentSize.x == cs.x ) {
165             Roo.log("no change in with or X");
166             return;
167         }
168         this.currentSize = cs;
169         this.layout();
170     },
171     
172     layout : function()
173     {
174          Roo.log('layout');
175         this._resetLayout();
176         //this._manageStamps();
177       
178         // don't animate first layout
179         var isInstant = this.isLayoutInstant !== undefined ? this.isLayoutInstant : !this._isLayoutInited;
180         this.layoutItems( isInstant );
181       
182         // flag for initalized
183         this._isLayoutInited = true;
184     },
185     
186     layoutItems : function( isInstant )
187     {
188         //var items = this._getItemsForLayout( this.items );
189         // original code supports filtering layout items.. we just ignore it..
190         
191         this._layoutItems( this.bricks , isInstant );
192       
193         this._postLayout();
194     },
195     _layoutItems : function ( items , isInstant)
196     {
197        //this.fireEvent( 'layout', this, items );
198     
199
200         if ( !items || !items.elements.length ) {
201           // no items, emit event with empty array
202             return;
203         }
204
205         var queue = [];
206         items.each(function(item) {
207             Roo.log("layout item");
208             Roo.log(item);
209             // get x/y object from method
210             var position = this._getItemLayoutPosition( item );
211             // enqueue
212             position.item = item;
213             position.isInstant = isInstant; // || item.isLayoutInstant; << not set yet...
214             queue.push( position );
215         }, this);
216       
217         this._processLayoutQueue( queue );
218     },
219     /** Sets position of item in DOM
220     * @param {Element} item
221     * @param {Number} x - horizontal position
222     * @param {Number} y - vertical position
223     * @param {Boolean} isInstant - disables transitions
224     */
225     _processLayoutQueue : function( queue )
226     {
227         for ( var i=0, len = queue.length; i < len; i++ ) {
228             var obj = queue[i];
229             obj.item.position('absolute');
230             obj.item.setXY([obj.x,obj.y], obj.isInstant ? false : true);
231         }
232     },
233       
234     
235     /**
236     * Any logic you want to do after each layout,
237     * i.e. size the container
238     */
239     _postLayout : function()
240     {
241         this.resizeContainer();
242     },
243     
244     resizeContainer : function()
245     {
246         if ( !this.isResizingContainer ) {
247             return;
248         }
249         var size = this._getContainerSize();
250         if ( size ) {
251             this.el.setSize(size.width,size.height);
252             this.boxesEl.setSize(size.width,size.height);
253         }
254     },
255     
256     
257     
258     _resetLayout : function()
259     {
260         //this.getSize();  // -- does not really do anything.. it probably applies left/right etc. to obuject but not used
261         this.colWidth = this.el.getWidth();
262         //this.gutter = this.el.getWidth(); 
263         
264         this.measureColumns();
265
266         // reset column Y
267         var i = this.cols;
268         this.colYs = [];
269         while (i--) {
270             this.colYs.push( 0 );
271         }
272     
273         this.maxY = 0;
274     },
275
276     measureColumns : function()
277     {
278         this.getContainerWidth();
279       // if columnWidth is 0, default to outerWidth of first item
280         if ( !this.columnWidth ) {
281             var firstItem = this.bricks.first();
282             Roo.log(firstItem);
283             this.columnWidth  = this.containerWidth;
284             if (firstItem && firstItem.attr('originalwidth') ) {
285                 this.columnWidth = 1* (firstItem.attr('originalwidth') || firstItem.getWidth());
286             }
287             // columnWidth fall back to item of first element
288             Roo.log("set column width?");
289                         this.initialColumnWidth = this.columnWidth  ;
290
291             // if first elem has no width, default to size of container
292             
293         }
294         
295         
296         if (this.initialColumnWidth) {
297             this.columnWidth = this.initialColumnWidth;
298         }
299         
300         
301             
302         // column width is fixed at the top - however if container width get's smaller we should
303         // reduce it...
304         
305         // this bit calcs how man columns..
306             
307         var columnWidth = this.columnWidth += this.gutter;
308       
309         // calculate columns
310         var containerWidth = this.containerWidth + this.gutter;
311         
312         var cols = (containerWidth - this.padWidth) / (columnWidth - this.padWidth);
313         // fix rounding errors, typically with gutters
314         var excess = columnWidth - containerWidth % columnWidth;
315         
316         
317         // if overshoot is less than a pixel, round up, otherwise floor it
318         var mathMethod = excess && excess < 1 ? 'round' : 'floor';
319         cols = Math[ mathMethod ]( cols );
320         this.cols = Math.max( cols, 1 );
321         
322         
323          // padding positioning..
324         var totalColWidth = this.cols * this.columnWidth;
325         var padavail = this.containerWidth - totalColWidth;
326         // so for 2 columns - we need 3 'pads'
327         
328         var padNeeded = (1+this.cols) * this.padWidth;
329         
330         var padExtra = Math.floor((padavail - padNeeded) / this.cols);
331         
332         this.columnWidth += padExtra
333         //this.padWidth = Math.floor(padavail /  ( this.cols));
334         
335         // adjust colum width so that padding is fixed??
336         
337         // we have 3 columns ... total = width * 3
338         // we have X left over... that should be used by 
339         
340         //if (this.expandC) {
341             
342         //}
343         
344         
345         
346     },
347     
348     getContainerWidth : function()
349     {
350        /* // container is parent if fit width
351         var container = this.isFitWidth ? this.element.parentNode : this.element;
352         // check that this.size and size are there
353         // IE8 triggers resize on body size change, so they might not be
354         
355         var size = getSize( container );  //FIXME
356         this.containerWidth = size && size.innerWidth; //FIXME
357         */
358          
359         this.containerWidth = this.el.getBox(true).width;  //maybe use getComputedWidth
360         
361     },
362     
363     _getItemLayoutPosition : function( item )  // what is item?
364     {
365         // we resize the item to our columnWidth..
366       
367         item.setWidth(this.columnWidth);
368         item.autoBoxAdjust  = false;
369         
370         var sz = item.getSize();
371  
372         // how many columns does this brick span
373         var remainder = this.containerWidth % this.columnWidth;
374         
375         var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
376         // round if off by 1 pixel, otherwise use ceil
377         var colSpan = Math[ mathMethod ]( sz.width  / this.columnWidth );
378         colSpan = Math.min( colSpan, this.cols );
379         
380         // normally this should be '1' as we dont' currently allow multi width columns..
381         
382         var colGroup = this._getColGroup( colSpan );
383         // get the minimum Y value from the columns
384         var minimumY = Math.min.apply( Math, colGroup );
385         Roo.log([ 'setHeight',  minimumY, sz.height, setHeight ]);
386         
387         var shortColIndex = colGroup.indexOf(  minimumY ); // broken on ie8..?? probably...
388          
389         // position the brick
390         var position = {
391             x: this.currentSize.x + (this.padWidth /2) + ((this.columnWidth + this.padWidth )* shortColIndex),
392             y: this.currentSize.y + minimumY + this.padHeight
393         };
394         
395         Roo.log(position);
396         // apply setHeight to necessary columns
397         var setHeight = minimumY + sz.height + this.padHeight;
398         //Roo.log([ 'setHeight',  minimumY, sz.height, setHeight ]);
399         
400         var setSpan = this.cols + 1 - colGroup.length;
401         for ( var i = 0; i < setSpan; i++ ) {
402           this.colYs[ shortColIndex + i ] = setHeight ;
403         }
404       
405         return position;
406     },
407     
408     /**
409      * @param {Number} colSpan - number of columns the element spans
410      * @returns {Array} colGroup
411      */
412     _getColGroup : function( colSpan )
413     {
414         if ( colSpan < 2 ) {
415           // if brick spans only one column, use all the column Ys
416           return this.colYs;
417         }
418       
419         var colGroup = [];
420         // how many different places could this brick fit horizontally
421         var groupCount = this.cols + 1 - colSpan;
422         // for each group potential horizontal position
423         for ( var i = 0; i < groupCount; i++ ) {
424           // make an array of colY values for that one group
425           var groupColYs = this.colYs.slice( i, i + colSpan );
426           // and get the max value of the array
427           colGroup[i] = Math.max.apply( Math, groupColYs );
428         }
429         return colGroup;
430     },
431     /*
432     _manageStamp : function( stamp )
433     {
434         var stampSize =  stamp.getSize();
435         var offset = stamp.getBox();
436         // get the columns that this stamp affects
437         var firstX = this.isOriginLeft ? offset.x : offset.right;
438         var lastX = firstX + stampSize.width;
439         var firstCol = Math.floor( firstX / this.columnWidth );
440         firstCol = Math.max( 0, firstCol );
441         
442         var lastCol = Math.floor( lastX / this.columnWidth );
443         // lastCol should not go over if multiple of columnWidth #425
444         lastCol -= lastX % this.columnWidth ? 0 : 1;
445         lastCol = Math.min( this.cols - 1, lastCol );
446         
447         // set colYs to bottom of the stamp
448         var stampMaxY = ( this.isOriginTop ? offset.y : offset.bottom ) +
449             stampSize.height;
450             
451         for ( var i = firstCol; i <= lastCol; i++ ) {
452           this.colYs[i] = Math.max( stampMaxY, this.colYs[i] );
453         }
454     },
455     */
456     
457     _getContainerSize : function()
458     {
459         this.maxY = Math.max.apply( Math, this.colYs );
460         var size = {
461             height: this.maxY
462         };
463       
464         if ( this.isFitWidth ) {
465             size.width = this._getContainerFitWidth();
466         }
467       
468         return size;
469     },
470     
471     _getContainerFitWidth : function()
472     {
473         var unusedCols = 0;
474         // count unused columns
475         var i = this.cols;
476         while ( --i ) {
477           if ( this.colYs[i] !== 0 ) {
478             break;
479           }
480           unusedCols++;
481         }
482         // fit container to columns that have been used
483         return ( this.cols - unusedCols ) * this.columnWidth - this.gutter;
484     },
485     
486     needsResizeLayout : function()
487     {
488         var previousWidth = this.containerWidth;
489         this.getContainerWidth();
490         return previousWidth !== this.containerWidth;
491     }
492  
493 });
494
495  
496
497