]>
Commit | Line | Data |
---|---|---|
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 | |
52 | Ext.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 |