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; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js">// Currently has the following issues:
19 // - Does not handle postEditValue
20 // - Fields without editors need to sync with their values in Store
21 // - starting to edit another record while already editing and dirty should probably prevent it
22 // - aggregating validation messages
23 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
24 // - layout issues when changing sizes/width while hidden (layout bug)
26 <span id='Ext-grid-RowEditor'>/**
27 </span> * @class Ext.grid.RowEditor
28 * @extends Ext.form.Panel
30 * Internal utility class used to provide row editing functionality. For developers, they should use
31 * the RowEditing plugin to use this functionality with a grid.
35 Ext.define('Ext.grid.RowEditor', {
36 extend: 'Ext.form.Panel',
43 saveBtnText : 'Update',
44 cancelBtnText: 'Cancel',
46 dirtyText: 'You need to commit or cancel your changes',
53 // Change the hideMode to offsets so that we get accurate measurements when
54 // the roweditor is hidden for laying out things like a TriggerField.
57 initComponent: function() {
61 me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
68 // Maintain field-to-column mapping
69 // It's easy to get a field from a column, but not vice versa
70 me.columns = Ext.create('Ext.util.HashMap');
71 me.columns.getKey = function(columnHeader) {
73 if (columnHeader.getEditor) {
74 f = columnHeader.getEditor();
79 return columnHeader.id;
83 remove: me.onFieldRemove,
84 replace: me.onFieldReplace,
88 me.callParent(arguments);
91 me.setField(me.fields);
96 form.trackResetOnLoad = true;
99 onFieldChange: function() {
102 valid = form.isValid();
103 if (me.errorSummary && me.isVisible()) {
104 me[valid ? 'hideToolTip' : 'showToolTip']();
106 if (me.floatingButtons) {
107 me.floatingButtons.child('#update').setDisabled(!valid);
112 afterRender: function() {
114 plugin = me.editingPlugin;
116 me.callParent(arguments);
117 me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
119 // Prevent from bubbling click events to the grid view
122 stopPropagation: true
130 me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
131 enter: plugin.completeEdit,
132 esc: plugin.onEscKey,
136 me.mon(plugin.view, {
137 beforerefresh: me.onBeforeViewRefresh,
138 refresh: me.onViewRefresh,
143 onBeforeViewRefresh: function(view) {
145 viewDom = view.el.dom;
147 if (me.el.dom.parentNode === viewDom) {
148 viewDom.removeChild(me.el.dom);
152 onViewRefresh: function(view) {
154 viewDom = view.el.dom,
155 context = me.context,
158 viewDom.appendChild(me.el.dom);
160 // Recover our row node after a view refresh
161 if (context && (idx = context.store.indexOf(context.record)) >= 0) {
162 context.row = view.getNode(idx);
164 if (me.tooltip && me.tooltip.isVisible()) {
165 me.tooltip.setTarget(context.row);
168 me.editingPlugin.cancelEdit();
172 onCtScroll: function(e, target) {
174 scrollTop = target.scrollTop,
175 scrollLeft = target.scrollLeft;
177 if (scrollTop !== me.lastScrollTop) {
178 me.lastScrollTop = scrollTop;
179 if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
183 if (scrollLeft !== me.lastScrollLeft) {
184 me.lastScrollLeft = scrollLeft;
189 onColumnAdd: function(column) {
190 this.setField(column);
193 onColumnRemove: function(column) {
194 this.columns.remove(column);
197 onColumnResize: function(column, width) {
198 column.getEditor().setWidth(width - 2);
199 if (this.isVisible()) {
204 onColumnHide: function(column) {
205 column.getEditor().hide();
206 if (this.isVisible()) {
211 onColumnShow: function(column) {
212 var field = column.getEditor();
213 field.setWidth(column.getWidth() - 2).show();
214 if (this.isVisible()) {
219 onColumnMove: function(column, fromIdx, toIdx) {
220 var field = column.getEditor();
221 if (this.items.indexOf(field) != toIdx) {
222 this.move(fromIdx, toIdx);
226 onFieldAdd: function(map, fieldId, column) {
228 colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
229 field = column.getEditor({ xtype: 'displayfield' });
231 me.insert(colIdx, field);
234 onFieldRemove: function(map, fieldId, column) {
236 field = column.getEditor(),
238 me.remove(field, false);
244 onFieldReplace: function(map, fieldId, column, oldColumn) {
246 me.onFieldRemove(map, fieldId, oldColumn);
249 clearFields: function() {
252 map.each(function(fieldId) {
253 map.removeAtKey(fieldId);
257 getFloatingButtons: function() {
259 cssPrefix = Ext.baseCSSPrefix,
260 btnsCss = cssPrefix + 'grid-row-editor-buttons',
261 plugin = me.editingPlugin,
264 if (!me.floatingButtons) {
265 btns = me.floatingButtons = Ext.create('Ext.Container', {
267 '<div class="{baseCls}-ml"></div>',
268 '<div class="{baseCls}-mr"></div>',
269 '<div class="{baseCls}-bl"></div>',
270 '<div class="{baseCls}-br"></div>',
271 '<div class="{baseCls}-bc"></div>'
287 handler: plugin.completeEdit,
289 text: me.saveBtnText,
290 disabled: !me.isValid
294 handler: plugin.cancelEdit,
296 text: me.cancelBtnText
300 // Prevent from bubbling click events to the grid view
302 // BrowserBug: Opera 11.01
303 // causes the view to scroll when a button is focused from mousedown
304 mousedown: Ext.emptyFn,
309 return me.floatingButtons;
312 reposition: function(animateConfig) {
314 context = me.context,
315 row = context && Ext.get(context.row),
316 btns = me.getFloatingButtons(),
318 grid = me.editingPlugin.grid,
319 viewEl = grid.view.el,
320 scroller = grid.verticalScroller,
322 // always get data from ColumnModel as its what drives
323 // the GridView's sizing
324 mainBodyWidth = grid.headerCt.getFullWidth(),
325 scrollerWidth = grid.getWidth(),
327 // use the minimum as the columns may not fill up the entire grid
329 width = Math.min(mainBodyWidth, scrollerWidth),
330 scrollLeft = grid.view.el.dom.scrollLeft,
331 btnWidth = btns.getWidth(),
332 left = (width - btnWidth) / 2 + scrollLeft,
335 invalidateScroller = function() {
337 scroller.invalidate();
338 btnEl.scrollIntoView(viewEl, false);
340 if (animateConfig && animateConfig.callback) {
341 animateConfig.callback.call(animateConfig.scope || me);
345 // need to set both top/left
346 if (row && Ext.isElement(row.dom)) {
347 // Bring our row into view if necessary, so a row editor that's already
348 // visible and animated to the row will appear smooth
349 row.scrollIntoView(viewEl, false);
351 // Get the y position of the row relative to its top-most static parent.
352 // offsetTop will be relative to the table, and is incorrect
353 // when mixed with certain grid features (e.g., grouping).
354 y = row.getXY()[1] - 5;
355 rowH = row.getHeight();
356 newHeight = rowH + 10;
358 // IE doesn't set the height quite right.
359 // This isn't a border-box issue, it even happens
360 // in IE8 and IE7 quirks.
361 // TODO: Test in IE9!
366 // Set editor height to match the row height
367 if (me.getHeight() != newHeight) {
368 me.setHeight(newHeight);
377 duration: animateConfig.duration || 125,
379 afteranimate: function() {
380 invalidateScroller();
381 y = row.getXY()[1] - 5;
389 invalidateScroller();
392 if (me.getWidth() != mainBodyWidth) {
393 me.setWidth(mainBodyWidth);
398 getEditor: function(fieldInfo) {
401 if (Ext.isNumber(fieldInfo)) {
402 // Query only form fields. This just future-proofs us in case we add
403 // other components to RowEditor later on. Don't want to mess with
405 return me.query('>[isFormField]')[fieldInfo];
406 } else if (fieldInfo instanceof Ext.grid.column.Column) {
407 return fieldInfo.getEditor();
411 removeField: function(field) {
414 // Incase we pass a column instead, which is fine
415 field = me.getEditor(field);
416 me.mun(field, 'validitychange', me.onValidityChange, me);
418 // Remove field/column from our mapping, which will fire the event to
419 // remove the field from our container
420 me.columns.removeKey(field.id);
423 setField: function(column) {
427 if (Ext.isArray(column)) {
428 Ext.Array.forEach(column, me.setField, me);
432 // Get a default display field if necessary
433 field = column.getEditor(null, {
434 xtype: 'displayfield',
435 // Default display fields will not return values. This is done because
436 // the display field will pick up column renderers from the grid.
437 getModelData: function() {
441 field.margins = '0 0 0 2';
442 field.setWidth(column.getDesiredWidth() - 2);
443 me.mon(field, 'change', me.onFieldChange, me);
445 // Maintain mapping of fields-to-columns
446 // This will fire events that maintain our container items
447 me.columns.add(field.id, column);
449 me.onColumnHide(column);
451 if (me.isVisible() && me.context) {
452 me.renderColumnData(field, me.context.record);
456 loadRecord: function(record) {
459 form.loadRecord(record);
460 if (form.isValid()) {
466 // render display fields so they honor the column renderer/template
467 Ext.Array.forEach(me.query('>displayfield'), function(field) {
468 me.renderColumnData(field, record);
472 renderColumnData: function(field, record) {
474 grid = me.editingPlugin.grid,
475 headerCt = grid.headerCt,
478 column = me.columns.get(field.id),
479 value = record.get(column.dataIndex);
481 // honor our column's renderer (TemplateHeader sets renderer for us!)
482 if (column.renderer) {
483 var metaData = { tdCls: '', style: '' },
484 rowIdx = store.indexOf(record),
485 colIdx = headerCt.getHeaderIndex(column);
487 value = column.renderer.call(
488 column.scope || headerCt.ownerCt,
499 field.setRawValue(value);
500 field.resetOriginalValue();
503 beforeEdit: function() {
506 if (me.isVisible() && !me.autoCancel && me.isDirty()) {
512 <span id='Ext-grid-RowEditor-method-startEdit'> /**
513 </span> * Start editing the specified grid at the specified position.
514 * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
515 * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
517 startEdit: function(record, columnHeader) {
519 grid = me.editingPlugin.grid,
520 view = grid.getView(),
522 context = me.context = Ext.apply(me.editingPlugin.context, {
523 view: grid.getView(),
527 // make sure our row is selected before editing
528 context.grid.getSelectionModel().select(record);
530 // Reload the record data
531 me.loadRecord(record);
533 if (!me.isVisible()) {
535 me.focusContextCell();
538 callback: this.focusContextCell
543 // Focus the cell on start edit based upon the current context
544 focusContextCell: function() {
545 var field = this.getEditor(this.context.colIdx);
546 if (field && field.focus) {
551 cancelEdit: function() {
560 completeEdit: function() {
564 if (!form.isValid()) {
568 form.updateRecord(me.context.record);
575 me.callParent(arguments);
581 me.callParent(arguments);
583 me.invalidateScroller();
585 me.context.view.focus();
590 isDirty: function() {
593 return form.isDirty();
596 getToolTip: function() {
601 tip = me.tooltip = Ext.createWidget('tooltip', {
602 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
603 title: me.errorsText,
606 closeAction: 'disable',
613 hideToolTip: function() {
615 tip = me.getToolTip();
619 me.hiddenTip = false;
622 showToolTip: function() {
624 tip = me.getToolTip(),
625 context = me.context,
626 row = Ext.get(context.row),
627 viewEl = context.grid.view.el;
630 tip.showAt([-10000, -10000]);
631 tip.body.update(me.getErrors());
632 tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
638 repositionTip: function() {
640 tip = me.getToolTip(),
641 context = me.context,
642 row = Ext.get(context.row),
643 viewEl = context.grid.view.el,
644 viewHeight = viewEl.getHeight(),
645 viewTop = me.lastScrollTop,
646 viewBottom = viewTop + viewHeight,
647 rowHeight = row.getHeight(),
648 rowTop = row.dom.offsetTop,
649 rowBottom = rowTop + rowHeight;
651 if (rowBottom > viewTop && rowTop < viewBottom) {
653 me.hiddenTip = false;
660 getErrors: function() {
662 dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
665 Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
666 errors = errors.concat(
667 Ext.Array.map(field.getErrors(), function(e) {
668 return '<li>' + e + '</li>';
673 return dirtyText + '<ul>' + errors.join('') + '</ul>';
676 invalidateScroller: function() {
678 context = me.context,
679 scroller = context.grid.verticalScroller;
682 scroller.invalidate();