Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / docs / source / RowEditor.html
1 <html>\r
2 <head>\r
3   <title>The source code</title>\r
4     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />\r
5     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>\r
6 </head>\r
7 <body  onload="prettyPrint();">\r
8     <pre class="prettyprint lang-js">Ext.ns('Ext.ux.grid');
9
10 <div id="cls-Ext.ux.grid.RowEditor"></div>/**
11  * @class Ext.ux.grid.RowEditor
12  * @extends Ext.Panel 
13  * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
14  * A validation mode may be enabled which uses AnchorTips to notify the user of all
15  * validation errors at once.
16  * 
17  * @ptype roweditor
18  */
19 Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
20     floating: true,
21     shadow: false,
22     layout: 'hbox',
23     cls: 'x-small-editor',
24     buttonAlign: 'center',
25     baseCls: 'x-row-editor',
26     elements: 'header,footer,body',
27     frameWidth: 5,
28     buttonPad: 3,
29     clicksToEdit: 'auto',
30     monitorValid: true,
31     focusDelay: 250,
32     errorSummary: true,
33
34     defaults: {
35         normalWidth: true
36     },
37
38     initComponent: function(){
39         Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
40         this.addEvents(
41             <div id="event-Ext.ux.grid.RowEditor-beforeedit"></div>/**
42              * @event beforeedit
43              * Fired before the row editor is activated.
44              * If the listener returns <tt>false</tt> the editor will not be activated.
45              * @param {Ext.ux.grid.RowEditor} roweditor This object
46              * @param {Number} rowIndex The rowIndex of the row just edited
47              */
48             'beforeedit',
49             <div id="event-Ext.ux.grid.RowEditor-validateedit"></div>/**
50              * @event validateedit
51              * Fired after a row is edited and passes validation.
52              * If the listener returns <tt>false</tt> changes to the record will not be set.
53              * @param {Ext.ux.grid.RowEditor} roweditor This object
54              * @param {Object} changes Object with changes made to the record.
55              * @param {Ext.data.Record} r The Record that was edited.
56              * @param {Number} rowIndex The rowIndex of the row just edited
57              */
58             'validateedit',
59             <div id="event-Ext.ux.grid.RowEditor-afteredit"></div>/**
60              * @event afteredit
61              * Fired after a row is edited and passes validation.  This event is fired
62              * after the store's update event is fired with this edit.
63              * @param {Ext.ux.grid.RowEditor} roweditor This object
64              * @param {Object} changes Object with changes made to the record.
65              * @param {Ext.data.Record} r The Record that was edited.
66              * @param {Number} rowIndex The rowIndex of the row just edited
67              */
68             'afteredit'
69         );
70     },
71
72     init: function(grid){
73         this.grid = grid;
74         this.ownerCt = grid;
75         if(this.clicksToEdit === 2){
76             grid.on('rowdblclick', this.onRowDblClick, this);
77         }else{
78             grid.on('rowclick', this.onRowClick, this);
79             if(Ext.isIE){
80                 grid.on('rowdblclick', this.onRowDblClick, this);
81             }
82         }
83
84         // stopEditing without saving when a record is removed from Store.
85         grid.getStore().on('remove', function() {
86             this.stopEditing(false);
87         },this);
88
89         grid.on({
90             scope: this,
91             keydown: this.onGridKey,
92             columnresize: this.verifyLayout,
93             columnmove: this.refreshFields,
94             reconfigure: this.refreshFields,
95             destroy : this.destroy,
96             bodyscroll: {
97                 buffer: 250,
98                 fn: this.positionButtons
99             }
100         });
101         grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
102         grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
103     },
104
105     refreshFields: function(){
106         this.initFields();
107         this.verifyLayout();
108     },
109
110     isDirty: function(){
111         var dirty;
112         this.items.each(function(f){
113             if(String(this.values[f.id]) !== String(f.getValue())){
114                 dirty = true;
115                 return false;
116             }
117         }, this);
118         return dirty;
119     },
120
121     startEditing: function(rowIndex, doFocus){
122         if(this.editing && this.isDirty()){
123             this.showTooltip('You need to commit or cancel your changes');
124             return;
125         }
126         this.editing = true;
127         if(typeof rowIndex == 'object'){
128             rowIndex = this.grid.getStore().indexOf(rowIndex);
129         }
130         if(this.fireEvent('beforeedit', this, rowIndex) !== false){
131             var g = this.grid, view = g.getView();
132             var row = view.getRow(rowIndex);
133             var record = g.store.getAt(rowIndex);
134             this.record = record;
135             this.rowIndex = rowIndex;
136             this.values = {};
137             if(!this.rendered){
138                 this.render(view.getEditorParent());
139             }
140             var w = Ext.fly(row).getWidth();
141             this.setSize(w);
142             if(!this.initialized){
143                 this.initFields();
144             }
145             var cm = g.getColumnModel(), fields = this.items.items, f, val;
146             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
147                 val = this.preEditValue(record, cm.getDataIndex(i));
148                 f = fields[i];
149                 f.setValue(val);
150                 this.values[f.id] = val || '';
151             }
152             this.verifyLayout(true);
153             if(!this.isVisible()){
154                 this.setPagePosition(Ext.fly(row).getXY());
155             } else{
156                 this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
157             }
158             if(!this.isVisible()){
159                 this.show().doLayout();
160             }
161             if(doFocus !== false){
162                 this.doFocus.defer(this.focusDelay, this);
163             }
164         }
165     },
166
167     stopEditing : function(saveChanges){
168         this.editing = false;
169         if(!this.isVisible()){
170             return;
171         }
172         if(saveChanges === false || !this.isValid()){
173             this.hide();
174             return;
175         }
176         var changes = {}, r = this.record, hasChange = false;
177         var cm = this.grid.colModel, fields = this.items.items;
178         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
179             if(!cm.isHidden(i)){
180                 var dindex = cm.getDataIndex(i);
181                 if(!Ext.isEmpty(dindex)){
182                     var oldValue = r.data[dindex];
183                     var value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
184                     if(String(oldValue) !== String(value)){
185                         changes[dindex] = value;
186                         hasChange = true;
187                     }
188                 }
189             }
190         }
191         if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
192             r.beginEdit();
193             for(var k in changes){
194                 if(changes.hasOwnProperty(k)){
195                     r.set(k, changes[k]);
196                 }
197             }
198             r.endEdit();
199             this.fireEvent('afteredit', this, changes, r, this.rowIndex);
200         }
201         this.hide();
202     },
203
204     verifyLayout: function(force){
205         if(this.el && (this.isVisible() || force === true)){
206             var row = this.grid.getView().getRow(this.rowIndex);
207             this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + (Ext.isBorderBox ? 9 : 0) : undefined);
208             var cm = this.grid.colModel, fields = this.items.items;
209             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
210                 if(!cm.isHidden(i)){
211                     var adjust = 0;
212                     if(i === 0){
213                         adjust += 0; // outer padding
214                     }
215                     if(i === (len - 1)){
216                         adjust += 3; // outer padding
217                     } else{
218                         adjust += 1;
219                     }
220                     fields[i].show();
221                     fields[i].setWidth(cm.getColumnWidth(i) - adjust);
222                 } else{
223                     fields[i].hide();
224                 }
225             }
226             this.doLayout();
227             this.positionButtons();
228         }
229     },
230
231     slideHide : function(){
232         this.hide();
233     },
234
235     initFields: function(){
236         var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
237         this.removeAll(false);
238         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
239             var c = cm.getColumnAt(i);
240             var ed = c.getEditor();
241             if(!ed){
242                 ed = c.displayEditor || new Ext.form.DisplayField();
243             }
244             if(i == 0){
245                 ed.margins = pm('0 1 2 1');
246             } else if(i == len - 1){
247                 ed.margins = pm('0 0 2 1');
248             } else{
249                 ed.margins = pm('0 1 2');
250             }
251             ed.setWidth(cm.getColumnWidth(i));
252             ed.column = c;
253             if(ed.ownerCt !== this){
254                 ed.on('focus', this.ensureVisible, this);
255                 ed.on('specialkey', this.onKey, this);
256             }
257             this.insert(i, ed);
258         }
259         this.initialized = true;
260     },
261
262     onKey: function(f, e){
263         if(e.getKey() === e.ENTER){
264             this.stopEditing(true);
265             e.stopPropagation();
266         }
267     },
268
269     onGridKey: function(e){
270         if(e.getKey() === e.ENTER && !this.isVisible()){
271             var r = this.grid.getSelectionModel().getSelected();
272             if(r){
273                 var index = this.grid.store.indexOf(r);
274                 this.startEditing(index);
275                 e.stopPropagation();
276             }
277         }
278     },
279
280     ensureVisible: function(editor){
281         if(this.isVisible()){
282              this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
283         }
284     },
285
286     onRowClick: function(g, rowIndex, e){
287         if(this.clicksToEdit == 'auto'){
288             var li = this.lastClickIndex;
289             this.lastClickIndex = rowIndex;
290             if(li != rowIndex && !this.isVisible()){
291                 return;
292             }
293         }
294         this.startEditing(rowIndex, false);
295         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
296     },
297
298     onRowDblClick: function(g, rowIndex, e){
299         this.startEditing(rowIndex, false);
300         this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
301     },
302
303     onRender: function(){
304         Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
305         this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
306         this.btns = new Ext.Panel({
307             baseCls: 'x-plain',
308             cls: 'x-btns',
309             elements:'body',
310             layout: 'table',
311             width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
312             items: [{
313                 ref: 'saveBtn',
314                 itemId: 'saveBtn',
315                 xtype: 'button',
316                 text: this.saveText || 'Save',
317                 width: this.minButtonWidth,
318                 handler: this.stopEditing.createDelegate(this, [true])
319             }, {
320                 xtype: 'button',
321                 text: this.cancelText || 'Cancel',
322                 width: this.minButtonWidth,
323                 handler: this.stopEditing.createDelegate(this, [false])
324             }]
325         });
326         this.btns.render(this.bwrap);
327     },
328
329     afterRender: function(){
330         Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
331         this.positionButtons();
332         if(this.monitorValid){
333             this.startMonitoring();
334         }
335     },
336
337     onShow: function(){
338         if(this.monitorValid){
339             this.startMonitoring();
340         }
341         Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
342     },
343
344     onHide: function(){
345         Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
346         this.stopMonitoring();
347         this.grid.getView().focusRow(this.rowIndex);
348     },
349
350     positionButtons: function(){
351         if(this.btns){
352             var h = this.el.dom.clientHeight;
353             var view = this.grid.getView();
354             var scroll = view.scroller.dom.scrollLeft;
355             var width =  view.mainBody.getWidth();
356             var bw = this.btns.getWidth();
357             this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
358         }
359     },
360
361     // private
362     preEditValue : function(r, field){
363         var value = r.data[field];
364         return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
365     },
366
367     // private
368     postEditValue : function(value, originalValue, r, field){
369         return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
370     },
371
372     doFocus: function(pt){
373         if(this.isVisible()){
374             var index = 0;
375             if(pt){
376                 index = this.getTargetColumnIndex(pt);
377             }
378             var cm = this.grid.getColumnModel();
379             for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
380                 var c = cm.getColumnAt(i);
381                 if(!c.hidden && c.getEditor()){
382                     c.getEditor().focus();
383                     break;
384                 }
385             }
386         }
387     },
388
389     getTargetColumnIndex: function(pt){
390         var grid = this.grid, v = grid.view;
391         var x = pt.left;
392         var cms = grid.colModel.config;
393         var i = 0, match = false;
394         for(var len = cms.length, c; c = cms[i]; i++){
395             if(!c.hidden){
396                 if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
397                     match = i;
398                     break;
399                 }
400             }
401         }
402         return match;
403     },
404
405     startMonitoring : function(){
406         if(!this.bound && this.monitorValid){
407             this.bound = true;
408             Ext.TaskMgr.start({
409                 run : this.bindHandler,
410                 interval : this.monitorPoll || 200,
411                 scope: this
412             });
413         }
414     },
415
416     stopMonitoring : function(){
417         this.bound = false;
418         if(this.tooltip){
419             this.tooltip.hide();
420         }
421     },
422
423     isValid: function(){
424         var valid = true;
425         this.items.each(function(f){
426             if(!f.isValid(true)){
427                 valid = false;
428                 return false;
429             }
430         });
431         return valid;
432     },
433
434     // private
435     bindHandler : function(){
436         if(!this.bound){
437             return false; // stops binding
438         }
439         var valid = this.isValid();
440         if(!valid && this.errorSummary){
441             this.showTooltip(this.getErrorText().join(''));
442         }
443         this.btns.saveBtn.setDisabled(!valid);
444         this.fireEvent('validation', this, valid);
445     },
446
447     showTooltip: function(msg){
448         var t = this.tooltip;
449         if(!t){
450             t = this.tooltip = new Ext.ToolTip({
451                 maxWidth: 600,
452                 cls: 'errorTip',
453                 width: 300,
454                 title: 'Errors',
455                 autoHide: false,
456                 anchor: 'left',
457                 anchorToTarget: true,
458                 mouseOffset: [40,0]
459             });
460         }
461         t.initTarget(this.items.last().getEl());
462         if(!t.rendered){
463             t.show();
464             t.hide();
465         }
466         t.body.update(msg);
467         t.doAutoWidth();
468         t.show();
469     },
470
471     getErrorText: function(){
472         var data = ['<ul>'];
473         this.items.each(function(f){
474             if(!f.isValid(true)){
475                 data.push('<li>', f.activeError, '</li>');
476             }
477         });
478         data.push('</ul>');
479         return data;
480     }
481 });
482 Ext.preg('roweditor', Ext.ux.grid.RowEditor);
483
484 Ext.override(Ext.form.Field, {
485     markInvalid : function(msg){
486         if(!this.rendered || this.preventMark){ // not rendered
487             return;
488         }
489         msg = msg || this.invalidText;
490
491         var mt = this.getMessageHandler();
492         if(mt){
493             mt.mark(this, msg);
494         }else if(this.msgTarget){
495             this.el.addClass(this.invalidClass);
496             var t = Ext.getDom(this.msgTarget);
497             if(t){
498                 t.innerHTML = msg;
499                 t.style.display = this.msgDisplay;
500             }
501         }
502         this.activeError = msg;
503         this.fireEvent('invalid', this, msg);
504     }
505 });
506
507 Ext.override(Ext.ToolTip, {
508     doAutoWidth : function(){
509         var bw = this.body.getTextWidth();
510         if(this.title){
511             bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
512         }
513         bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + 20;
514         this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
515
516         // IE7 repaint bug on initial show
517         if(Ext.isIE7 && !this.repainted){
518             this.el.repaint();
519             this.repainted = true;
520         }
521     }
522 });
523 </pre>    \r
524 </body>\r
525 </html>