847f84843324eb2fec7d57d73d79770e4db6b3cc
[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 rowclass
302          * Fires when a row is rendered, so you can change add a style to it.
303          * @param {GridView} gridview   The grid view
304          * @param {Object} rowcfg   contains record  rowIndex and rowClass - set rowClass to add a style.
305          */
306         'rowclass' : true,
307
308         /**
309          * @event render
310          * Fires when the grid is rendered
311          * @param {Grid} grid
312          */
313         'render' : true
314     });
315
316     Roo.grid.Grid.superclass.constructor.call(this);
317 };
318 Roo.extend(Roo.grid.Grid, Roo.util.Observable, {
319     
320     /**
321          * @cfg {Roo.grid.AbstractSelectionModel} sm The selection Model (default = Roo.grid.RowSelectionModel)
322          */
323         /**
324          * @cfg {Roo.grid.GridView} view  The view that renders the grid (default = Roo.grid.GridView)
325          */
326         /**
327          * @cfg {Roo.grid.ColumnModel} cm[] The columns of the grid
328          */
329         /**
330          * @cfg {Roo.data.Store} ds The data store for the grid
331          */
332         /**
333          * @cfg {Roo.Toolbar} toolbar a toolbar for buttons etc.
334          */
335                 /**
336          * @cfg {Roo.PagingToolbar} footer the paging toolbar
337          */
338         
339         /**
340      * @cfg {String} ddGroup - drag drop group.
341      */
342       /**
343      * @cfg {String} dragGroup - drag group (?? not sure if needed.)
344      */
345
346     /**
347      * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Default is 25.
348      */
349     minColumnWidth : 25,
350
351     /**
352      * @cfg {Boolean} autoSizeColumns True to automatically resize the columns to fit their content
353      * <b>on initial render.</b> It is more efficient to explicitly size the columns
354      * through the ColumnModel's {@link Roo.grid.ColumnModel#width} config option.  Default is false.
355      */
356     autoSizeColumns : false,
357
358     /**
359      * @cfg {Boolean} autoSizeHeaders True to measure headers with column data when auto sizing columns. Default is true.
360      */
361     autoSizeHeaders : true,
362
363     /**
364      * @cfg {Boolean} monitorWindowResize True to autoSize the grid when the window resizes. Default is true.
365      */
366     monitorWindowResize : true,
367
368     /**
369      * @cfg {Boolean} maxRowsToMeasure If autoSizeColumns is on, maxRowsToMeasure can be used to limit the number of
370      * rows measured to get a columns size. Default is 0 (all rows).
371      */
372     maxRowsToMeasure : 0,
373
374     /**
375      * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is true.
376      */
377     trackMouseOver : true,
378
379     /**
380     * @cfg {Boolean} enableDrag  True to enable drag of rows. Default is false. (double check if this is needed?)
381     */
382       /**
383     * @cfg {Boolean} enableDrop  True to enable drop of elements. Default is false. (double check if this is needed?)
384     */
385     
386     /**
387     * @cfg {Boolean} enableDragDrop True to enable drag and drop of rows. Default is false.
388     */
389     enableDragDrop : false,
390     
391     /**
392     * @cfg {Boolean} enableColumnMove True to enable drag and drop reorder of columns. Default is true.
393     */
394     enableColumnMove : true,
395     
396     /**
397     * @cfg {Boolean} enableColumnHide True to enable hiding of columns with the header context menu. Default is true.
398     */
399     enableColumnHide : true,
400     
401     /**
402     * @cfg {Boolean} enableRowHeightSync True to manually sync row heights across locked and not locked rows. Default is false.
403     */
404     enableRowHeightSync : false,
405     
406     /**
407     * @cfg {Boolean} stripeRows True to stripe the rows.  Default is true.
408     */
409     stripeRows : true,
410     
411     /**
412     * @cfg {Boolean} autoHeight True to fit the height of the grid container to the height of the data. Default is false.
413     */
414     autoHeight : false,
415
416     /**
417      * @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.
418      */
419     autoExpandColumn : false,
420
421     /**
422     * @cfg {Number} autoExpandMin The minimum width the autoExpandColumn can have (if enabled).
423     * Default is 50.
424     */
425     autoExpandMin : 50,
426
427     /**
428     * @cfg {Number} autoExpandMax The maximum width the autoExpandColumn can have (if enabled). Default is 1000.
429     */
430     autoExpandMax : 1000,
431
432     /**
433     * @cfg {Object} view The {@link Roo.grid.GridView} used by the grid. This can be set before a call to render().
434     */
435     view : null,
436
437     /**
438     * @cfg {Object} loadMask An {@link Roo.LoadMask} config or true to mask the grid while loading. Default is false.
439     */
440     loadMask : false,
441     /**
442     * @cfg {Roo.dd.DropTarget} dropTarget An {@link Roo.dd.DropTarget} config
443     */
444     dropTarget: false,
445      /**
446     * @cfg {boolean} sortColMenu Sort the column order menu when it shows (usefull for long lists..) default false
447     */ 
448     sortColMenu : false,
449     
450     // private
451     rendered : false,
452
453     /**
454     * @cfg {Boolean} autoWidth True to set the grid's width to the default total width of the grid's columns instead
455     * of a fixed width. Default is false.
456     */
457     /**
458     * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if autoHeight is not on.
459     */
460     
461     
462     /**
463     * @cfg {String} ddText Configures the text is the drag proxy (defaults to "%0 selected row(s)").
464     * %0 is replaced with the number of selected rows.
465     */
466     ddText : "{0} selected row{1}",
467     
468     
469     /**
470      * Called once after all setup has been completed and the grid is ready to be rendered.
471      * @return {Roo.grid.Grid} this
472      */
473     render : function()
474     {
475         var c = this.container;
476         // try to detect autoHeight/width mode
477         if((!c.dom.offsetHeight || c.dom.offsetHeight < 20) || c.getStyle("height") == "auto"){
478             this.autoHeight = true;
479         }
480         var view = this.getView();
481         view.init(this);
482
483         c.on("click", this.onClick, this);
484         c.on("dblclick", this.onDblClick, this);
485         c.on("contextmenu", this.onContextMenu, this);
486         c.on("keydown", this.onKeyDown, this);
487         if (Roo.isTouch) {
488             c.on("touchstart", this.onTouchStart, this);
489         }
490
491         this.relayEvents(c, ["mousedown","mouseup","mouseover","mouseout","keypress"]);
492
493         this.getSelectionModel().init(this);
494
495         view.render();
496
497         if(this.loadMask){
498             this.loadMask = new Roo.LoadMask(this.container,
499                     Roo.apply({store:this.dataSource}, this.loadMask));
500         }
501         
502         
503         if (this.toolbar && this.toolbar.xtype) {
504             this.toolbar.container = this.getView().getHeaderPanel(true);
505             this.toolbar = new Roo.Toolbar(this.toolbar);
506         }
507         if (this.footer && this.footer.xtype) {
508             this.footer.dataSource = this.getDataSource();
509             this.footer.container = this.getView().getFooterPanel(true);
510             this.footer = Roo.factory(this.footer, Roo);
511         }
512         if (this.dropTarget && this.dropTarget.xtype) {
513             delete this.dropTarget.xtype;
514             this.dropTarget =  new Roo.dd.DropTarget(this.getView().mainBody, this.dropTarget);
515         }
516         
517         
518         this.rendered = true;
519         this.fireEvent('render', this);
520         return this;
521     },
522
523     /**
524      * Reconfigures the grid to use a different Store and Column Model.
525      * The View will be bound to the new objects and refreshed.
526      * @param {Roo.data.Store} dataSource The new {@link Roo.data.Store} object
527      * @param {Roo.grid.ColumnModel} The new {@link Roo.grid.ColumnModel} object
528      */
529     reconfigure : function(dataSource, colModel){
530         if(this.loadMask){
531             this.loadMask.destroy();
532             this.loadMask = new Roo.LoadMask(this.container,
533                     Roo.apply({store:dataSource}, this.loadMask));
534         }
535         this.view.bind(dataSource, colModel);
536         this.dataSource = dataSource;
537         this.colModel = colModel;
538         this.view.refresh(true);
539     },
540     /**
541      * addColumns
542      * Add's a column, default at the end..
543      
544      * @param {int} position to add (default end)
545      * @param {Array} of objects of column configuration see {@link Roo.grid.ColumnModel} 
546      */
547     addColumns : function(pos, ar)
548     {
549         
550         for (var i =0;i< ar.length;i++) {
551             var cfg = ar[i];
552             cfg.id = typeof(cfg.id) == 'undefined' ? Roo.id() : cfg.id; // don't normally use this..
553             this.cm.lookup[cfg.id] = cfg;
554         }
555         
556         
557         if (typeof(pos) == 'undefined' || pos >= this.cm.config.length) {
558             pos = this.cm.config.length; //this.cm.config.push(cfg);
559         } 
560         pos = Math.max(0,pos);
561         ar.unshift(0);
562         ar.unshift(pos);
563         this.cm.config.splice.apply(this.cm.config, ar);
564         
565         
566         
567         this.view.generateRules(this.cm);
568         this.view.refresh(true);
569         
570     },
571     
572     
573     
574     
575     // private
576     onKeyDown : function(e){
577         this.fireEvent("keydown", e);
578     },
579
580     /**
581      * Destroy this grid.
582      * @param {Boolean} removeEl True to remove the element
583      */
584     destroy : function(removeEl, keepListeners){
585         if(this.loadMask){
586             this.loadMask.destroy();
587         }
588         var c = this.container;
589         c.removeAllListeners();
590         this.view.destroy();
591         this.colModel.purgeListeners();
592         if(!keepListeners){
593             this.purgeListeners();
594         }
595         c.update("");
596         if(removeEl === true){
597             c.remove();
598         }
599     },
600
601     // private
602     processEvent : function(name, e){
603         // does this fire select???
604         //Roo.log('grid:processEvent '  + name);
605         
606         if (name != 'touchstart' ) {
607             this.fireEvent(name, e);    
608         }
609         
610         var t = e.getTarget();
611         var v = this.view;
612         var header = v.findHeaderIndex(t);
613         if(header !== false){
614             var ename = name == 'touchstart' ? 'click' : name;
615              
616             this.fireEvent("header" + ename, this, header, e);
617         }else{
618             var row = v.findRowIndex(t);
619             var cell = v.findCellIndex(t);
620             if (name == 'touchstart') {
621                 // first touch is always a click.
622                 // hopefull this happens after selection is updated.?
623                 name = false;
624                 
625                 if (typeof(this.selModel.getSelectedCell) != 'undefined') {
626                     var cs = this.selModel.getSelectedCell();
627                     if (row == cs[0] && cell == cs[1]){
628                         name = 'dblclick';
629                     }
630                 }
631                 if (typeof(this.selModel.getSelections) != 'undefined') {
632                     var cs = this.selModel.getSelections();
633                     var ds = this.dataSource;
634                     if (cs.length == 1 && ds.getAt(row) == cs[0]){
635                         name = 'dblclick';
636                     }
637                 }
638                 if (!name) {
639                     return;
640                 }
641             }
642             
643             
644             if(row !== false){
645                 this.fireEvent("row" + name, this, row, e);
646                 if(cell !== false){
647                     this.fireEvent("cell" + name, this, row, cell, e);
648                 }
649             }
650         }
651     },
652
653     // private
654     onClick : function(e){
655         this.processEvent("click", e);
656     },
657    // private
658     onTouchStart : function(e){
659         this.processEvent("touchstart", e);
660     },
661
662     // private
663     onContextMenu : function(e, t){
664         this.processEvent("contextmenu", e);
665     },
666
667     // private
668     onDblClick : function(e){
669         this.processEvent("dblclick", e);
670     },
671
672     // private
673     walkCells : function(row, col, step, fn, scope){
674         var cm = this.colModel, clen = cm.getColumnCount();
675         var ds = this.dataSource, rlen = ds.getCount(), first = true;
676         if(step < 0){
677             if(col < 0){
678                 row--;
679                 first = false;
680             }
681             while(row >= 0){
682                 if(!first){
683                     col = clen-1;
684                 }
685                 first = false;
686                 while(col >= 0){
687                     if(fn.call(scope || this, row, col, cm) === true){
688                         return [row, col];
689                     }
690                     col--;
691                 }
692                 row--;
693             }
694         } else {
695             if(col >= clen){
696                 row++;
697                 first = false;
698             }
699             while(row < rlen){
700                 if(!first){
701                     col = 0;
702                 }
703                 first = false;
704                 while(col < clen){
705                     if(fn.call(scope || this, row, col, cm) === true){
706                         return [row, col];
707                     }
708                     col++;
709                 }
710                 row++;
711             }
712         }
713         return null;
714     },
715
716     // private
717     getSelections : function(){
718         return this.selModel.getSelections();
719     },
720
721     /**
722      * Causes the grid to manually recalculate its dimensions. Generally this is done automatically,
723      * but if manual update is required this method will initiate it.
724      */
725     autoSize : function(){
726         if(this.rendered){
727             this.view.layout();
728             if(this.view.adjustForScroll){
729                 this.view.adjustForScroll();
730             }
731         }
732     },
733
734     /**
735      * Returns the grid's underlying element.
736      * @return {Element} The element
737      */
738     getGridEl : function(){
739         return this.container;
740     },
741
742     // private for compatibility, overridden by editor grid
743     stopEditing : function(){},
744
745     /**
746      * Returns the grid's SelectionModel.
747      * @return {SelectionModel}
748      */
749     getSelectionModel : function(){
750         if(!this.selModel){
751             this.selModel = new Roo.grid.RowSelectionModel();
752         }
753         return this.selModel;
754     },
755
756     /**
757      * Returns the grid's DataSource.
758      * @return {DataSource}
759      */
760     getDataSource : function(){
761         return this.dataSource;
762     },
763
764     /**
765      * Returns the grid's ColumnModel.
766      * @return {ColumnModel}
767      */
768     getColumnModel : function(){
769         return this.colModel;
770     },
771
772     /**
773      * Returns the grid's GridView object.
774      * @return {GridView}
775      */
776     getView : function(){
777         if(!this.view){
778             this.view = new Roo.grid.GridView(this.viewConfig);
779             this.relayEvents(this.view, [
780                 "beforerowremoved", "beforerowsinserted",
781                 "beforerefresh", "rowremoved",
782                 "rowsinserted", "rowupdated" ,"refresh"
783             ]);
784         }
785         return this.view;
786     },
787     /**
788      * Called to get grid's drag proxy text, by default returns this.ddText.
789      * Override this to put something different in the dragged text.
790      * @return {String}
791      */
792     getDragDropText : function(){
793         var count = this.selModel.getCount();
794         return String.format(this.ddText, count, count == 1 ? '' : 's');
795     }
796 });