Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / grid / plugin / RowEditing.js
1 /**
2  * @class Ext.grid.plugin.RowEditing
3  * @extends Ext.grid.plugin.Editing
4  * 
5  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
6  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
7  * for editing. There is a button to save or cancel all changes for the edit.
8  * 
9  * The field that will be used for the editor is defined at the
10  * {@link Ext.grid.column.Column#field field}. The editor can be a field instance or a field configuration.
11  * If an editor is not specified for a particular column then that column won't be editable and the value of
12  * the column will be displayed.
13  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
14  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
15  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
16  * 
17  * {@img Ext.grid.plugin.RowEditing/Ext.grid.plugin.RowEditing.png Ext.grid.plugin.RowEditing plugin}
18  *
19  * ## Example Usage
20  *    Ext.create('Ext.data.Store', {
21  *        storeId:'simpsonsStore',
22  *        fields:['name', 'email', 'phone'],
23  *        data:{'items':[
24  *            {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
25  *            {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
26  *            {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},                        
27  *            {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}            
28  *        ]},
29  *        proxy: {
30  *            type: 'memory',
31  *            reader: {
32  *                type: 'json',
33  *                root: 'items'
34  *            }
35  *        }
36  *    });
37  *   
38  *    Ext.create('Ext.grid.Panel', {
39  *        title: 'Simpsons',
40  *        store: Ext.data.StoreManager.lookup('simpsonsStore'),
41  *        columns: [
42  *            {header: 'Name',  dataIndex: 'name', field: 'textfield'},
43  *            {header: 'Email', dataIndex: 'email', flex:1, 
44  *                editor: {
45  *                    xtype:'textfield',
46  *                    allowBlank:false
47  *                }
48  *            },
49  *            {header: 'Phone', dataIndex: 'phone'}
50  *        ],
51  *        selType: 'rowmodel',
52  *        plugins: [
53  *            Ext.create('Ext.grid.plugin.RowEditing', {
54  *                clicksToEdit: 1
55  *            })
56  *        ],
57  *        height: 200,
58  *        width: 400,
59  *        renderTo: Ext.getBody()
60  *    });
61  * 
62  * @markdown
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      * @markdown
80      */
81     autoCancel: true,
82
83     /**
84      * @cfg {Number} clicksToMoveEditor
85      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
86      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
87      * @markdown
88      */
89
90     /**
91      * @cfg {Boolean} errorSummary
92      * `true` to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
93      * in the row editor. Set to `false` to prevent the tooltip from showing. Defaults to `true`.
94      * @markdown
95      */
96     errorSummary: true,
97
98     /**
99      * @event beforeedit
100      * Fires before row editing is triggered. The edit event object has the following properties <br />
101      * <ul style="padding:5px;padding-left:16px;">
102      * <li>grid - The grid this editor is on</li>
103      * <li>view - The grid view</li>
104      * <li>store - The grid store</li>
105      * <li>record - The record being edited</li>
106      * <li>row - The grid table row</li>
107      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
108      * <li>rowIdx - The row index that is being edited</li>
109      * <li>colIdx - The column index that initiated the edit</li>
110      * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
111      * </ul>
112      * @param {Ext.grid.plugin.Editing} editor
113      * @param {Object} e An edit event (see above for description)
114      */
115     /**
116      * @event edit
117      * Fires after a row is edited. The edit event object has the following properties <br />
118      * <ul style="padding:5px;padding-left:16px;">
119      * <li>grid - The grid this editor is on</li>
120      * <li>view - The grid view</li>
121      * <li>store - The grid store</li>
122      * <li>record - The record being edited</li>
123      * <li>row - The grid table row</li>
124      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
125      * <li>rowIdx - The row index that is being edited</li>
126      * <li>colIdx - The column index that initiated the edit</li>
127      * </ul>
128      *
129      * <pre><code>
130 grid.on('edit', onEdit, this);
131
132 function onEdit(e) {
133     // execute an XHR to send/commit data to the server, in callback do (if successful):
134     e.record.commit();
135 };
136      * </code></pre>
137      * @param {Ext.grid.plugin.Editing} editor
138      * @param {Object} e An edit event (see above for description)
139      */
140     /**
141      * @event validateedit
142      * Fires after a cell is edited, but before the value is set in the record. Return false
143      * to cancel the change. The edit event object has the following properties <br />
144      * <ul style="padding:5px;padding-left:16px;">
145      * <li>grid - The grid this editor is on</li>
146      * <li>view - The grid view</li>
147      * <li>store - The grid store</li>
148      * <li>record - The record being edited</li>
149      * <li>row - The grid table row</li>
150      * <li>column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit</li>
151      * <li>rowIdx - The row index that is being edited</li>
152      * <li>colIdx - The column index that initiated the edit</li>
153      * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
154      * </ul>
155      * Usage example showing how to remove the red triangle (dirty record indicator) from some
156      * records (not all).  By observing the grid's validateedit event, it can be cancelled if
157      * the edit occurs on a targeted row (for example) and then setting the field's new value
158      * in the Record directly:
159      * <pre><code>
160 grid.on('validateedit', function(e) {
161   var myTargetRow = 6;
162
163   if (e.rowIdx == myTargetRow) {
164     e.cancel = true;
165     e.record.data[e.field] = e.value;
166   }
167 });
168      * </code></pre>
169      * @param {Ext.grid.plugin.Editing} editor
170      * @param {Object} e An edit event (see above for description)
171      */
172
173     constructor: function() {
174         var me = this;
175         me.callParent(arguments);
176
177         if (!me.clicksToMoveEditor) {
178             me.clicksToMoveEditor = me.clicksToEdit;
179         }
180
181         me.autoCancel = !!me.autoCancel;
182     },
183
184     /**
185      * @private
186      * AbstractComponent calls destroy on all its plugins at destroy time.
187      */
188     destroy: function() {
189         var me = this;
190         Ext.destroy(me.editor);
191         me.callParent(arguments);
192     },
193
194     /**
195      * Start editing the specified record, using the specified Column definition to define which field is being edited.
196      * @param {Model} record The Store data record which backs the row to be edited.
197      * @param {Model} columnHeader The Column object defining the column to be edited.
198      * @override
199      */
200     startEdit: function(record, columnHeader) {
201         var me = this,
202             editor = me.getEditor();
203
204         if (me.callParent(arguments) === false) {
205             return false;
206         }
207
208         // Fire off our editor
209         if (editor.beforeEdit() !== false) {
210             editor.startEdit(me.context.record, me.context.column);
211         }
212     },
213
214     // private
215     cancelEdit: function() {
216         var me = this;
217
218         if (me.editing) {
219             me.getEditor().cancelEdit();
220             me.callParent(arguments);
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         return me.callParent(arguments) && me.getEditor().completeEdit();
238     },
239
240     // private
241     getEditor: function() {
242         var me = this;
243
244         if (!me.editor) {
245             me.editor = me.initEditor();
246         }
247         return me.editor;
248     },
249
250     // private
251     initEditor: function() {
252         var me = this,
253             grid = me.grid,
254             view = me.view,
255             headerCt = grid.headerCt;
256
257         return Ext.create('Ext.grid.RowEditor', {
258             autoCancel: me.autoCancel,
259             errorSummary: me.errorSummary,
260             fields: headerCt.getGridColumns(),
261             hidden: true,
262
263             // keep a reference..
264             editingPlugin: me,
265             renderTo: view.el
266         });
267     },
268
269     // private
270     initEditTriggers: function() {
271         var me = this,
272             grid = me.grid,
273             view = me.view,
274             headerCt = grid.headerCt,
275             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
276
277         me.callParent(arguments);
278
279         if (me.clicksToMoveEditor !== me.clicksToEdit) {
280             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
281         }
282
283         view.on('render', function() {
284             // Column events
285             me.mon(headerCt, {
286                 add: me.onColumnAdd,
287                 remove: me.onColumnRemove,
288                 columnresize: me.onColumnResize,
289                 columnhide: me.onColumnHide,
290                 columnshow: me.onColumnShow,
291                 columnmove: me.onColumnMove,
292                 scope: me
293             });
294         }, me, { single: true });
295     },
296
297     startEditByClick: function() {
298         var me = this;
299         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
300             me.callParent(arguments);
301         }
302     },
303
304     moveEditorByClick: function() {
305         var me = this;
306         if (me.editing) {
307             me.superclass.startEditByClick.apply(me, arguments);
308         }
309     },
310
311     // private
312     onColumnAdd: function(ct, column) {
313         var me = this,
314             editor = me.getEditor();
315
316         me.initFieldAccessors(column);
317         if (editor && editor.onColumnAdd) {
318             editor.onColumnAdd(column);
319         }
320     },
321
322     // private
323     onColumnRemove: function(ct, column) {
324         var me = this,
325             editor = me.getEditor();
326
327         if (editor && editor.onColumnRemove) {
328             editor.onColumnRemove(column);
329         }
330         me.removeFieldAccessors(column);
331     },
332
333     // private
334     onColumnResize: function(ct, column, width) {
335         var me = this,
336             editor = me.getEditor();
337
338         if (editor && editor.onColumnResize) {
339             editor.onColumnResize(column, width);
340         }
341     },
342
343     // private
344     onColumnHide: function(ct, column) {
345         var me = this,
346             editor = me.getEditor();
347
348         if (editor && editor.onColumnHide) {
349             editor.onColumnHide(column);
350         }
351     },
352
353     // private
354     onColumnShow: function(ct, column) {
355         var me = this,
356             editor = me.getEditor();
357
358         if (editor && editor.onColumnShow) {
359             editor.onColumnShow(column);
360         }
361     },
362
363     // private
364     onColumnMove: function(ct, column, fromIdx, toIdx) {
365         var me = this,
366             editor = me.getEditor();
367
368         if (editor && editor.onColumnMove) {
369             editor.onColumnMove(column, fromIdx, toIdx);
370         }
371     },
372
373     // private
374     setColumnField: function(column, field) {
375         var me = this;
376         me.callParent(arguments);
377         me.getEditor().setField(column.field, column);
378     }
379 });