Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / grid / plugin / CellEditing.js
1 /**
2  * @class Ext.grid.plugin.CellEditing
3  * @extends Ext.grid.plugin.Editing
4  *
5  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
6  * cell will be editable at a time. The field that will be used for the editor is defined at the
7  * {@link Ext.grid.column.Column#field field}. The editor can be a field instance or a field configuration.
8  *
9  * If an editor is not specified for a particular column then that cell will not be editable and it will
10  * be skipped when activated via the mouse or the keyboard.
11  *
12  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
13  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
14  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
15  *
16  * {@img Ext.grid.plugin.CellEditing/Ext.grid.plugin.CellEditing.png Ext.grid.plugin.CellEditing plugin}
17  *
18  * ## Example Usage
19  *    Ext.create('Ext.data.Store', {
20  *        storeId:'simpsonsStore',
21  *        fields:['name', 'email', 'phone'],
22  *        data:{'items':[
23  *            {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
24  *            {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
25  *            {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
26  *            {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
27  *        ]},
28  *        proxy: {
29  *            type: 'memory',
30  *            reader: {
31  *                type: 'json',
32  *                root: 'items'
33  *            }
34  *        }
35  *    });
36  *
37  *    Ext.create('Ext.grid.Panel', {
38  *        title: 'Simpsons',
39  *        store: Ext.data.StoreManager.lookup('simpsonsStore'),
40  *        columns: [
41  *            {header: 'Name',  dataIndex: 'name', field: 'textfield'},
42  *            {header: 'Email', dataIndex: 'email', flex:1,
43  *                editor: {
44  *                    xtype:'textfield',
45  *                    allowBlank:false
46  *                }
47  *            },
48  *            {header: 'Phone', dataIndex: 'phone'}
49  *        ],
50  *        selType: 'cellmodel',
51  *        plugins: [
52  *            Ext.create('Ext.grid.plugin.CellEditing', {
53  *                clicksToEdit: 1
54  *            })
55  *        ],
56  *        height: 200,
57  *        width: 400,
58  *        renderTo: Ext.getBody()
59  *    });
60  *
61  */
62 Ext.define('Ext.grid.plugin.CellEditing', {
63     alias: 'plugin.cellediting',
64     extend: 'Ext.grid.plugin.Editing',
65     requires: ['Ext.grid.CellEditor'],
66
67     constructor: function() {
68         /**
69          * @event beforeedit
70          * Fires before cell editing is triggered. The edit event object has the following properties <br />
71          * <ul style="padding:5px;padding-left:16px;">
72          * <li>grid - The grid</li>
73          * <li>record - The record being edited</li>
74          * <li>field - The field name being edited</li>
75          * <li>value - The value for the field being edited.</li>
76          * <li>row - The grid table row</li>
77          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.</li>
78          * <li>rowIdx - The row index that is being edited</li>
79          * <li>colIdx - The column index that is being edited</li>
80          * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
81          * </ul>
82          * @param {Ext.grid.plugin.Editing} editor
83          * @param {Object} e An edit event (see above for description)
84          */
85         /**
86          * @event edit
87          * Fires after a cell is edited. The edit event object has the following properties <br />
88          * <ul style="padding:5px;padding-left:16px;">
89          * <li>grid - The grid</li>
90          * <li>record - The record that was edited</li>
91          * <li>field - The field name that was edited</li>
92          * <li>value - The value being set</li>
93          * <li>originalValue - The original value for the field, before the edit.</li>
94          * <li>row - The grid table row</li>
95          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.</li>
96          * <li>rowIdx - The row index that was edited</li>
97          * <li>colIdx - The column index that was edited</li>
98          * </ul>
99          *
100          * <pre><code>
101 grid.on('edit', onEdit, this);
102
103 function onEdit(e) {
104     // execute an XHR to send/commit data to the server, in callback do (if successful):
105     e.record.commit();
106 };
107          * </code></pre>
108          * @param {Ext.grid.plugin.Editing} editor
109          * @param {Object} e An edit event (see above for description)
110          */
111         /**
112          * @event validateedit
113          * Fires after a cell is edited, but before the value is set in the record. Return false
114          * to cancel the change. The edit event object has the following properties <br />
115          * <ul style="padding:5px;padding-left:16px;">
116          * <li>grid - The grid</li>
117          * <li>record - The record being edited</li>
118          * <li>field - The field name being edited</li>
119          * <li>value - The value being set</li>
120          * <li>originalValue - The original value for the field, before the edit.</li>
121          * <li>row - The grid table row</li>
122          * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.</li>
123          * <li>rowIdx - The row index that is being edited</li>
124          * <li>colIdx - The column index that is being edited</li>
125          * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
126          * </ul>
127          * Usage example showing how to remove the red triangle (dirty record indicator) from some
128          * records (not all).  By observing the grid's validateedit event, it can be cancelled if
129          * the edit occurs on a targeted row (for example) and then setting the field's new value
130          * in the Record directly:
131          * <pre><code>
132 grid.on('validateedit', function(e) {
133   var myTargetRow = 6;
134
135   if (e.row == myTargetRow) {
136     e.cancel = true;
137     e.record.data[e.field] = e.value;
138   }
139 });
140          * </code></pre>
141          * @param {Ext.grid.plugin.Editing} editor
142          * @param {Object} e An edit event (see above for description)
143          */
144         this.callParent(arguments);
145         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
146             return editor.editorId;
147         });
148     },
149
150     /**
151      * @private
152      * AbstractComponent calls destroy on all its plugins at destroy time.
153      */
154     destroy: function() {
155         var me = this;
156         me.editors.each(Ext.destroy, Ext);
157         me.editors.clear();
158         me.callParent(arguments);
159     },
160
161     // private
162     // Template method called from base class's initEvents
163     initCancelTriggers: function() {
164         var me   = this;
165             grid = me.grid,
166             view   = grid.view;
167
168         me.mon(view, {
169             mousewheel: {
170                 element: 'el',
171                 fn: me.cancelEdit,
172                 scope: me
173             }
174         });
175         me.mon(grid, {
176             columnresize: me.cancelEdit,
177             columnmove: me.cancelEdit,
178             scope: me
179         });
180     },
181
182     /**
183      * Start editing the specified record, using the specified Column definition to define which field is being edited.
184      * @param {Model} record The Store data record which backs the row to be edited.
185      * @param {Model} columnHeader The Column object defining the column to be edited.
186      * @override
187      */
188     startEdit: function(record, columnHeader) {
189         var me = this,
190             ed   = me.getEditor(record, columnHeader),
191             value = record.get(columnHeader.dataIndex),
192             context = me.getEditingContext(record, columnHeader);
193
194         record = context.record;
195         columnHeader = context.column;
196
197         // Complete the edit now, before getting the editor's target
198         // cell DOM element. Completing the edit causes a view refresh.
199         me.completeEdit();
200
201         // See if the field is editable for the requested record
202         if (columnHeader && !columnHeader.getEditor(record)) {
203             return false;
204         }
205
206         if (ed) {
207             context.originalValue = context.value = value;
208             if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
209                 return false;
210             }
211
212             me.context = context;
213             me.setActiveEditor(ed);
214             me.setActiveRecord(record);
215             me.setActiveColumn(columnHeader);
216
217             // Defer, so we have some time between view scroll to sync up the editor
218             Ext.defer(ed.startEdit, 15, ed, [me.getCell(record, columnHeader), value]);
219         } else {
220             // BrowserBug: WebKit & IE refuse to focus the element, rather
221             // it will focus it and then immediately focus the body. This
222             // temporary hack works for Webkit and IE6. IE7 and 8 are still
223             // broken
224             me.grid.getView().el.focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
225         }
226     },
227
228     completeEdit: function() {
229         var activeEd = this.getActiveEditor();
230         if (activeEd) {
231             activeEd.completeEdit();
232         }
233     },
234
235     // internal getters/setters
236     setActiveEditor: function(ed) {
237         this.activeEditor = ed;
238     },
239
240     getActiveEditor: function() {
241         return this.activeEditor;
242     },
243
244     setActiveColumn: function(column) {
245         this.activeColumn = column;
246     },
247
248     getActiveColumn: function() {
249         return this.activeColumn;
250     },
251
252     setActiveRecord: function(record) {
253         this.activeRecord = record;
254     },
255
256     getActiveRecord: function() {
257         return this.activeRecord;
258     },
259
260     getEditor: function(record, column) {
261         var editors = this.editors,
262             editorId = column.itemId || column.id,
263             editor = editors.getByKey(editorId);
264
265         if (editor) {
266             return editor;
267         } else {
268             editor = column.getEditor(record);
269             if (!editor) {
270                 return false;
271             }
272
273             // Allow them to specify a CellEditor in the Column
274             if (!(editor instanceof Ext.grid.CellEditor)) {
275                 editor = Ext.create('Ext.grid.CellEditor', {
276                     editorId: editorId,
277                     field: editor
278                 });
279             }
280             editor.parentEl = this.grid.getEditorParent();
281             // editor.parentEl should be set here.
282             editor.on({
283                 scope: this,
284                 specialkey: this.onSpecialKey,
285                 complete: this.onEditComplete,
286                 canceledit: this.cancelEdit
287             });
288             editors.add(editor);
289             return editor;
290         }
291     },
292
293     /**
294      * Get the cell (td) for a particular record and column.
295      * @param {Ext.data.Model} record
296      * @param {Ext.grid.column.Colunm} column
297      * @private
298      */
299     getCell: function(record, column) {
300         return this.grid.getView().getCell(record, column);
301     },
302
303     onSpecialKey: function(ed, field, e) {
304         var grid = this.grid,
305             sm;
306         if (e.getKey() === e.TAB) {
307             e.stopEvent();
308             sm = grid.getSelectionModel();
309             if (sm.onEditorTab) {
310                 sm.onEditorTab(this, e);
311             }
312         }
313     },
314
315     onEditComplete : function(ed, value, startValue) {
316         var me = this,
317             grid = me.grid,
318             sm = grid.getSelectionModel(),
319             dataIndex = me.getActiveColumn().dataIndex;
320
321         me.setActiveEditor(null);
322         me.setActiveColumn(null);
323         me.setActiveRecord(null);
324         delete sm.wasEditing;
325
326         if (!me.validateEdit()) {
327             return;
328         }
329         me.context.record.set(dataIndex, value);
330         me.fireEvent('edit', me, me.context);
331     },
332
333     /**
334      * Cancel any active editing.
335      */
336     cancelEdit: function() {
337         var me = this,
338             activeEd = me.getActiveEditor(),
339             viewEl = me.grid.getView().el;
340
341         me.setActiveEditor(null);
342         me.setActiveColumn(null);
343         me.setActiveRecord(null);
344         if (activeEd) {
345             activeEd.cancelEdit();
346             viewEl.focus();
347         }
348     },
349
350     /**
351      * Starts editing by position (row/column)
352      * @param {Object} position A position with keys of row and column.
353      */
354     startEditByPosition: function(position) {
355         var me = this,
356             grid = me.grid,
357             sm = grid.getSelectionModel(),
358             editRecord = grid.store.getAt(position.row),
359             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
360
361         if (sm.selectByPosition) {
362             sm.selectByPosition(position);
363         }
364         me.startEdit(editRecord, editColumnHeader);
365     }
366 });