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