4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../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 if (me.isVisible() && me.context) {
450 me.renderColumnData(field, me.context.record);
454 loadRecord: function(record) {
457 form.loadRecord(record);
458 if (form.isValid()) {
464 // render display fields so they honor the column renderer/template
465 Ext.Array.forEach(me.query('>displayfield'), function(field) {
466 me.renderColumnData(field, record);
470 renderColumnData: function(field, record) {
472 grid = me.editingPlugin.grid,
473 headerCt = grid.headerCt,
476 column = me.columns.get(field.id),
477 value = record.get(column.dataIndex);
479 // honor our column's renderer (TemplateHeader sets renderer for us!)
480 if (column.renderer) {
481 var metaData = { tdCls: '', style: '' },
482 rowIdx = store.indexOf(record),
483 colIdx = headerCt.getHeaderIndex(column);
485 value = column.renderer.call(
486 column.scope || headerCt.ownerCt,
497 field.setRawValue(value);
498 field.resetOriginalValue();
501 beforeEdit: function() {
504 if (me.isVisible() && !me.autoCancel && me.isDirty()) {
510 <span id='Ext-grid-RowEditor-method-startEdit'> /**
511 </span> * Start editing the specified grid at the specified position.
512 * @param {Model} record The Store data record which backs the row to be edited.
513 * @param {Model} columnHeader The Column object defining the column to be edited.
515 startEdit: function(record, columnHeader) {
517 grid = me.editingPlugin.grid,
518 view = grid.getView(),
520 context = me.context = Ext.apply(me.editingPlugin.context, {
521 view: grid.getView(),
525 // make sure our row is selected before editing
526 context.grid.getSelectionModel().select(record);
528 // Reload the record data
529 me.loadRecord(record);
531 if (!me.isVisible()) {
533 me.focusContextCell();
536 callback: this.focusContextCell
541 // Focus the cell on start edit based upon the current context
542 focusContextCell: function() {
543 var field = this.getEditor(this.context.colIdx);
544 if (field && field.focus) {
549 cancelEdit: function() {
558 completeEdit: function() {
562 if (!form.isValid()) {
566 form.updateRecord(me.context.record);
573 me.callParent(arguments);
579 me.callParent(arguments);
581 me.invalidateScroller();
583 me.context.view.focus();
588 isDirty: function() {
591 return form.isDirty();
594 getToolTip: function() {
599 tip = me.tooltip = Ext.createWidget('tooltip', {
600 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
601 title: me.errorsText,
604 closeAction: 'disable',
611 hideToolTip: function() {
613 tip = me.getToolTip();
617 me.hiddenTip = false;
620 showToolTip: function() {
622 tip = me.getToolTip(),
623 context = me.context,
624 row = Ext.get(context.row),
625 viewEl = context.grid.view.el;
628 tip.showAt([-10000, -10000]);
629 tip.body.update(me.getErrors());
630 tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
636 repositionTip: function() {
638 tip = me.getToolTip(),
639 context = me.context,
640 row = Ext.get(context.row),
641 viewEl = context.grid.view.el,
642 viewHeight = viewEl.getHeight(),
643 viewTop = me.lastScrollTop,
644 viewBottom = viewTop + viewHeight,
645 rowHeight = row.getHeight(),
646 rowTop = row.dom.offsetTop,
647 rowBottom = rowTop + rowHeight;
649 if (rowBottom > viewTop && rowTop < viewBottom) {
651 me.hiddenTip = false;
658 getErrors: function() {
660 dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
663 Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
664 errors = errors.concat(
665 Ext.Array.map(field.getErrors(), function(e) {
666 return '<li>' + e + '</li>';
671 return dirtyText + '<ul>' + errors.join('') + '</ul>';
674 invalidateScroller: function() {
676 context = me.context,
677 scroller = context.grid.verticalScroller;
680 scroller.invalidate();