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