]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/grid/plugin/RowEditing.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / grid / plugin / RowEditing.js
CommitLineData
6527f429
DM
1/**\r
2 * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,\r
3 * a small floating dialog will be shown for the appropriate row. Each editable column will show a field\r
4 * for editing. There is a button to save or cancel all changes for the edit.\r
5 *\r
6 * The field that will be used for the editor is defined at the\r
7 * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.\r
8 * If an editor is not specified for a particular column then that column won't be editable and the value of\r
9 * the column will be displayed. To provide a custom renderer for non-editable values, use the \r
10 * {@link Ext.grid.column.Column#editRenderer editRenderer} configuration on the column.\r
11 *\r
12 * The editor may be shared for each column in the grid, or a different one may be specified for each column.\r
13 * An appropriate field type should be chosen to match the data structure that it will be editing. For example,\r
14 * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.\r
15 *\r
16 * @example\r
17 * Ext.create('Ext.data.Store', {\r
18 * storeId: 'simpsonsStore',\r
19 * fields:[ 'name', 'email', 'phone'],\r
20 * data: [\r
21 * { name: 'Lisa', email: 'lisa@simpsons.com', phone: '555-111-1224' },\r
22 * { name: 'Bart', email: 'bart@simpsons.com', phone: '555-222-1234' },\r
23 * { name: 'Homer', email: 'homer@simpsons.com', phone: '555-222-1244' },\r
24 * { name: 'Marge', email: 'marge@simpsons.com', phone: '555-222-1254' }\r
25 * ]\r
26 * });\r
27 *\r
28 * Ext.create('Ext.grid.Panel', {\r
29 * title: 'Simpsons',\r
30 * store: Ext.data.StoreManager.lookup('simpsonsStore'),\r
31 * columns: [\r
32 * {header: 'Name', dataIndex: 'name', editor: 'textfield'},\r
33 * {header: 'Email', dataIndex: 'email', flex:1,\r
34 * editor: {\r
35 * xtype: 'textfield',\r
36 * allowBlank: false\r
37 * }\r
38 * },\r
39 * {header: 'Phone', dataIndex: 'phone'}\r
40 * ],\r
41 * selModel: 'rowmodel',\r
42 * plugins: {\r
43 * ptype: 'rowediting',\r
44 * clicksToEdit: 1\r
45 * },\r
46 * height: 200,\r
47 * width: 400,\r
48 * renderTo: Ext.getBody()\r
49 * });\r
50 *\r
51 */\r
52Ext.define('Ext.grid.plugin.RowEditing', {\r
53 extend: 'Ext.grid.plugin.Editing',\r
54 alias: 'plugin.rowediting',\r
55\r
56 requires: [\r
57 'Ext.grid.RowEditor'\r
58 ],\r
59\r
60 lockableScope: 'top',\r
61\r
62 editStyle: 'row',\r
63\r
64 /**\r
65 * @cfg {Boolean} autoCancel\r
66 * `true` to automatically cancel any pending changes when the row editor begins editing a new row.\r
67 * `false` to force the user to explicitly cancel the pending changes.\r
68 */\r
69 autoCancel: true,\r
70\r
71 /**\r
72 * @cfg {Number} clicksToMoveEditor\r
73 * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.\r
74 * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.\r
75 */\r
76\r
77 /**\r
78 * @cfg {Boolean} errorSummary\r
79 * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present\r
80 * in the row editor. Set to false to prevent the tooltip from showing.\r
81 */\r
82 errorSummary: true,\r
83\r
84 constructor: function() {\r
85 var me = this;\r
86\r
87 me.callParent(arguments);\r
88\r
89 if (!me.clicksToMoveEditor) {\r
90 me.clicksToMoveEditor = me.clicksToEdit;\r
91 }\r
92\r
93 me.autoCancel = !!me.autoCancel;\r
94 },\r
95\r
96 init: function(grid) {\r
97 this.callParent([grid]);\r
98\r
99 // This plugin has an interest in processing a request for actionable mode.\r
100 // It does not actually enter actionable mode, it just calls startEdit\r
101 if (grid.lockedGrid) {\r
102 grid.lockedGrid.registerActionable(this);\r
103 grid.normalGrid.registerActionable(this);\r
104 } else {\r
105 grid.registerActionable(this);\r
106 }\r
107 },\r
108\r
109 destroy: function() {\r
110 Ext.destroy(this.editor);\r
111 this.callParent();\r
112 },\r
113 \r
114 onBeforeReconfigure: function() {\r
115 this.callParent(arguments);\r
116 this.cancelEdit();\r
117 },\r
118 \r
119 onReconfigure: function(grid, store, columns) {\r
120 var ed = this.editor;\r
121 this.callParent(arguments);\r
122 // Only need to adjust column widths if we have new columns \r
123 if (columns && ed && ed.rendered) {\r
124 ed.needsSyncFieldWidths = true;\r
125 }\r
126 },\r
127\r
128 shouldStartEdit: function(editor) {\r
129 return true;\r
130 },\r
131\r
132 /**\r
133 * Starts editing the specified record, using the specified Column definition to define which field is being edited.\r
134 * @param {Ext.data.Model} record The Store data record which backs the row to be edited.\r
135 * @param {Ext.grid.column.Column/Number} [columnHeader] The Column object defining the column field to be focused, or index of the column.\r
136 * If not specified, it will default to the first visible column.\r
137 * @return {Boolean} `true` if editing was started, `false` otherwise.\r
138 */\r
139 startEdit: function(record, columnHeader) {\r
140 var me = this,\r
141 editor = me.getEditor(),\r
142 context;\r
143 \r
144 if (Ext.isEmpty(columnHeader)) {\r
145 columnHeader = me.grid.getTopLevelVisibleColumnManager().getHeaderAtIndex(0);\r
146 }\r
147\r
148 if (editor.beforeEdit() !== false) {\r
149 context = me.getEditingContext(record, columnHeader);\r
150 if (context && me.beforeEdit(context) !== false && me.fireEvent('beforeedit', me, context) !== false && !context.cancel) {\r
151 me.context = context;\r
152\r
153 // If editing one side of a lockable grid, cancel any edit on the other side.\r
154 if (me.lockingPartner) {\r
155 me.lockingPartner.cancelEdit();\r
156 }\r
157 editor.startEdit(context.record, context.column, context);\r
158 me.editing = true;\r
159 return true;\r
160 }\r
161 }\r
162 return false;\r
163 },\r
164\r
165 /**\r
166 * This method is called when actionable mode is requested for a cell. \r
167 * @param {Ext.grid.CellContext} position The position at which actionable mode was requested.\r
168 * @return {Boolean} `false` Actionable mode is *not* entered for RowEditing.\r
169 * @protected\r
170 */\r
171 activateCell: function(pos) {\r
172 // Only activate editing if there are no readily activatable elements in the activate position.\r
173 // We defer to those focusables. Editing may be started on other columns.\r
174 if (!pos.getCell().query('[tabIndex="-1"]').length) {\r
175 this.startEdit(pos.record, pos.column);\r
176 return true ;\r
177 }\r
178 },\r
179\r
180 /**\r
181 * @private\r
182 * The {@link Ext.grid.RowEditor RowEditor} hooks up a KeyNav to call this method to complete the edit.\r
183 */\r
184 onEnterKey: function(e) {\r
185 var me = this,\r
186 targetComponent;\r
187\r
188 // KeyMap entry for EnterKey added after the entry that sets actionable mode, so this will get called\r
189 // after that handler. We must ignore ENTER key in actionable mode.\r
190 if (!me.grid.ownerGrid.actionableMode && me.editing) {\r
191 targetComponent = Ext.getCmp(e.getTarget().getAttribute('componentId'));\r
192\r
193 // ENTER when a picker is expanded does not complete the edit\r
194 if (!(targetComponent && targetComponent.isPickerField && targetComponent.isExpanded)) {\r
195 me.completeEdit();\r
196 }\r
197 }\r
198 },\r
199\r
200 cancelEdit: function() {\r
201 var me = this;\r
202\r
203 if (me.editing) {\r
204 me.getContextFieldValues();\r
205 me.getEditor().cancelEdit();\r
206 me.callParent(arguments);\r
207 return;\r
208 }\r
209 // If we aren't editing, return true to allow the event to bubble\r
210 return true;\r
211 },\r
212\r
213 completeEdit: function() {\r
214 var me = this,\r
215 context = me.context;\r
216\r
217 if (me.editing && me.validateEdit(context)) {\r
218 me.editing = false;\r
219 me.fireEvent('edit', me, context);\r
220 }\r
221 },\r
222\r
223 validateEdit: function() {\r
224 this.getContextFieldValues();\r
225 return this.callParent(arguments) && this.getEditor().completeEdit();\r
226 },\r
227\r
228 getEditor: function() {\r
229 var me = this;\r
230\r
231 if (!me.editor) {\r
232 me.editor = me.initEditor();\r
233 }\r
234 return me.editor;\r
235 },\r
236\r
237 getContextFieldValues: function () {\r
238 var editor = this.editor,\r
239 context = this.context,\r
240 record = context.record,\r
241 newValues = {},\r
242 originalValues = {},\r
243 editors = editor.query('>[isFormField]'),\r
244 len = editors.length,\r
245 i, name, item;\r
246\r
247 for (i = 0; i < len; i++) {\r
248 item = editors[i];\r
249 name = item.dataIndex;\r
250\r
251 newValues[name] = item.getValue();\r
252 originalValues[name] = record.get(name);\r
253 }\r
254\r
255 Ext.apply(context, {\r
256 newValues : newValues,\r
257 originalValues : originalValues\r
258 });\r
259 },\r
260\r
261 /**\r
262 * @private\r
263 */\r
264 initEditor: function() {\r
265 return new Ext.grid.RowEditor(this.initEditorConfig());\r
266 },\r
267 \r
268 initEditorConfig: function(){\r
269 var me = this,\r
270 grid = me.grid,\r
271 view = me.view,\r
272 headerCt = grid.headerCt,\r
273 btns = ['saveBtnText', 'cancelBtnText', 'errorsText', 'dirtyText'],\r
274 b,\r
275 bLen = btns.length,\r
276 cfg = {\r
277 autoCancel: me.autoCancel,\r
278 errorSummary: me.errorSummary,\r
279 fields: headerCt.getGridColumns(),\r
280 hidden: true,\r
281 view: view,\r
282 // keep a reference..\r
283 editingPlugin: me\r
284 },\r
285 item;\r
286\r
287 for (b = 0; b < bLen; b++) {\r
288 item = btns[b];\r
289\r
290 if (Ext.isDefined(me[item])) {\r
291 cfg[item] = me[item];\r
292 }\r
293 }\r
294 return cfg; \r
295 },\r
296\r
297 /**\r
298 * @private\r
299 */\r
300 initEditTriggers: function() {\r
301 var me = this,\r
302 view = me.view,\r
303 moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';\r
304\r
305 me.callParent(arguments);\r
306\r
307 if (me.clicksToMoveEditor !== me.clicksToEdit) {\r
308 me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);\r
309 }\r
310\r
311 view.on({\r
312 render: function() {\r
313 me.mon(me.grid.headerCt, {\r
314 scope: me,\r
315 columnresize: me.onColumnResize,\r
316 columnhide: me.onColumnHide,\r
317 columnshow: me.onColumnShow\r
318 });\r
319 },\r
320 single: true\r
321 });\r
322 },\r
323\r
324 moveEditorByClick: function() {\r
325 var me = this;\r
326 if (me.editing) {\r
327 me.superclass.onCellClick.apply(me, arguments);\r
328 }\r
329 },\r
330\r
331 /**\r
332 * @private\r
333 */\r
334 onColumnAdd: function(ct, column) {\r
335 if (column.isHeader) {\r
336 var me = this,\r
337 editor;\r
338\r
339 me.initFieldAccessors(column);\r
340\r
341 // Only inform the editor about a new column if the editor has already been instantiated,\r
342 // so do not use getEditor which instantiates the editor if not present.\r
343 editor = me.editor;\r
344 if (editor) {\r
345 editor.onColumnAdd(column);\r
346 }\r
347 }\r
348 },\r
349\r
350 // Ensure editors are cleaned up.\r
351 beforeGridHeaderDestroy: function(headerCt) {\r
352 var columns = this.grid.getColumnManager().getColumns(),\r
353 len = columns.length,\r
354 i,\r
355 column,\r
356 field;\r
357\r
358 for (i = 0; i < len; i++) {\r
359 column = columns[i];\r
360\r
361 // If it has a field accessor, then destroy any field, and remove the accessors.\r
362 if (column.hasEditor) {\r
363 if (column.hasEditor() && (field = column.getEditor())) {\r
364 field.destroy();\r
365 }\r
366 this.removeFieldAccessors(column);\r
367 }\r
368 }\r
369 },\r
370\r
371 /**\r
372 * @private\r
373 */\r
374 onColumnResize: function(ct, column, width) {\r
375 if (column.isHeader) {\r
376 var me = this,\r
377 editor = me.getEditor();\r
378\r
379 if (editor) {\r
380 editor.onColumnResize(column, width);\r
381 }\r
382 }\r
383 },\r
384\r
385 /**\r
386 * @private\r
387 */\r
388 onColumnHide: function(ct, column) {\r
389 // no isHeader check here since its already a columnhide event.\r
390 var me = this,\r
391 editor = me.getEditor();\r
392\r
393 if (editor) {\r
394 editor.onColumnHide(column);\r
395 }\r
396 },\r
397\r
398 /**\r
399 * @private\r
400 */\r
401 onColumnShow: function(ct, column) {\r
402 // no isHeader check here since its already a columnshow event.\r
403 var me = this,\r
404 editor = me.getEditor();\r
405\r
406 if (editor) {\r
407 editor.onColumnShow(column);\r
408 }\r
409 },\r
410\r
411 /**\r
412 * @private\r
413 */\r
414 onColumnMove: function(ct, column, fromIdx, toIdx) {\r
415 // no isHeader check here since its already a columnmove event.\r
416 var me = this,\r
417 editor = me.getEditor();\r
418\r
419 // Inject field accessors on move because if the move FROM the main headerCt and INTO a grouped header,\r
420 // the accessors will have been deleted but not added. They are added conditionally.\r
421 me.initFieldAccessors(column);\r
422\r
423 if (editor) {\r
424 // Must adjust the toIdx to account for removal if moving rightwards\r
425 // because RowEditor.onColumnMove just calls Container.move which does not do this.\r
426 editor.onColumnMove(column, fromIdx, toIdx);\r
427 }\r
428 },\r
429\r
430 /**\r
431 * @private\r
432 */\r
433 setColumnField: function(column, field) {\r
434 var me = this,\r
435 editor = me.getEditor();\r
436\r
437 if (editor) {\r
438 // Remove the old editor and destroy it.\r
439 editor.destroyColumnEditor(column);\r
440 }\r
441\r
442 me.callParent(arguments);\r
443\r
444 if (editor) {\r
445 editor.insertColumnEditor(column);\r
446 }\r
447 },\r
448\r
449 createColumnField: function(column, defaultField) {\r
450 var editor = this.editor,\r
451 def;\r
452\r
453 if (editor) {\r
454 def = editor.getDefaultFieldCfg();\r
455 }\r
456\r
457 return this.callParent([column, defaultField || def]);\r
458 }\r
459});\r