Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / grid / plugin / RowEditing.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
17  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
18  * for editing. There is a button to save or cancel all changes for the edit.
19  *
20  * The field that will be used for the editor is defined at the
21  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
22  * If an editor is not specified for a particular column then that column won't be editable and the value of
23  * the column will be displayed.
24  *
25  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
26  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
27  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
28  *
29  *     @example
30  *     Ext.create('Ext.data.Store', {
31  *         storeId:'simpsonsStore',
32  *         fields:['name', 'email', 'phone'],
33  *         data: [
34  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
35  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
36  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
37  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
38  *         ]
39  *     });
40  *
41  *     Ext.create('Ext.grid.Panel', {
42  *         title: 'Simpsons',
43  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
44  *         columns: [
45  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
46  *             {header: 'Email', dataIndex: 'email', flex:1,
47  *                 editor: {
48  *                     xtype: 'textfield',
49  *                     allowBlank: false
50  *                 }
51  *             },
52  *             {header: 'Phone', dataIndex: 'phone'}
53  *         ],
54  *         selType: 'rowmodel',
55  *         plugins: [
56  *             Ext.create('Ext.grid.plugin.RowEditing', {
57  *                 clicksToEdit: 1
58  *             })
59  *         ],
60  *         height: 200,
61  *         width: 400,
62  *         renderTo: Ext.getBody()
63  *     });
64  */
65 Ext.define('Ext.grid.plugin.RowEditing', {
66     extend: 'Ext.grid.plugin.Editing',
67     alias: 'plugin.rowediting',
68
69     requires: [
70         'Ext.grid.RowEditor'
71     ],
72
73     editStyle: 'row',
74
75     /**
76      * @cfg {Boolean} autoCancel
77      * True to automatically cancel any pending changes when the row editor begins editing a new row.
78      * False to force the user to explicitly cancel the pending changes. Defaults to true.
79      */
80     autoCancel: true,
81
82     /**
83      * @cfg {Number} clicksToMoveEditor
84      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
85      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
86      */
87
88     /**
89      * @cfg {Boolean} errorSummary
90      * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
91      * in the row editor. Set to false to prevent the tooltip from showing. Defaults to true.
92      */
93     errorSummary: true,
94
95     /**
96      * @event beforeedit
97      * Fires before row editing is triggered.
98      *
99      * @param {Ext.grid.plugin.Editing} editor
100      * @param {Object} e An edit event with the following properties:
101      *
102      * - grid - The grid this editor is on
103      * - view - The grid view
104      * - store - The grid store
105      * - record - The record being edited
106      * - row - The grid table row
107      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
108      * - rowIdx - The row index that is being edited
109      * - colIdx - The column index that initiated the edit
110      * - cancel - Set this to true to cancel the edit or return false from your handler.
111      */
112     
113     /**
114      * @event canceledit
115      * Fires when the user has started editing a row but then cancelled the edit
116      * @param {Object} grid The grid
117      */
118     
119     /**
120      * @event edit
121      * Fires after a row is edited. Usage example:
122      *
123      *     grid.on('edit', function(editor, e) {
124      *         // commit the changes right after editing finished
125      *         e.record.commit();
126      *     };
127      *
128      * @param {Ext.grid.plugin.Editing} editor
129      * @param {Object} e An edit event with the following properties:
130      *
131      * - grid - The grid this editor is on
132      * - view - The grid view
133      * - store - The grid store
134      * - record - The record being edited
135      * - row - The grid table row
136      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
137      * - rowIdx - The row index that is being edited
138      * - colIdx - The column index that initiated the edit
139      */
140     /**
141      * @event validateedit
142      * Fires after a cell is edited, but before the value is set in the record. Return false to cancel the change. The
143      * edit event object has the following properties
144      *
145      * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
146      * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for example)
147      * and then setting the field's new value in the Record directly:
148      *
149      *     grid.on('validateedit', function(editor, e) {
150      *       var myTargetRow = 6;
151      *
152      *       if (e.rowIdx == myTargetRow) {
153      *         e.cancel = true;
154      *         e.record.data[e.field] = e.value;
155      *       }
156      *     });
157      *
158      * @param {Ext.grid.plugin.Editing} editor
159      * @param {Object} e An edit event with the following properties:
160      *
161      * - grid - The grid this editor is on
162      * - view - The grid view
163      * - store - The grid store
164      * - record - The record being edited
165      * - row - The grid table row
166      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
167      * - rowIdx - The row index that is being edited
168      * - colIdx - The column index that initiated the edit
169      * - cancel - Set this to true to cancel the edit or return false from your handler.
170      */
171
172     constructor: function() {
173         var me = this;
174         me.callParent(arguments);
175
176         if (!me.clicksToMoveEditor) {
177             me.clicksToMoveEditor = me.clicksToEdit;
178         }
179
180         me.autoCancel = !!me.autoCancel;
181     },
182
183     /**
184      * @private
185      * AbstractComponent calls destroy on all its plugins at destroy time.
186      */
187     destroy: function() {
188         var me = this;
189         Ext.destroy(me.editor);
190         me.callParent(arguments);
191     },
192
193     /**
194      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
195      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
196      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
197      */
198     startEdit: function(record, columnHeader) {
199         var me = this,
200             editor = me.getEditor();
201
202         if (me.callParent(arguments) === false) {
203             return false;
204         }
205
206         // Fire off our editor
207         if (editor.beforeEdit() !== false) {
208             editor.startEdit(me.context.record, me.context.column);
209         }
210     },
211
212     // private
213     cancelEdit: function() {
214         var me = this;
215
216         if (me.editing) {
217             me.getEditor().cancelEdit();
218             me.callParent(arguments);
219             
220             me.fireEvent('canceledit', me.context);
221         }
222     },
223
224     // private
225     completeEdit: function() {
226         var me = this;
227
228         if (me.editing && me.validateEdit()) {
229             me.editing = false;
230             me.fireEvent('edit', me.context);
231         }
232     },
233
234     // private
235     validateEdit: function() {
236         var me             = this,
237             editor         = me.editor,
238             context        = me.context,
239             record         = context.record,
240             newValues      = {},
241             originalValues = {},
242             name;
243
244         editor.items.each(function(item) {
245             name = item.name;
246
247             newValues[name]      = item.getValue();
248             originalValues[name] = record.get(name);
249         });
250
251         Ext.apply(context, {
252             newValues      : newValues,
253             originalValues : originalValues
254         });
255
256         return me.callParent(arguments) && me.getEditor().completeEdit();
257     },
258
259     // private
260     getEditor: function() {
261         var me = this;
262
263         if (!me.editor) {
264             me.editor = me.initEditor();
265         }
266         return me.editor;
267     },
268
269     // private
270     initEditor: function() {
271         var me = this,
272             grid = me.grid,
273             view = me.view,
274             headerCt = grid.headerCt;
275
276         return Ext.create('Ext.grid.RowEditor', {
277             autoCancel: me.autoCancel,
278             errorSummary: me.errorSummary,
279             fields: headerCt.getGridColumns(),
280             hidden: true,
281
282             // keep a reference..
283             editingPlugin: me,
284             renderTo: view.el
285         });
286     },
287
288     // private
289     initEditTriggers: function() {
290         var me = this,
291             grid = me.grid,
292             view = me.view,
293             headerCt = grid.headerCt,
294             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
295
296         me.callParent(arguments);
297
298         if (me.clicksToMoveEditor !== me.clicksToEdit) {
299             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
300         }
301
302         view.on('render', function() {
303             // Column events
304             me.mon(headerCt, {
305                 add: me.onColumnAdd,
306                 remove: me.onColumnRemove,
307                 columnresize: me.onColumnResize,
308                 columnhide: me.onColumnHide,
309                 columnshow: me.onColumnShow,
310                 columnmove: me.onColumnMove,
311                 scope: me
312             });
313         }, me, { single: true });
314     },
315
316     startEditByClick: function() {
317         var me = this;
318         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
319             me.callParent(arguments);
320         }
321     },
322
323     moveEditorByClick: function() {
324         var me = this;
325         if (me.editing) {
326             me.superclass.startEditByClick.apply(me, arguments);
327         }
328     },
329
330     // private
331     onColumnAdd: function(ct, column) {
332         if (column.isHeader) {
333             var me = this,
334                 editor;
335
336             me.initFieldAccessors(column);
337             editor = me.getEditor();
338
339             if (editor && editor.onColumnAdd) {
340                 editor.onColumnAdd(column);
341             }
342         }
343     },
344
345     // private
346     onColumnRemove: function(ct, column) {
347         if (column.isHeader) {
348             var me = this,
349                 editor = me.getEditor();
350
351             if (editor && editor.onColumnRemove) {
352                 editor.onColumnRemove(column);
353             }
354             me.removeFieldAccessors(column);
355         }
356     },
357
358     // private
359     onColumnResize: function(ct, column, width) {
360         if (column.isHeader) {
361             var me = this,
362                 editor = me.getEditor();
363
364             if (editor && editor.onColumnResize) {
365                 editor.onColumnResize(column, width);
366             }
367         }
368     },
369
370     // private
371     onColumnHide: function(ct, column) {
372         // no isHeader check here since its already a columnhide event.
373         var me = this,
374             editor = me.getEditor();
375
376         if (editor && editor.onColumnHide) {
377             editor.onColumnHide(column);
378         }
379     },
380
381     // private
382     onColumnShow: function(ct, column) {
383         // no isHeader check here since its already a columnshow event.
384         var me = this,
385             editor = me.getEditor();
386
387         if (editor && editor.onColumnShow) {
388             editor.onColumnShow(column);
389         }
390     },
391
392     // private
393     onColumnMove: function(ct, column, fromIdx, toIdx) {
394         // no isHeader check here since its already a columnmove event.
395         var me = this,
396             editor = me.getEditor();
397
398         if (editor && editor.onColumnMove) {
399             editor.onColumnMove(column, fromIdx, toIdx);
400         }
401     },
402
403     // private
404     setColumnField: function(column, field) {
405         var me = this;
406         me.callParent(arguments);
407         me.getEditor().setField(column.field, column);
408     }
409 });
410