Roo/grid/Grid.js
[roojs1] / Roo / grid / Grid.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11  
12 /**
13  * @class Roo.grid.Grid
14  * @extends Roo.util.Observable
15  * This class represents the primary interface of a component based grid control.
16  * <br><br>Usage:<pre><code>
17  var grid = new Roo.grid.Grid("my-container-id", {
18      ds: myDataStore,
19      cm: myColModel,
20      selModel: mySelectionModel,
21      autoSizeColumns: true,
22      monitorWindowResize: false,
23      trackMouseOver: true
24  });
25  // set any options
26  grid.render();
27  * </code></pre>
28  * <b>Common Problems:</b><br/>
29  * - Grid does not resize properly when going smaller: Setting overflow hidden on the container
30  * element will correct this<br/>
31  * - If you get el.style[camel]= NaNpx or -2px or something related, be certain you have given your container element
32  * dimensions. The grid adapts to your container's size, if your container has no size defined then the results
33  * are unpredictable.<br/>
34  * - Do not render the grid into an element with display:none. Try using visibility:hidden. Otherwise there is no way for the
35  * grid to calculate dimensions/offsets.<br/>
36   * @constructor
37  * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
38  * The container MUST have some type of size defined for the grid to fill. The container will be
39  * automatically set to position relative if it isn't already.
40  * @param {Object} config A config object that sets properties on this grid.
41  */
42 Roo.grid.Grid = function(container, config){
43         // initialize the container
44         this.container = Roo.get(container);
45         this.container.update("");
46         this.container.setStyle("overflow", "hidden");
47     this.container.addClass('x-grid-container');
48
49     this.id = this.container.id;
50
51     Roo.apply(this, config);
52     // check and correct shorthanded configs
53     if(this.ds){
54         this.dataSource = this.ds;
55         delete this.ds;
56     }
57     if(this.cm){
58         this.colModel = this.cm;
59         delete this.cm;
60     }
61     if(this.sm){
62         this.selModel = this.sm;
63         delete this.sm;
64     }
65
66     if (this.selModel) {
67         this.selModel = Roo.factory(this.selModel, Roo.grid);
68         this.sm = this.selModel;
69         this.sm.xmodule = this.xmodule || false;
70     }
71     if (typeof(this.colModel.config) == 'undefined') {
72         this.colModel = new Roo.grid.ColumnModel(this.colModel);
73         this.cm = this.colModel;
74         this.cm.xmodule = this.xmodule || false;
75     }
76     if (this.dataSource) {
77         this.dataSource= Roo.factory(this.dataSource, Roo.data);
78         this.ds = this.dataSource;
79         this.ds.xmodule = this.xmodule || false;
80         
81     }
82     
83     
84     
85     if(this.width){
86         this.container.setWidth(this.width);
87     }
88
89     if(this.height){
90         this.container.setHeight(this.height);
91     }
92     /** @private */
93         this.addEvents({
94             // raw events
95             /**
96              * @event click
97              * The raw click event for the entire grid.
98              * @param {Roo.EventObject} e
99              */
100             "click" : true,
101             /**
102              * @event dblclick
103              * The raw dblclick event for the entire grid.
104              * @param {Roo.EventObject} e
105              */
106             "dblclick" : true,
107             /**
108              * @event contextmenu
109              * The raw contextmenu event for the entire grid.
110              * @param {Roo.EventObject} e
111              */
112             "contextmenu" : true,
113             /**
114              * @event mousedown
115              * The raw mousedown event for the entire grid.
116              * @param {Roo.EventObject} e
117              */
118             "mousedown" : true,
119             /**
120              * @event mouseup
121              * The raw mouseup event for the entire grid.
122              * @param {Roo.EventObject} e
123              */
124             "mouseup" : true,
125             /**
126              * @event mouseover
127              * The raw mouseover event for the entire grid.
128              * @param {Roo.EventObject} e
129              */
130             "mouseover" : true,
131             /**
132              * @event mouseout
133              * The raw mouseout event for the entire grid.
134              * @param {Roo.EventObject} e
135              */
136             "mouseout" : true,
137             /**
138              * @event keypress
139              * The raw keypress event for the entire grid.
140              * @param {Roo.EventObject} e
141              */
142             "keypress" : true,
143             /**
144              * @event keydown
145              * The raw keydown event for the entire grid.
146              * @param {Roo.EventObject} e
147              */
148             "keydown" : true,
149
150             // custom events
151
152             /**
153              * @event cellclick
154              * Fires when a cell is clicked
155              * @param {Grid} this
156              * @param {Number} rowIndex
157              * @param {Number} columnIndex
158              * @param {Roo.EventObject} e
159              */
160             "cellclick" : true,
161             /**
162              * @event celldblclick
163              * Fires when a cell is double clicked
164              * @param {Grid} this
165              * @param {Number} rowIndex
166              * @param {Number} columnIndex
167              * @param {Roo.EventObject} e
168              */
169             "celldblclick" : true,
170             /**
171              * @event rowclick
172              * Fires when a row is clicked
173              * @param {Grid} this
174              * @param {Number} rowIndex
175              * @param {Roo.EventObject} e
176              */
177             "rowclick" : true,
178             /**
179              * @event rowdblclick
180              * Fires when a row is double clicked
181              * @param {Grid} this
182              * @param {Number} rowIndex
183              * @param {Roo.EventObject} e
184              */
185             "rowdblclick" : true,
186             /**
187              * @event headerclick
188              * Fires when a header is clicked
189              * @param {Grid} this
190              * @param {Number} columnIndex
191              * @param {Roo.EventObject} e
192              */
193             "headerclick" : true,
194             /**
195              * @event headerdblclick
196              * Fires when a header cell is double clicked
197              * @param {Grid} this
198              * @param {Number} columnIndex
199              * @param {Roo.EventObject} e
200              */
201             "headerdblclick" : true,
202             /**
203              * @event rowcontextmenu
204              * Fires when a row is right clicked
205              * @param {Grid} this
206              * @param {Number} rowIndex
207              * @param {Roo.EventObject} e
208              */
209             "rowcontextmenu" : true,
210             /**
211          * @event cellcontextmenu
212          * Fires when a cell is right clicked
213          * @param {Grid} this
214          * @param {Number} rowIndex
215          * @param {Number} cellIndex
216          * @param {Roo.EventObject} e
217          */
218          "cellcontextmenu" : true,
219             /**
220              * @event headercontextmenu
221              * Fires when a header is right clicked
222              * @param {Grid} this
223              * @param {Number} columnIndex
224              * @param {Roo.EventObject} e
225              */
226             "headercontextmenu" : true,
227             /**
228              * @event bodyscroll
229              * Fires when the body element is scrolled
230              * @param {Number} scrollLeft
231              * @param {Number} scrollTop
232              */
233             "bodyscroll" : true,
234             /**
235              * @event columnresize
236              * Fires when the user resizes a column
237              * @param {Number} columnIndex
238              * @param {Number} newSize
239              */
240             "columnresize" : true,
241             /**
242              * @event columnmove
243              * Fires when the user moves a column
244              * @param {Number} oldIndex
245              * @param {Number} newIndex
246              */
247             "columnmove" : true,
248             /**
249              * @event startdrag
250              * Fires when row(s) start being dragged
251              * @param {Grid} this
252              * @param {Roo.GridDD} dd The drag drop object
253              * @param {event} e The raw browser event
254              */
255             "startdrag" : true,
256             /**
257              * @event enddrag
258              * Fires when a drag operation is complete
259              * @param {Grid} this
260              * @param {Roo.GridDD} dd The drag drop object
261              * @param {event} e The raw browser event
262              */
263             "enddrag" : true,
264             /**
265              * @event dragdrop
266              * Fires when dragged row(s) are dropped on a valid DD target
267              * @param {Grid} this
268              * @param {Roo.GridDD} dd The drag drop object
269              * @param {String} targetId The target drag drop object
270              * @param {event} e The raw browser event
271              */
272             "dragdrop" : true,
273             /**
274              * @event dragover
275              * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
276              * @param {Grid} this
277              * @param {Roo.GridDD} dd The drag drop object
278              * @param {String} targetId The target drag drop object
279              * @param {event} e The raw browser event
280              */
281             "dragover" : true,
282             /**
283              * @event dragenter
284              *  Fires when the dragged row(s) first cross another DD target while being dragged
285              * @param {Grid} this
286              * @param {Roo.GridDD} dd The drag drop object
287              * @param {String} targetId The target drag drop object
288              * @param {event} e The raw browser event
289              */
290             "dragenter" : true,
291             /**
292              * @event dragout
293              * Fires when the dragged row(s) leave another DD target while being dragged
294              * @param {Grid} this
295              * @param {Roo.GridDD} dd The drag drop object
296              * @param {String} targetId The target drag drop object
297              * @param {event} e The raw browser event
298              */
299             "dragout" : true,
300         /**
301          * @event render
302          * Fires when the grid is rendered
303          * @param {Grid} grid
304          */
305         render : true
306     });
307
308     Roo.grid.Grid.superclass.constructor.call(this);
309 };
310 Roo.extend(Roo.grid.Grid, Roo.util.Observable, {
311     
312     /**
313      * @cfg {String} ddGroup - drag drop group.
314          */
315     
316     /**
317      * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Default is 25.
318          */
319         minColumnWidth : 25,
320
321     /**
322          * @cfg {Boolean} autoSizeColumns True to automatically resize the columns to fit their content
323          * <b>on initial render.</b> It is more efficient to explicitly size the columns
324          * through the ColumnModel's {@link Roo.grid.ColumnModel#width} config option.  Default is false.
325          */
326         autoSizeColumns : false,
327
328         /**
329          * @cfg {Boolean} autoSizeHeaders True to measure headers with column data when auto sizing columns. Default is true.
330          */
331         autoSizeHeaders : true,
332
333         /**
334          * @cfg {Boolean} monitorWindowResize True to autoSize the grid when the window resizes. Default is true.
335          */
336         monitorWindowResize : true,
337
338         /**
339          * @cfg {Boolean} maxRowsToMeasure If autoSizeColumns is on, maxRowsToMeasure can be used to limit the number of
340          * rows measured to get a columns size. Default is 0 (all rows).
341          */
342         maxRowsToMeasure : 0,
343
344         /**
345          * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is true.
346          */
347         trackMouseOver : true,
348
349     /**
350          * @cfg {Boolean} enableDrag  True to enable drag of rows. Default is false. (double check if this is needed?)
351          */
352     
353         /**
354          * @cfg {Boolean} enableDragDrop True to enable drag and drop of rows. Default is false.
355          */
356         enableDragDrop : false,
357
358         /**
359          * @cfg {Boolean} enableColumnMove True to enable drag and drop reorder of columns. Default is true.
360          */
361         enableColumnMove : true,
362
363         /**
364          * @cfg {Boolean} enableColumnHide True to enable hiding of columns with the header context menu. Default is true.
365          */
366         enableColumnHide : true,
367
368         /**
369          * @cfg {Boolean} enableRowHeightSync True to manually sync row heights across locked and not locked rows. Default is false.
370          */
371         enableRowHeightSync : false,
372
373         /**
374          * @cfg {Boolean} stripeRows True to stripe the rows.  Default is true.
375          */
376         stripeRows : true,
377
378         /**
379          * @cfg {Boolean} autoHeight True to fit the height of the grid container to the height of the data. Default is false.
380          */
381         autoHeight : false,
382
383     /**
384      * @cfg {String} autoExpandColumn The id (or dataIndex) of a column in this grid that should expand to fill unused space. This id can not be 0. Default is false.
385      */
386     autoExpandColumn : false,
387
388     /**
389     * @cfg {Number} autoExpandMin The minimum width the autoExpandColumn can have (if enabled).
390     * Default is 50.
391     */
392     autoExpandMin : 50,
393
394     /**
395     * @cfg {Number} autoExpandMax The maximum width the autoExpandColumn can have (if enabled). Default is 1000.
396     */
397     autoExpandMax : 1000,
398
399     /**
400          * @cfg {Object} view The {@link Roo.grid.GridView} used by the grid. This can be set before a call to render().
401          */
402         view : null,
403
404         /**
405      * @cfg {Object} loadMask An {@link Roo.LoadMask} config or true to mask the grid while loading. Default is false.
406          */
407         loadMask : false,
408     /**
409      * @cfg {Roo.dd.DropTarget} dragTarget An {@link Roo.dd.DragTarget} config
410          */
411         dropTarget: false,
412     // private
413     rendered : false,
414
415     /**
416     * @cfg {Boolean} autoWidth True to set the grid's width to the default total width of the grid's columns instead
417     * of a fixed width. Default is false.
418     */
419     /**
420     * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if autoHeight is not on.
421     */
422     /**
423      * Called once after all setup has been completed and the grid is ready to be rendered.
424      * @return {Roo.grid.Grid} this
425      */
426     render : function(){
427         var c = this.container;
428         // try to detect autoHeight/width mode
429         if((!c.dom.offsetHeight || c.dom.offsetHeight < 20) || c.getStyle("height") == "auto"){
430             this.autoHeight = true;
431         }
432         var view = this.getView();
433         view.init(this);
434
435         c.on("click", this.onClick, this);
436         c.on("dblclick", this.onDblClick, this);
437         c.on("contextmenu", this.onContextMenu, this);
438         c.on("keydown", this.onKeyDown, this);
439
440         this.relayEvents(c, ["mousedown","mouseup","mouseover","mouseout","keypress"]);
441
442         this.getSelectionModel().init(this);
443
444         view.render();
445
446         if(this.loadMask){
447             this.loadMask = new Roo.LoadMask(this.container,
448                     Roo.apply({store:this.dataSource}, this.loadMask));
449         }
450         
451         
452         if (this.toolbar && this.toolbar.xtype) {
453             this.toolbar.container = this.getView().getHeaderPanel(true);
454             this.toolbar = new Ext.Toolbar(this.toolbar);
455         }
456         if (this.footer && this.footer.xtype) {
457             this.footer.dataSource = this.getDataSource();
458             this.footer.container = this.getView().getFooterPanel(true);
459             this.footer = Roo.factory(this.footer, Roo);
460         }
461         if (this.dropTarget && this.dropTarget.xtype) {
462             
463             delete this.dropTarget.xtype;
464             this.dropTarget =  new Ext.dd.DropTarget(this.getView().mainBody, this.dropTarget);
465         }
466         
467         
468         this.rendered = true;
469         this.fireEvent('render', this);
470         return this;
471     },
472
473         /**
474          * Reconfigures the grid to use a different Store and Column Model.
475          * The View will be bound to the new objects and refreshed.
476          * @param {Roo.data.Store} dataSource The new {@link Roo.data.Store} object
477          * @param {Roo.grid.ColumnModel} The new {@link Roo.grid.ColumnModel} object
478          */
479     reconfigure : function(dataSource, colModel){
480         if(this.loadMask){
481             this.loadMask.destroy();
482             this.loadMask = new Roo.LoadMask(this.container,
483                     Roo.apply({store:dataSource}, this.loadMask));
484         }
485         this.view.bind(dataSource, colModel);
486         this.dataSource = dataSource;
487         this.colModel = colModel;
488         this.view.refresh(true);
489     },
490
491     // private
492     onKeyDown : function(e){
493         this.fireEvent("keydown", e);
494     },
495
496     /**
497      * Destroy this grid.
498      * @param {Boolean} removeEl True to remove the element
499      */
500     destroy : function(removeEl, keepListeners){
501         if(this.loadMask){
502             this.loadMask.destroy();
503         }
504         var c = this.container;
505         c.removeAllListeners();
506         this.view.destroy();
507         this.colModel.purgeListeners();
508         if(!keepListeners){
509             this.purgeListeners();
510         }
511         c.update("");
512         if(removeEl === true){
513             c.remove();
514         }
515     },
516
517     // private
518     processEvent : function(name, e){
519         this.fireEvent(name, e);
520         var t = e.getTarget();
521         var v = this.view;
522         var header = v.findHeaderIndex(t);
523         if(header !== false){
524             this.fireEvent("header" + name, this, header, e);
525         }else{
526             var row = v.findRowIndex(t);
527             var cell = v.findCellIndex(t);
528             if(row !== false){
529                 this.fireEvent("row" + name, this, row, e);
530                 if(cell !== false){
531                     this.fireEvent("cell" + name, this, row, cell, e);
532                 }
533             }
534         }
535     },
536
537     // private
538     onClick : function(e){
539         this.processEvent("click", e);
540     },
541
542     // private
543     onContextMenu : function(e, t){
544         this.processEvent("contextmenu", e);
545     },
546
547     // private
548     onDblClick : function(e){
549         this.processEvent("dblclick", e);
550     },
551
552     // private
553     walkCells : function(row, col, step, fn, scope){
554         var cm = this.colModel, clen = cm.getColumnCount();
555         var ds = this.dataSource, rlen = ds.getCount(), first = true;
556         if(step < 0){
557             if(col < 0){
558                 row--;
559                 first = false;
560             }
561             while(row >= 0){
562                 if(!first){
563                     col = clen-1;
564                 }
565                 first = false;
566                 while(col >= 0){
567                     if(fn.call(scope || this, row, col, cm) === true){
568                         return [row, col];
569                     }
570                     col--;
571                 }
572                 row--;
573             }
574         } else {
575             if(col >= clen){
576                 row++;
577                 first = false;
578             }
579             while(row < rlen){
580                 if(!first){
581                     col = 0;
582                 }
583                 first = false;
584                 while(col < clen){
585                     if(fn.call(scope || this, row, col, cm) === true){
586                         return [row, col];
587                     }
588                     col++;
589                 }
590                 row++;
591             }
592         }
593         return null;
594     },
595
596     // private
597     getSelections : function(){
598         return this.selModel.getSelections();
599     },
600
601     /**
602      * Causes the grid to manually recalculate its dimensions. Generally this is done automatically,
603      * but if manual update is required this method will initiate it.
604      */
605     autoSize : function(){
606         if(this.rendered){
607             this.view.layout();
608             if(this.view.adjustForScroll){
609                 this.view.adjustForScroll();
610             }
611         }
612     },
613
614     /**
615      * Returns the grid's underlying element.
616      * @return {Element} The element
617      */
618     getGridEl : function(){
619         return this.container;
620     },
621
622     // private for compatibility, overridden by editor grid
623     stopEditing : function(){},
624
625     /**
626      * Returns the grid's SelectionModel.
627      * @return {SelectionModel}
628      */
629     getSelectionModel : function(){
630         if(!this.selModel){
631             this.selModel = new Roo.grid.RowSelectionModel();
632         }
633         return this.selModel;
634     },
635
636     /**
637      * Returns the grid's DataSource.
638      * @return {DataSource}
639      */
640     getDataSource : function(){
641         return this.dataSource;
642     },
643
644     /**
645      * Returns the grid's ColumnModel.
646      * @return {ColumnModel}
647      */
648     getColumnModel : function(){
649         return this.colModel;
650     },
651
652     /**
653      * Returns the grid's GridView object.
654      * @return {GridView}
655      */
656     getView : function(){
657         if(!this.view){
658             this.view = new Roo.grid.GridView(this.viewConfig);
659         }
660         return this.view;
661     },
662     /**
663      * Called to get grid's drag proxy text, by default returns this.ddText.
664      * @return {String}
665      */
666     getDragDropText : function(){
667         var count = this.selModel.getCount();
668         return String.format(this.ddText, count, count == 1 ? '' : 's');
669     }
670 });
671 /**
672  * Configures the text is the drag proxy (defaults to "%0 selected row(s)").
673  * %0 is replaced with the number of selected rows.
674  * @type String
675  */
676 Roo.grid.Grid.prototype.ddText = "{0} selected row{1}";