+<!DOCTYPE html>
<html>
<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>The source code</title>
- <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
- <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
+ <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
+ <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
+ <style type="text/css">
+ .highlight { display: block; background-color: #ddd; }
+ </style>
+ <script type="text/javascript">
+ function highlight() {
+ document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
+ }
+ </script>
</head>
-<body onload="prettyPrint();">
- <pre class="prettyprint lang-js">Ext.ns('Ext.ux.grid');
-
-<div id="cls-Ext.ux.grid.RowEditor"></div>/**
- * @class Ext.ux.grid.RowEditor
- * @extends Ext.Panel
- * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
- * A validation mode may be enabled which uses AnchorTips to notify the user of all
- * validation errors at once.
- *
- * @ptype roweditor
+<body onload="prettyPrint(); highlight();">
+ <pre class="prettyprint lang-js">// Currently has the following issues:
+// - Does not handle postEditValue
+// - Fields without editors need to sync with their values in Store
+// - starting to edit another record while already editing and dirty should probably prevent it
+// - aggregating validation messages
+// - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
+// - layout issues when changing sizes/width while hidden (layout bug)
+
+<span id='Ext-grid-RowEditor'>/**
+</span> * @class Ext.grid.RowEditor
+ * @extends Ext.form.Panel
+ *
+ * Internal utility class used to provide row editing functionality. For developers, they should use
+ * the RowEditing plugin to use this functionality with a grid.
+ *
+ * @ignore
*/
-Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
- floating: true,
- shadow: false,
- layout: 'hbox',
- cls: 'x-small-editor',
- buttonAlign: 'center',
- baseCls: 'x-row-editor',
- elements: 'header,footer,body',
- frameWidth: 5,
- buttonPad: 3,
- clicksToEdit: 'auto',
- monitorValid: true,
- focusDelay: 250,
- errorSummary: true,
+Ext.define('Ext.grid.RowEditor', {
+ extend: 'Ext.form.Panel',
+ requires: [
+ 'Ext.tip.ToolTip',
+ 'Ext.util.HashMap',
+ 'Ext.util.KeyNav'
+ ],
+
+ saveBtnText : 'Update',
+ cancelBtnText: 'Cancel',
+ errorsText: 'Errors',
+ dirtyText: 'You need to commit or cancel your changes',
+
+ lastScrollLeft: 0,
+ lastScrollTop: 0,
+
+ border: false,
- saveText: 'Save',
- cancelText: 'Cancel',
- commitChangesText: 'You need to commit or cancel your changes',
- errorText: 'Errors',
-
- defaults: {
- normalWidth: true
- },
-
- initComponent: function(){
- Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
- this.addEvents(
- <div id="event-Ext.ux.grid.RowEditor-beforeedit"></div>/**
- * @event beforeedit
- * Fired before the row editor is activated.
- * If the listener returns <tt>false</tt> the editor will not be activated.
- * @param {Ext.ux.grid.RowEditor} roweditor This object
- * @param {Number} rowIndex The rowIndex of the row just edited
- */
- 'beforeedit',
- <div id="event-Ext.ux.grid.RowEditor-canceledit"></div>/**
- * @event canceledit
- * Fired when the editor is cancelled.
- * @param {Ext.ux.grid.RowEditor} roweditor This object
- * @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid.
- */
- 'canceledit',
- <div id="event-Ext.ux.grid.RowEditor-validateedit"></div>/**
- * @event validateedit
- * Fired after a row is edited and passes validation.
- * If the listener returns <tt>false</tt> changes to the record will not be set.
- * @param {Ext.ux.grid.RowEditor} roweditor This object
- * @param {Object} changes Object with changes made to the record.
- * @param {Ext.data.Record} r The Record that was edited.
- * @param {Number} rowIndex The rowIndex of the row just edited
- */
- 'validateedit',
- <div id="event-Ext.ux.grid.RowEditor-afteredit"></div>/**
- * @event afteredit
- * Fired after a row is edited and passes validation. This event is fired
- * after the store's update event is fired with this edit.
- * @param {Ext.ux.grid.RowEditor} roweditor This object
- * @param {Object} changes Object with changes made to the record.
- * @param {Ext.data.Record} r The Record that was edited.
- * @param {Number} rowIndex The rowIndex of the row just edited
- */
- 'afteredit'
- );
- },
-
- init: function(grid){
- this.grid = grid;
- this.ownerCt = grid;
- if(this.clicksToEdit === 2){
- grid.on('rowdblclick', this.onRowDblClick, this);
- }else{
- grid.on('rowclick', this.onRowClick, this);
- if(Ext.isIE){
- grid.on('rowdblclick', this.onRowDblClick, this);
+ // Change the hideMode to offsets so that we get accurate measurements when
+ // the roweditor is hidden for laying out things like a TriggerField.
+ hideMode: 'offsets',
+
+ initComponent: function() {
+ var me = this,
+ form;
+
+ me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
+
+ me.layout = {
+ type: 'hbox',
+ align: 'middle'
+ };
+
+ // Maintain field-to-column mapping
+ // It's easy to get a field from a column, but not vice versa
+ me.columns = Ext.create('Ext.util.HashMap');
+ me.columns.getKey = function(columnHeader) {
+ var f;
+ if (columnHeader.getEditor) {
+ f = columnHeader.getEditor();
+ if (f) {
+ return f.id;
+ }
}
+ return columnHeader.id;
+ };
+ me.mon(me.columns, {
+ add: me.onFieldAdd,
+ remove: me.onFieldRemove,
+ replace: me.onFieldReplace,
+ scope: me
+ });
+
+ me.callParent(arguments);
+
+ if (me.fields) {
+ me.setField(me.fields);
+ delete me.fields;
}
- // stopEditing without saving when a record is removed from Store.
- grid.getStore().on('remove', function() {
- this.stopEditing(false);
- },this);
-
- grid.on({
- scope: this,
- keydown: this.onGridKey,
- columnresize: this.verifyLayout,
- columnmove: this.refreshFields,
- reconfigure: this.refreshFields,
- beforedestroy : this.beforedestroy,
- destroy : this.destroy,
- bodyscroll: {
- buffer: 250,
- fn: this.positionButtons
- }
- });
- grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
- grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
+ form = me.getForm();
+ form.trackResetOnLoad = true;
},
- beforedestroy: function() {
- this.grid.getStore().un('remove', this.onStoreRemove, this);
- this.stopEditing(false);
- Ext.destroy(this.btns);
+ onFieldChange: function() {
+ var me = this,
+ form = me.getForm(),
+ valid = form.isValid();
+ if (me.errorSummary && me.isVisible()) {
+ me[valid ? 'hideToolTip' : 'showToolTip']();
+ }
+ if (me.floatingButtons) {
+ me.floatingButtons.child('#update').setDisabled(!valid);
+ }
+ me.isValid = valid;
},
- refreshFields: function(){
- this.initFields();
- this.verifyLayout();
- },
+ afterRender: function() {
+ var me = this,
+ plugin = me.editingPlugin;
- isDirty: function(){
- var dirty;
- this.items.each(function(f){
- if(String(this.values[f.id]) !== String(f.getValue())){
- dirty = true;
- return false;
- }
- }, this);
- return dirty;
+ me.callParent(arguments);
+ me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
+
+ // Prevent from bubbling click events to the grid view
+ me.mon(me.el, {
+ click: Ext.emptyFn,
+ stopPropagation: true
+ });
+
+ me.el.swallowEvent([
+ 'keypress',
+ 'keydown'
+ ]);
+
+ me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
+ enter: plugin.completeEdit,
+ esc: plugin.onEscKey,
+ scope: plugin
+ });
+
+ me.mon(plugin.view, {
+ beforerefresh: me.onBeforeViewRefresh,
+ refresh: me.onViewRefresh,
+ scope: me
+ });
},
- startEditing: function(rowIndex, doFocus){
- if(this.editing && this.isDirty()){
- this.showTooltip(this.commitChangesText);
- return;
- }
- if(Ext.isObject(rowIndex)){
- rowIndex = this.grid.getStore().indexOf(rowIndex);
+ onBeforeViewRefresh: function(view) {
+ var me = this,
+ viewDom = view.el.dom;
+
+ if (me.el.dom.parentNode === viewDom) {
+ viewDom.removeChild(me.el.dom);
}
- if(this.fireEvent('beforeedit', this, rowIndex) !== false){
- this.editing = true;
- var g = this.grid, view = g.getView(),
- row = view.getRow(rowIndex),
- record = g.store.getAt(rowIndex);
-
- this.record = record;
- this.rowIndex = rowIndex;
- this.values = {};
- if(!this.rendered){
- this.render(view.getEditorParent());
- }
- var w = Ext.fly(row).getWidth();
- this.setSize(w);
- if(!this.initialized){
- this.initFields();
- }
- var cm = g.getColumnModel(), fields = this.items.items, f, val;
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- val = this.preEditValue(record, cm.getDataIndex(i));
- f = fields[i];
- f.setValue(val);
- this.values[f.id] = Ext.isEmpty(val) ? '' : val;
- }
- this.verifyLayout(true);
- if(!this.isVisible()){
- this.setPagePosition(Ext.fly(row).getXY());
- } else{
- this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
- }
- if(!this.isVisible()){
- this.show().doLayout();
- }
- if(doFocus !== false){
- this.doFocus.defer(this.focusDelay, this);
+ },
+
+ onViewRefresh: function(view) {
+ var me = this,
+ viewDom = view.el.dom,
+ context = me.context,
+ idx;
+
+ viewDom.appendChild(me.el.dom);
+
+ // Recover our row node after a view refresh
+ if (context && (idx = context.store.indexOf(context.record)) >= 0) {
+ context.row = view.getNode(idx);
+ me.reposition();
+ if (me.tooltip && me.tooltip.isVisible()) {
+ me.tooltip.setTarget(context.row);
}
+ } else {
+ me.editingPlugin.cancelEdit();
}
},
- stopEditing : function(saveChanges){
- this.editing = false;
- if(!this.isVisible()){
- return;
- }
- if(saveChanges === false || !this.isValid()){
- this.hide();
- this.fireEvent('canceledit', this, saveChanges === false);
- return;
- }
- var changes = {},
- r = this.record,
- hasChange = false,
- cm = this.grid.colModel,
- fields = this.items.items;
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- if(!cm.isHidden(i)){
- var dindex = cm.getDataIndex(i);
- if(!Ext.isEmpty(dindex)){
- var oldValue = r.data[dindex],
- value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
- if(String(oldValue) !== String(value)){
- changes[dindex] = value;
- hasChange = true;
- }
- }
+ onCtScroll: function(e, target) {
+ var me = this,
+ scrollTop = target.scrollTop,
+ scrollLeft = target.scrollLeft;
+
+ if (scrollTop !== me.lastScrollTop) {
+ me.lastScrollTop = scrollTop;
+ if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
+ me.repositionTip();
}
}
- if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
- r.beginEdit();
- Ext.iterate(changes, function(name, value){
- r.set(name, value);
- });
- r.endEdit();
- this.fireEvent('afteredit', this, changes, r, this.rowIndex);
- }
- this.hide();
- },
-
- verifyLayout: function(force){
- if(this.el && (this.isVisible() || force === true)){
- var row = this.grid.getView().getRow(this.rowIndex);
- this.setSize(Ext.fly(row).getWidth(), Ext.fly(row).getHeight() + 9);
- var cm = this.grid.colModel, fields = this.items.items;
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- if(!cm.isHidden(i)){
- var adjust = 0;
- if(i === (len - 1)){
- adjust += 3; // outer padding
- } else{
- adjust += 1;
- }
- fields[i].show();
- fields[i].setWidth(cm.getColumnWidth(i) - adjust);
- } else{
- fields[i].hide();
- }
- }
- this.doLayout();
- this.positionButtons();
+ if (scrollLeft !== me.lastScrollLeft) {
+ me.lastScrollLeft = scrollLeft;
+ me.reposition();
}
},
- slideHide : function(){
- this.hide();
+ onColumnAdd: function(column) {
+ this.setField(column);
},
- initFields: function(){
- var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
- this.removeAll(false);
- for(var i = 0, len = cm.getColumnCount(); i < len; i++){
- var c = cm.getColumnAt(i),
- ed = c.getEditor();
- if(!ed){
- ed = c.displayEditor || new Ext.form.DisplayField();
- }
- if(i == 0){
- ed.margins = pm('0 1 2 1');
- } else if(i == len - 1){
- ed.margins = pm('0 0 2 1');
- } else{
- ed.margins = pm('0 1 2');
- }
- ed.setWidth(cm.getColumnWidth(i));
- ed.column = c;
- if(ed.ownerCt !== this){
- ed.on('focus', this.ensureVisible, this);
- ed.on('specialkey', this.onKey, this);
- }
- this.insert(i, ed);
+ onColumnRemove: function(column) {
+ this.columns.remove(column);
+ },
+
+ onColumnResize: function(column, width) {
+ column.getEditor().setWidth(width - 2);
+ if (this.isVisible()) {
+ this.reposition();
}
- this.initialized = true;
},
- onKey: function(f, e){
- if(e.getKey() === e.ENTER){
- this.stopEditing(true);
- e.stopPropagation();
+ onColumnHide: function(column) {
+ column.getEditor().hide();
+ if (this.isVisible()) {
+ this.reposition();
}
},
- onGridKey: function(e){
- if(e.getKey() === e.ENTER && !this.isVisible()){
- var r = this.grid.getSelectionModel().getSelected();
- if(r){
- var index = this.grid.store.indexOf(r);
- this.startEditing(index);
- e.stopPropagation();
- }
+ onColumnShow: function(column) {
+ var field = column.getEditor();
+ field.setWidth(column.getWidth() - 2).show();
+ if (this.isVisible()) {
+ this.reposition();
}
},
- ensureVisible: function(editor){
- if(this.isVisible()){
- this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
+ onColumnMove: function(column, fromIdx, toIdx) {
+ var field = column.getEditor();
+ if (this.items.indexOf(field) != toIdx) {
+ this.move(fromIdx, toIdx);
}
},
- onRowClick: function(g, rowIndex, e){
- if(this.clicksToEdit == 'auto'){
- var li = this.lastClickIndex;
- this.lastClickIndex = rowIndex;
- if(li != rowIndex && !this.isVisible()){
- return;
- }
+ onFieldAdd: function(map, fieldId, column) {
+ var me = this,
+ colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
+ field = column.getEditor({ xtype: 'displayfield' });
+
+ me.insert(colIdx, field);
+ },
+
+ onFieldRemove: function(map, fieldId, column) {
+ var me = this,
+ field = column.getEditor(),
+ fieldEl = field.el;
+ me.remove(field, false);
+ if (fieldEl) {
+ fieldEl.remove();
}
- this.startEditing(rowIndex, false);
- this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
- },
-
- onRowDblClick: function(g, rowIndex, e){
- this.startEditing(rowIndex, false);
- this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
- },
-
- onRender: function(){
- Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
- this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
- this.btns = new Ext.Panel({
- baseCls: 'x-plain',
- cls: 'x-btns',
- elements:'body',
- layout: 'table',
- width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
- items: [{
- ref: 'saveBtn',
- itemId: 'saveBtn',
- xtype: 'button',
- text: this.saveText,
- width: this.minButtonWidth,
- handler: this.stopEditing.createDelegate(this, [true])
- }, {
- xtype: 'button',
- text: this.cancelText,
- width: this.minButtonWidth,
- handler: this.stopEditing.createDelegate(this, [false])
- }]
+ },
+
+ onFieldReplace: function(map, fieldId, column, oldColumn) {
+ var me = this;
+ me.onFieldRemove(map, fieldId, oldColumn);
+ },
+
+ clearFields: function() {
+ var me = this,
+ map = me.columns;
+ map.each(function(fieldId) {
+ map.removeAtKey(fieldId);
});
- this.btns.render(this.bwrap);
},
- afterRender: function(){
- Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
- this.positionButtons();
- if(this.monitorValid){
- this.startMonitoring();
+ getFloatingButtons: function() {
+ var me = this,
+ cssPrefix = Ext.baseCSSPrefix,
+ btnsCss = cssPrefix + 'grid-row-editor-buttons',
+ plugin = me.editingPlugin,
+ btns;
+
+ if (!me.floatingButtons) {
+ btns = me.floatingButtons = Ext.create('Ext.Container', {
+ renderTpl: [
+ '<div class="{baseCls}-ml"></div>',
+ '<div class="{baseCls}-mr"></div>',
+ '<div class="{baseCls}-bl"></div>',
+ '<div class="{baseCls}-br"></div>',
+ '<div class="{baseCls}-bc"></div>'
+ ],
+
+ renderTo: me.el,
+ baseCls: btnsCss,
+ layout: {
+ type: 'hbox',
+ align: 'middle'
+ },
+ defaults: {
+ margins: '0 1 0 1'
+ },
+ items: [{
+ itemId: 'update',
+ flex: 1,
+ xtype: 'button',
+ handler: plugin.completeEdit,
+ scope: plugin,
+ text: me.saveBtnText,
+ disabled: !me.isValid
+ }, {
+ flex: 1,
+ xtype: 'button',
+ handler: plugin.cancelEdit,
+ scope: plugin,
+ text: me.cancelBtnText
+ }]
+ });
+
+ // Prevent from bubbling click events to the grid view
+ me.mon(btns.el, {
+ // BrowserBug: Opera 11.01
+ // causes the view to scroll when a button is focused from mousedown
+ mousedown: Ext.emptyFn,
+ click: Ext.emptyFn,
+ stopEvent: true
+ });
+ }
+ return me.floatingButtons;
+ },
+
+ reposition: function(animateConfig) {
+ var me = this,
+ context = me.context,
+ row = context && Ext.get(context.row),
+ btns = me.getFloatingButtons(),
+ btnEl = btns.el,
+ grid = me.editingPlugin.grid,
+ viewEl = grid.view.el,
+ scroller = grid.verticalScroller,
+
+ // always get data from ColumnModel as its what drives
+ // the GridView's sizing
+ mainBodyWidth = grid.headerCt.getFullWidth(),
+ scrollerWidth = grid.getWidth(),
+
+ // use the minimum as the columns may not fill up the entire grid
+ // width
+ width = Math.min(mainBodyWidth, scrollerWidth),
+ scrollLeft = grid.view.el.dom.scrollLeft,
+ btnWidth = btns.getWidth(),
+ left = (width - btnWidth) / 2 + scrollLeft,
+ y, rowH, newHeight,
+
+ invalidateScroller = function() {
+ if (scroller) {
+ scroller.invalidate();
+ btnEl.scrollIntoView(viewEl, false);
+ }
+ if (animateConfig && animateConfig.callback) {
+ animateConfig.callback.call(animateConfig.scope || me);
+ }
+ };
+
+ // need to set both top/left
+ if (row && Ext.isElement(row.dom)) {
+ // Bring our row into view if necessary, so a row editor that's already
+ // visible and animated to the row will appear smooth
+ row.scrollIntoView(viewEl, false);
+
+ // Get the y position of the row relative to its top-most static parent.
+ // offsetTop will be relative to the table, and is incorrect
+ // when mixed with certain grid features (e.g., grouping).
+ y = row.getXY()[1] - 5;
+ rowH = row.getHeight();
+ newHeight = rowH + 10;
+
+ // IE doesn't set the height quite right.
+ // This isn't a border-box issue, it even happens
+ // in IE8 and IE7 quirks.
+ // TODO: Test in IE9!
+ if (Ext.isIE) {
+ newHeight += 2;
+ }
+
+ // Set editor height to match the row height
+ if (me.getHeight() != newHeight) {
+ me.setHeight(newHeight);
+ me.el.setLeft(0);
+ }
+
+ if (animateConfig) {
+ var animObj = {
+ to: {
+ y: y
+ },
+ duration: animateConfig.duration || 125,
+ listeners: {
+ afteranimate: function() {
+ invalidateScroller();
+ y = row.getXY()[1] - 5;
+ me.el.setY(y);
+ }
+ }
+ };
+ me.animate(animObj);
+ } else {
+ me.el.setY(y);
+ invalidateScroller();
+ }
+ }
+ if (me.getWidth() != mainBodyWidth) {
+ me.setWidth(mainBodyWidth);
}
+ btnEl.setLeft(left);
},
- onShow: function(){
- if(this.monitorValid){
- this.startMonitoring();
+ getEditor: function(fieldInfo) {
+ var me = this;
+
+ if (Ext.isNumber(fieldInfo)) {
+ // Query only form fields. This just future-proofs us in case we add
+ // other components to RowEditor later on. Don't want to mess with
+ // indices.
+ return me.query('>[isFormField]')[fieldInfo];
+ } else if (fieldInfo instanceof Ext.grid.column.Column) {
+ return fieldInfo.getEditor();
}
- Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
},
- onHide: function(){
- Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
- this.stopMonitoring();
- this.grid.getView().focusRow(this.rowIndex);
+ removeField: function(field) {
+ var me = this;
+
+ // Incase we pass a column instead, which is fine
+ field = me.getEditor(field);
+ me.mun(field, 'validitychange', me.onValidityChange, me);
+
+ // Remove field/column from our mapping, which will fire the event to
+ // remove the field from our container
+ me.columns.removeKey(field.id);
},
- positionButtons: function(){
- if(this.btns){
- var g = this.grid,
- h = this.el.dom.clientHeight,
- view = g.getView(),
- scroll = view.scroller.dom.scrollLeft,
- bw = this.btns.getWidth(),
- width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth());
-
- this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
+ setField: function(column) {
+ var me = this,
+ field;
+
+ if (Ext.isArray(column)) {
+ Ext.Array.forEach(column, me.setField, me);
+ return;
}
- },
- // private
- preEditValue : function(r, field){
- var value = r.data[field];
- return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
+ // Get a default display field if necessary
+ field = column.getEditor(null, {
+ xtype: 'displayfield',
+ // Default display fields will not return values. This is done because
+ // the display field will pick up column renderers from the grid.
+ getModelData: function() {
+ return null;
+ }
+ });
+ field.margins = '0 0 0 2';
+ field.setWidth(column.getDesiredWidth() - 2);
+ me.mon(field, 'change', me.onFieldChange, me);
+
+ // Maintain mapping of fields-to-columns
+ // This will fire events that maintain our container items
+ me.columns.add(field.id, column);
+ if (column.hidden) {
+ me.onColumnHide(column);
+ }
+ if (me.isVisible() && me.context) {
+ me.renderColumnData(field, me.context.record);
+ }
},
- // private
- postEditValue : function(value, originalValue, r, field){
- return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
+ loadRecord: function(record) {
+ var me = this,
+ form = me.getForm();
+ form.loadRecord(record);
+ if (form.isValid()) {
+ me.hideToolTip();
+ } else {
+ me.showToolTip();
+ }
+
+ // render display fields so they honor the column renderer/template
+ Ext.Array.forEach(me.query('>displayfield'), function(field) {
+ me.renderColumnData(field, record);
+ }, me);
},
- doFocus: function(pt){
- if(this.isVisible()){
- var index = 0,
- cm = this.grid.getColumnModel(),
- c;
- if(pt){
- index = this.getTargetColumnIndex(pt);
- }
- for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
- c = cm.getColumnAt(i);
- if(!c.hidden && c.getEditor()){
- c.getEditor().focus();
- break;
- }
- }
+ renderColumnData: function(field, record) {
+ var me = this,
+ grid = me.editingPlugin.grid,
+ headerCt = grid.headerCt,
+ view = grid.view,
+ store = view.store,
+ column = me.columns.get(field.id),
+ value = record.get(column.dataIndex);
+
+ // honor our column's renderer (TemplateHeader sets renderer for us!)
+ if (column.renderer) {
+ var metaData = { tdCls: '', style: '' },
+ rowIdx = store.indexOf(record),
+ colIdx = headerCt.getHeaderIndex(column);
+
+ value = column.renderer.call(
+ column.scope || headerCt.ownerCt,
+ value,
+ metaData,
+ record,
+ rowIdx,
+ colIdx,
+ store,
+ view
+ );
}
+
+ field.setRawValue(value);
+ field.resetOriginalValue();
},
- getTargetColumnIndex: function(pt){
- var grid = this.grid,
- v = grid.view,
- x = pt.left,
- cms = grid.colModel.config,
- i = 0,
- match = false;
- for(var len = cms.length, c; c = cms[i]; i++){
- if(!c.hidden){
- if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
- match = i;
- break;
- }
- }
+ beforeEdit: function() {
+ var me = this;
+
+ if (me.isVisible() && !me.autoCancel && me.isDirty()) {
+ me.showToolTip();
+ return false;
}
- return match;
},
- startMonitoring : function(){
- if(!this.bound && this.monitorValid){
- this.bound = true;
- Ext.TaskMgr.start({
- run : this.bindHandler,
- interval : this.monitorPoll || 200,
- scope: this
+<span id='Ext-grid-RowEditor-method-startEdit'> /**
+</span> * Start editing the specified grid at the specified position.
+ * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
+ * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
+ */
+ startEdit: function(record, columnHeader) {
+ var me = this,
+ grid = me.editingPlugin.grid,
+ view = grid.getView(),
+ store = grid.store,
+ context = me.context = Ext.apply(me.editingPlugin.context, {
+ view: grid.getView(),
+ store: store
+ });
+
+ // make sure our row is selected before editing
+ context.grid.getSelectionModel().select(record);
+
+ // Reload the record data
+ me.loadRecord(record);
+
+ if (!me.isVisible()) {
+ me.show();
+ me.focusContextCell();
+ } else {
+ me.reposition({
+ callback: this.focusContextCell
});
}
},
- stopMonitoring : function(){
- this.bound = false;
- if(this.tooltip){
- this.tooltip.hide();
+ // Focus the cell on start edit based upon the current context
+ focusContextCell: function() {
+ var field = this.getEditor(this.context.colIdx);
+ if (field && field.focus) {
+ field.focus();
}
},
- isValid: function(){
- var valid = true;
- this.items.each(function(f){
- if(!f.isValid(true)){
- valid = false;
- return false;
- }
- });
- return valid;
+ cancelEdit: function() {
+ var me = this,
+ form = me.getForm();
+
+ me.hide();
+ form.clearInvalid();
+ form.reset();
},
- // private
- bindHandler : function(){
- if(!this.bound){
- return false; // stops binding
+ completeEdit: function() {
+ var me = this,
+ form = me.getForm();
+
+ if (!form.isValid()) {
+ return;
}
- var valid = this.isValid();
- if(!valid && this.errorSummary){
- this.showTooltip(this.getErrorText().join(''));
+
+ form.updateRecord(me.context.record);
+ me.hide();
+ return true;
+ },
+
+ onShow: function() {
+ var me = this;
+ me.callParent(arguments);
+ me.reposition();
+ },
+
+ onHide: function() {
+ var me = this;
+ me.callParent(arguments);
+ me.hideToolTip();
+ me.invalidateScroller();
+ if (me.context) {
+ me.context.view.focus();
+ me.context = null;
}
- this.btns.saveBtn.setDisabled(!valid);
- this.fireEvent('validation', this, valid);
- },
-
- showTooltip: function(msg){
- var t = this.tooltip;
- if(!t){
- t = this.tooltip = new Ext.ToolTip({
- maxWidth: 600,
- cls: 'errorTip',
- width: 300,
- title: this.errorText,
+ },
+
+ isDirty: function() {
+ var me = this,
+ form = me.getForm();
+ return form.isDirty();
+ },
+
+ getToolTip: function() {
+ var me = this,
+ tip;
+
+ if (!me.tooltip) {
+ tip = me.tooltip = Ext.createWidget('tooltip', {
+ cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
+ title: me.errorsText,
autoHide: false,
- anchor: 'left',
- anchorToTarget: true,
- mouseOffset: [40,0]
+ closable: true,
+ closeAction: 'disable',
+ anchor: 'left'
});
}
- var v = this.grid.getView(),
- top = parseInt(this.el.dom.style.top, 10),
- scroll = v.scroller.dom.scrollTop,
- h = this.el.getHeight();
-
- if(top + h >= scroll){
- t.initTarget(this.items.last().getEl());
- if(!t.rendered){
- t.show();
- t.hide();
- }
- t.body.update(msg);
- t.doAutoWidth();
- t.show();
- }else if(t.rendered){
- t.hide();
+ return me.tooltip;
+ },
+
+ hideToolTip: function() {
+ var me = this,
+ tip = me.getToolTip();
+ if (tip.rendered) {
+ tip.disable();
}
+ me.hiddenTip = false;
},
- getErrorText: function(){
- var data = ['<ul>'];
- this.items.each(function(f){
- if(!f.isValid(true)){
- data.push('<li>', f.activeError, '</li>');
- }
- });
- data.push('</ul>');
- return data;
- }
-});
-Ext.preg('roweditor', Ext.ux.grid.RowEditor);
+ showToolTip: function() {
+ var me = this,
+ tip = me.getToolTip(),
+ context = me.context,
+ row = Ext.get(context.row),
+ viewEl = context.grid.view.el;
+
+ tip.setTarget(row);
+ tip.showAt([-10000, -10000]);
+ tip.body.update(me.getErrors());
+ tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
+ me.repositionTip();
+ tip.doLayout();
+ tip.enable();
+ },
-Ext.override(Ext.form.Field, {
- markInvalid : function(msg){
- if(!this.rendered || this.preventMark){ // not rendered
- return;
- }
- msg = msg || this.invalidText;
-
- var mt = this.getMessageHandler();
- if(mt){
- mt.mark(this, msg);
- }else if(this.msgTarget){
- this.el.addClass(this.invalidClass);
- var t = Ext.getDom(this.msgTarget);
- if(t){
- t.innerHTML = msg;
- t.style.display = this.msgDisplay;
- }
+ repositionTip: function() {
+ var me = this,
+ tip = me.getToolTip(),
+ context = me.context,
+ row = Ext.get(context.row),
+ viewEl = context.grid.view.el,
+ viewHeight = viewEl.getHeight(),
+ viewTop = me.lastScrollTop,
+ viewBottom = viewTop + viewHeight,
+ rowHeight = row.getHeight(),
+ rowTop = row.dom.offsetTop,
+ rowBottom = rowTop + rowHeight;
+
+ if (rowBottom > viewTop && rowTop < viewBottom) {
+ tip.show();
+ me.hiddenTip = false;
+ } else {
+ tip.hide();
+ me.hiddenTip = true;
}
- this.activeError = msg;
- this.fireEvent('invalid', this, msg);
- }
-});
+ },
-Ext.override(Ext.ToolTip, {
- doAutoWidth : function(){
- var bw = this.body.getTextWidth();
- if(this.title){
- bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
- }
- bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + 20;
- this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
+ getErrors: function() {
+ var me = this,
+ dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
+ errors = [];
+
+ Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
+ errors = errors.concat(
+ Ext.Array.map(field.getErrors(), function(e) {
+ return '<li>' + e + '</li>';
+ })
+ );
+ }, me);
+
+ return dirtyText + '<ul>' + errors.join('') + '</ul>';
+ },
+
+ invalidateScroller: function() {
+ var me = this,
+ context = me.context,
+ scroller = context.grid.verticalScroller;
- // IE7 repaint bug on initial show
- if(Ext.isIE7 && !this.repainted){
- this.el.repaint();
- this.repainted = true;
+ if (scroller) {
+ scroller.invalidate();
}
}
-});
-</pre>
+});</pre>
</body>
-</html>
\ No newline at end of file
+</html>