CellEditing.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>The source code</title>
  6. <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  7. <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  8. <style type="text/css">
  9. .highlight { display: block; background-color: #ddd; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  17. <body onload="prettyPrint(); highlight();">
  18. <pre class="prettyprint lang-js"><span id='Ext-grid-plugin-CellEditing'>/**
  19. </span> * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
  20. * cell will be editable at a time. 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. *
  23. * If an editor is not specified for a particular column then that cell will not be editable and it will
  24. * be skipped when activated via the mouse or the keyboard.
  25. *
  26. * The editor may be shared for each column in the grid, or a different one may be specified for each column.
  27. * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
  28. * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
  29. *
  30. * ## Example
  31. *
  32. * A grid with editor for the name and the email columns:
  33. *
  34. * @example
  35. * Ext.create('Ext.data.Store', {
  36. * storeId:'simpsonsStore',
  37. * fields:['name', 'email', 'phone'],
  38. * data:{'items':[
  39. * {&quot;name&quot;:&quot;Lisa&quot;, &quot;email&quot;:&quot;lisa@simpsons.com&quot;, &quot;phone&quot;:&quot;555-111-1224&quot;},
  40. * {&quot;name&quot;:&quot;Bart&quot;, &quot;email&quot;:&quot;bart@simpsons.com&quot;, &quot;phone&quot;:&quot;555-222-1234&quot;},
  41. * {&quot;name&quot;:&quot;Homer&quot;, &quot;email&quot;:&quot;home@simpsons.com&quot;, &quot;phone&quot;:&quot;555-222-1244&quot;},
  42. * {&quot;name&quot;:&quot;Marge&quot;, &quot;email&quot;:&quot;marge@simpsons.com&quot;, &quot;phone&quot;:&quot;555-222-1254&quot;}
  43. * ]},
  44. * proxy: {
  45. * type: 'memory',
  46. * reader: {
  47. * type: 'json',
  48. * root: 'items'
  49. * }
  50. * }
  51. * });
  52. *
  53. * Ext.create('Ext.grid.Panel', {
  54. * title: 'Simpsons',
  55. * store: Ext.data.StoreManager.lookup('simpsonsStore'),
  56. * columns: [
  57. * {header: 'Name', dataIndex: 'name', editor: 'textfield'},
  58. * {header: 'Email', dataIndex: 'email', flex:1,
  59. * editor: {
  60. * xtype: 'textfield',
  61. * allowBlank: false
  62. * }
  63. * },
  64. * {header: 'Phone', dataIndex: 'phone'}
  65. * ],
  66. * selType: 'cellmodel',
  67. * plugins: [
  68. * Ext.create('Ext.grid.plugin.CellEditing', {
  69. * clicksToEdit: 1
  70. * })
  71. * ],
  72. * height: 200,
  73. * width: 400,
  74. * renderTo: Ext.getBody()
  75. * });
  76. *
  77. * This requires a little explanation. We're passing in `store` and `columns` as normal, but
  78. * we also specify a {@link Ext.grid.column.Column#field field} on two of our columns. For the
  79. * Name column we just want a default textfield to edit the value, so we specify 'textfield'.
  80. * For the Email column we customized the editor slightly by passing allowBlank: false, which
  81. * will provide inline validation.
  82. *
  83. * To support cell editing, we also specified that the grid should use the 'cellmodel'
  84. * {@link Ext.grid.Panel#selType selType}, and created an instance of the CellEditing plugin,
  85. * which we configured to activate each editor after a single click.
  86. *
  87. */
  88. Ext.define('Ext.grid.plugin.CellEditing', {
  89. alias: 'plugin.cellediting',
  90. extend: 'Ext.grid.plugin.Editing',
  91. requires: ['Ext.grid.CellEditor', 'Ext.util.DelayedTask'],
  92. constructor: function() {
  93. <span id='Ext-grid-plugin-CellEditing-event-beforeedit'> /**
  94. </span> * @event beforeedit
  95. * Fires before cell editing is triggered. Return false from event handler to stop the editing.
  96. *
  97. * @param {Ext.grid.plugin.CellEditing} editor
  98. * @param {Object} e An edit event with the following properties:
  99. *
  100. * - grid - The grid
  101. * - record - The record being edited
  102. * - field - The field name being edited
  103. * - value - The value for the field being edited.
  104. * - row - The grid table row
  105. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
  106. * - rowIdx - The row index that is being edited
  107. * - colIdx - The column index that is being edited
  108. * - cancel - Set this to true to cancel the edit or return false from your handler.
  109. */
  110. <span id='Ext-grid-plugin-CellEditing-event-edit'> /**
  111. </span> * @event edit
  112. * Fires after a cell is edited. Usage example:
  113. *
  114. * grid.on('edit', function(editor, e) {
  115. * // commit the changes right after editing finished
  116. * e.record.commit();
  117. * });
  118. *
  119. * @param {Ext.grid.plugin.CellEditing} editor
  120. * @param {Object} e An edit event with the following properties:
  121. *
  122. * - grid - The grid
  123. * - record - The record that was edited
  124. * - field - The field name that was edited
  125. * - value - The value being set
  126. * - originalValue - The original value for the field, before the edit.
  127. * - row - The grid table row
  128. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
  129. * - rowIdx - The row index that was edited
  130. * - colIdx - The column index that was edited
  131. */
  132. <span id='Ext-grid-plugin-CellEditing-event-validateedit'> /**
  133. </span> * @event validateedit
  134. * Fires after a cell is edited, but before the value is set in the record. Return false from event handler to
  135. * cancel the change.
  136. *
  137. * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
  138. * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for
  139. * example) and then setting the field's new value in the Record directly:
  140. *
  141. * grid.on('validateedit', function(editor, e) {
  142. * var myTargetRow = 6;
  143. *
  144. * if (e.row == myTargetRow) {
  145. * e.cancel = true;
  146. * e.record.data[e.field] = e.value;
  147. * }
  148. * });
  149. *
  150. * @param {Ext.grid.plugin.CellEditing} editor
  151. * @param {Object} e An edit event with the following properties:
  152. *
  153. * - grid - The grid
  154. * - record - The record being edited
  155. * - field - The field name being edited
  156. * - value - The value being set
  157. * - originalValue - The original value for the field, before the edit.
  158. * - row - The grid table row
  159. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
  160. * - rowIdx - The row index that is being edited
  161. * - colIdx - The column index that is being edited
  162. * - cancel - Set this to true to cancel the edit or return false from your handler.
  163. */
  164. <span id='Ext-grid-plugin-CellEditing-event-canceledit'> /**
  165. </span> * @event canceledit
  166. * Fires when the user started editing a cell but then cancelled the edit.
  167. * @param {Ext.grid.plugin.CellEditing} editor
  168. * @param {Object} e An edit event with the following properties:
  169. *
  170. * - grid - The grid
  171. * - record - The record that was edited
  172. * - field - The field name that was edited
  173. * - value - The value being set
  174. * - row - The grid table row
  175. * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
  176. * - rowIdx - The row index that was edited
  177. * - colIdx - The column index that was edited
  178. */
  179. this.callParent(arguments);
  180. this.editors = new Ext.util.MixedCollection(false, function(editor) {
  181. return editor.editorId;
  182. });
  183. this.editTask = new Ext.util.DelayedTask();
  184. },
  185. onReconfigure: function(){
  186. this.editors.clear();
  187. this.callParent();
  188. },
  189. <span id='Ext-grid-plugin-CellEditing-method-destroy'> /**
  190. </span> * @private
  191. * AbstractComponent calls destroy on all its plugins at destroy time.
  192. */
  193. destroy: function() {
  194. var me = this;
  195. me.editTask.cancel();
  196. me.editors.each(Ext.destroy, Ext);
  197. me.editors.clear();
  198. me.callParent(arguments);
  199. },
  200. onBodyScroll: function() {
  201. var me = this,
  202. ed = me.getActiveEditor(),
  203. scroll = me.view.el.getScroll();
  204. // Scroll happened during editing...
  205. if (ed &amp;&amp; ed.editing) {
  206. // Terminate editing only on vertical scroll. Horiz scroll can be caused by tabbing between cells.
  207. if (scroll.top !== me.scroll.top) {
  208. if (ed.field) {
  209. if (ed.field.triggerBlur) {
  210. ed.field.triggerBlur();
  211. } else {
  212. ed.field.blur();
  213. }
  214. }
  215. }
  216. // Horiz scroll just requires that the editor be realigned.
  217. else {
  218. ed.realign();
  219. }
  220. }
  221. me.scroll = scroll;
  222. },
  223. // private
  224. // Template method called from base class's initEvents
  225. initCancelTriggers: function() {
  226. var me = this,
  227. grid = me.grid,
  228. view = grid.view;
  229. view.addElListener('mousewheel', me.cancelEdit, me);
  230. me.mon(view, 'bodyscroll', me.onBodyScroll, me);
  231. me.mon(grid, {
  232. columnresize: me.cancelEdit,
  233. columnmove: me.cancelEdit,
  234. scope: me
  235. });
  236. },
  237. isCellEditable: function(record, columnHeader) {
  238. var me = this,
  239. context = me.getEditingContext(record, columnHeader);
  240. if (me.grid.view.isVisible(true) &amp;&amp; context) {
  241. columnHeader = context.column;
  242. record = context.record;
  243. if (columnHeader &amp;&amp; me.getEditor(record, columnHeader)) {
  244. return true;
  245. }
  246. }
  247. },
  248. <span id='Ext-grid-plugin-CellEditing-method-startEdit'> /**
  249. </span> * Starts editing the specified record, using the specified Column definition to define which field is being edited.
  250. * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
  251. * @param {Ext.grid.column.Column} columnHeader The Column object defining the column to be edited.
  252. * @return `true` if editing was started, `false` otherwise.
  253. */
  254. startEdit: function(record, columnHeader) {
  255. var me = this,
  256. context = me.getEditingContext(record, columnHeader),
  257. value, ed;
  258. // Complete the edit now, before getting the editor's target
  259. // cell DOM element. Completing the edit causes a row refresh.
  260. // Also allows any post-edit events to take effect before continuing
  261. me.completeEdit();
  262. // Cancel editing if EditingContext could not be found (possibly because record has been deleted by an intervening listener), or if the grid view is not currently visible
  263. if (!context || !me.grid.view.isVisible(true)) {
  264. return false;
  265. }
  266. record = context.record;
  267. columnHeader = context.column;
  268. // See if the field is editable for the requested record
  269. if (columnHeader &amp;&amp; !columnHeader.getEditor(record)) {
  270. return false;
  271. }
  272. value = record.get(columnHeader.dataIndex);
  273. context.originalValue = context.value = value;
  274. if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', me, context) === false || context.cancel) {
  275. return false;
  276. }
  277. ed = me.getEditor(record, columnHeader);
  278. // Whether we are going to edit or not, ensure the edit cell is scrolled into view
  279. me.grid.view.cancelFocus();
  280. me.view.focusCell({
  281. row: context.rowIdx,
  282. column: context.colIdx
  283. });
  284. if (ed) {
  285. me.editTask.delay(15, me.showEditor, me, [ed, context, value]);
  286. return true;
  287. }
  288. return false;
  289. },
  290. showEditor: function(ed, context, value) {
  291. var me = this,
  292. record = context.record,
  293. columnHeader = context.column,
  294. sm = me.grid.getSelectionModel(),
  295. selection = sm.getCurrentPosition();
  296. me.context = context;
  297. me.setActiveEditor(ed);
  298. me.setActiveRecord(record);
  299. me.setActiveColumn(columnHeader);
  300. // Select cell on edit only if it's not the currently selected cell
  301. if (sm.selectByPosition &amp;&amp; (!selection || selection.column !== context.colIdx || selection.row !== context.rowIdx)) {
  302. sm.selectByPosition({
  303. row: context.rowIdx,
  304. column: context.colIdx
  305. });
  306. }
  307. ed.startEdit(me.getCell(record, columnHeader), value);
  308. me.editing = true;
  309. me.scroll = me.view.el.getScroll();
  310. },
  311. completeEdit: function() {
  312. var activeEd = this.getActiveEditor();
  313. if (activeEd) {
  314. activeEd.completeEdit();
  315. this.editing = false;
  316. }
  317. },
  318. // internal getters/setters
  319. setActiveEditor: function(ed) {
  320. this.activeEditor = ed;
  321. },
  322. getActiveEditor: function() {
  323. return this.activeEditor;
  324. },
  325. setActiveColumn: function(column) {
  326. this.activeColumn = column;
  327. },
  328. getActiveColumn: function() {
  329. return this.activeColumn;
  330. },
  331. setActiveRecord: function(record) {
  332. this.activeRecord = record;
  333. },
  334. getActiveRecord: function() {
  335. return this.activeRecord;
  336. },
  337. getEditor: function(record, column) {
  338. var me = this,
  339. editors = me.editors,
  340. editorId = column.getItemId(),
  341. editor = editors.getByKey(editorId);
  342. if (editor) {
  343. return editor;
  344. } else {
  345. editor = column.getEditor(record);
  346. if (!editor) {
  347. return false;
  348. }
  349. // Allow them to specify a CellEditor in the Column
  350. // Either way, the Editor is a floating Component, and must be attached to an ownerCt
  351. // which it uses to traverse upwards to find a ZIndexManager at render time.
  352. if (!(editor instanceof Ext.grid.CellEditor)) {
  353. editor = new Ext.grid.CellEditor({
  354. editorId: editorId,
  355. field: editor,
  356. ownerCt: me.grid
  357. });
  358. } else {
  359. editor.ownerCt = me.grid;
  360. }
  361. editor.editingPlugin = me;
  362. editor.isForTree = me.grid.isTree;
  363. editor.on({
  364. scope: me,
  365. specialkey: me.onSpecialKey,
  366. complete: me.onEditComplete,
  367. canceledit: me.cancelEdit
  368. });
  369. editors.add(editor);
  370. return editor;
  371. }
  372. },
  373. // inherit docs
  374. setColumnField: function(column, field) {
  375. var ed = this.editors.getByKey(column.getItemId());
  376. Ext.destroy(ed, column.field);
  377. this.editors.removeAtKey(column.getItemId());
  378. this.callParent(arguments);
  379. },
  380. <span id='Ext-grid-plugin-CellEditing-method-getCell'> /**
  381. </span> * Gets the cell (td) for a particular record and column.
  382. * @param {Ext.data.Model} record
  383. * @param {Ext.grid.column.Column} column
  384. * @private
  385. */
  386. getCell: function(record, column) {
  387. return this.grid.getView().getCell(record, column);
  388. },
  389. onSpecialKey: function(ed, field, e) {
  390. var me = this,
  391. grid = me.grid,
  392. sm;
  393. if (e.getKey() === e.TAB) {
  394. e.stopEvent();
  395. if (ed) {
  396. // Allow the field to act on tabs before onEditorTab, which ends
  397. // up calling completeEdit. This is useful for picker type fields.
  398. ed.onEditorTab(e);
  399. }
  400. sm = grid.getSelectionModel();
  401. if (sm.onEditorTab) {
  402. sm.onEditorTab(me, e);
  403. }
  404. }
  405. },
  406. onEditComplete : function(ed, value, startValue) {
  407. var me = this,
  408. grid = me.grid,
  409. activeColumn = me.getActiveColumn(),
  410. sm = grid.getSelectionModel(),
  411. record;
  412. if (activeColumn) {
  413. record = me.context.record;
  414. me.setActiveEditor(null);
  415. me.setActiveColumn(null);
  416. me.setActiveRecord(null);
  417. if (!me.validateEdit()) {
  418. return;
  419. }
  420. // Only update the record if the new value is different than the
  421. // startValue. When the view refreshes its el will gain focus
  422. if (!record.isEqual(value, startValue)) {
  423. record.set(activeColumn.dataIndex, value);
  424. }
  425. // Restore focus back to the view's element.
  426. if (sm.setCurrentPosition) {
  427. sm.setCurrentPosition(sm.getCurrentPosition());
  428. }
  429. grid.getView().getEl(activeColumn).focus();
  430. me.context.value = value;
  431. me.fireEvent('edit', me, me.context);
  432. }
  433. },
  434. <span id='Ext-grid-plugin-CellEditing-method-cancelEdit'> /**
  435. </span> * Cancels any active editing.
  436. */
  437. cancelEdit: function() {
  438. var me = this,
  439. activeEd = me.getActiveEditor(),
  440. viewEl = me.grid.getView().getEl(me.getActiveColumn());
  441. me.setActiveEditor(null);
  442. me.setActiveColumn(null);
  443. me.setActiveRecord(null);
  444. if (activeEd) {
  445. activeEd.cancelEdit();
  446. viewEl.focus();
  447. me.callParent(arguments);
  448. }
  449. },
  450. <span id='Ext-grid-plugin-CellEditing-method-startEditByPosition'> /**
  451. </span> * Starts editing by position (row/column)
  452. * @param {Object} position A position with keys of row and column.
  453. */
  454. startEditByPosition: function(position) {
  455. // Coerce the edit column to the closest visible column
  456. position.column = this.view.getHeaderCt().getVisibleHeaderClosestToIndex(position.column).getIndex();
  457. return this.startEdit(position.row, position.column);
  458. }
  459. });
  460. </pre>
  461. </body>
  462. </html>