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