PDA

View Full Version : Ext.ux.grid.RowEditor with extra fields



OutpostMM
10 Sep 2010, 4:15 PM
This is a modification of the row editor example here:

http://dev.sencha.com/deploy/dev/examples/grid/row-editor.html

The example there lets you edit all of the fields in the grid. My modifications are for editing extra fields in the record that are not displayed in the grid columns. Originally I just wanted to edit the row body as a text area, but I extended this to account for additional fields.

Here is my version of the RowEditor.js file, I have marked my changes with // extraFields // comments:


/*!
* Ext JS Library 3.2.1
* Copyright(c) 2006-2010 Ext JS, Inc.
* licensing@extjs.com
* http://www.extjs.com/license
*/
Ext.ns('Ext.ux.grid');

/**
* @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
*/
Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
floating: true,
shadow: false,
// extraFields //
layout: 'anchor',
//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,

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(
/**
* @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',
/**
* @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',
/**
* @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',
/**
* @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);
}
}

// 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, []));

// extraFields //
this.extraFields = [];
var fields = this.grid.getStore().fields.items;
for (var i = 0; i < fields.length; i++)
{
if (Ext.isDefined(fields[i]) && Ext.isDefined(fields[i].editor))
{
this.extraFields.push(fields[i]);
}
}
// extraFields //
},

beforedestroy: function() {
this.stopMonitoring();
this.grid.getStore().un('remove', this.onStoreRemove, this);
this.stopEditing(false);
Ext.destroy(this.btns, this.tooltip);
},

refreshFields: function(){
this.initFields();
this.verifyLayout();
},

isDirty: function(){
var dirty;

// extraFields //
this.items.items[0].items.each(function(f){
if(String(this.values[f.id]) !== String(f.getValue())){
dirty = true;
return false;
}
}, this);
if (this.extraFields.length > 0)
{
this.items.items[1].items.each(function(f){
if(String(this.values[f.id]) !== String(f.getValue())){
dirty = true;
return false;
}
}, this);
}
// extraFields //

return dirty;
},

startEditing: function(rowIndex, doFocus){
if(this.editing && this.isDirty()){
this.showTooltip(this.commitChangesText);
return;
}
if(Ext.isObject(rowIndex)){
rowIndex = this.grid.getStore().indexOf(rowIndex);
}
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();
}

// extraFields //
var cm = g.getColumnModel(), fields = this.items.items[0].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;
}
for (i = 0; i < this.extraFields.length; i++)
{
val = this.preEditValue(record, this.extraFields[i].name);
f = this.items.items[1].items.items[i];
f.setValue(val);
this.values[f.id] = Ext.isEmpty(val) ? '' : val;
}
// extraFields //

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);
}
}
},

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,
// extraFields //
fields = this.items.items[0].items.items;
//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;
}
}
}
}

// extraFields //
for (i = 0; i < this.extraFields.length; i++)
{
oldValue = r.data[this.extraFields[i].name];
value = this.postEditValue(this.items.items[1].items.items[i].getValue(), oldValue, r, this.extraFields[i].name);
if(String(oldValue) !== String(value)){
changes[this.extraFields[i].name] = value;
hasChange = true;
}
}
// extraFields //

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.isIE ? Ext.fly(row).getHeight() + 9 : undefined);
// extraFields //
var cm = this.grid.colModel, fields = this.items.items[0].items.items;
//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();
}
},

slideHide : function(){
this.hide();
},

initFields: function(){
var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
this.removeAll(false);

// extraFields //
var fieldCt = new Ext.Panel({
layout: 'hbox',
anchor: '100%',
border: false,
baseCls: 'x-row-editor'
});
this.add(fieldCt);
if (this.extraFields.length > 0)
{
var bodyCt = new Ext.Panel({
layout: 'form',
labelWidth: 100,
anchor: '100%',
border: false,
baseCls: 'x-row-editor'
});
this.add(bodyCt);
}
// extraFields //

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{
if (Ext.isIE) {
ed.margins = pm('0 0 2 0');
}
else {
ed.margins = pm('0 1 2 0');
}
}
ed.setWidth(cm.getColumnWidth(i));
ed.column = c;
if(ed.ownerCt !== this){
ed.on('focus', this.ensureVisible, this);
ed.on('specialkey', this.onKey, this);
}
// extraFields //
fieldCt.insert(i, ed);
//this.insert(i, ed);
}

// extraFields //
for (i = 0; i < this.extraFields.length; i++)
{
ed = Ext.ComponentMgr.create(this.extraFields[i].editor);
ed.dataIndex = this.extraFields[i].name;
if(ed.ownerCt !== this){
ed.on('focus', this.ensureVisible, this);
ed.on('specialkey', this.onKey, this);
}
bodyCt.add(ed);
}
// extraFields //

this.initialized = true;
},

onKey: function(f, e){
if(e.getKey() === e.ENTER){
this.stopEditing(true);
e.stopPropagation();
}
},

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();
}
}
},

ensureVisible: function(editor){
// extraFields //
if(this.isVisible() && Ext.isDefined(editor.column)){
this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
}
else{
this.grid.getView().ensureVisible(this.rowIndex, 0, true);
}
// extraFields //
},

onRowClick: function(g, rowIndex, e){
if(this.clicksToEdit == 'auto'){
var li = this.lastClickIndex;
this.lastClickIndex = rowIndex;
if(li != rowIndex && !this.isVisible()){
return;
}
}
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])
}]
});
this.btns.render(this.bwrap);
},

afterRender: function(){
Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
this.positionButtons();
if(this.monitorValid){
this.startMonitoring();
}
},

onShow: function(){
if(this.monitorValid){
this.startMonitoring();
}
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);
},

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});
}
},

// private
preEditValue : function(r, field){
var value = r.data[field];
return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
},

// private
postEditValue : function(value, originalValue, r, field){
return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
},

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;
}
}
}
},

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;
}
}
}
return match;
},

startMonitoring : function(){
if(!this.bound && this.monitorValid){
this.bound = true;
Ext.TaskMgr.start({
run : this.bindHandler,
interval : this.monitorPoll || 200,
scope: this
});
}
},

stopMonitoring : function(){
this.bound = false;
if(this.tooltip){
this.tooltip.hide();
}
},

isValid: function(){
var valid = true;

// extraFields //
this.items.items[0].items.each(function(f){
if(!f.isValid(true)){
valid = false;
return false;
}
});
if (this.extraFields.length > 0)
{
this.items.items[1].items.each(function(f){
if(!f.isValid(true)){
valid = false;
return false;
}
});
}
// extraFields //

return valid;
},

// private
bindHandler : function(){
if(!this.bound){
return false; // stops binding
}
var valid = this.isValid();
if(!valid && this.errorSummary){
this.showTooltip(this.getErrorText().join(''));
}
this.btns.saveBtn.setDisabled(!valid);
this.fireEvent('validation', this, valid);
},

lastVisibleColumn : function() {
// extraFields //
var i = this.items.items[0].items.getCount() - 1,
c;
for(; i >= 0; i--) {
c = this.items.items[0].items.items[i];
if (!c.hidden) {
return c;
}
}
// extraFields //
},

showTooltip: function(msg){
var t = this.tooltip;
if(!t){
t = this.tooltip = new Ext.ToolTip({
maxWidth: 600,
cls: 'errorTip',
width: 300,
title: this.errorText,
autoHide: false,
anchor: 'left',
anchorToTarget: true,
mouseOffset: [40,0]
});
}
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.lastVisibleColumn().getEl());
if(!t.rendered){
t.show();
t.hide();
}
t.body.update(msg);
t.doAutoWidth(20);
t.show();
}else if(t.rendered){
t.hide();
}
},

getErrorText: function(){
var data = ['<ul>'];
// extraFields //
this.items.items[0].items.each(function(f){
if(!f.isValid(true)){
data.push('<li>', f.getActiveError(), '</li>');
}
});
if (this.extraFields.length > 0)
{
this.items.items[1].items.each(function(f){
if(!f.isValid(true)){
data.push('<li>', f.getActiveError(), '</li>');
}
});
}
// extraFields //
data.push('</ul>');
return data;
}
});
Ext.preg('roweditor', Ext.ux.grid.RowEditor);

In order to use the new fields, just add editors to the field definitions for the grid's store. E.g., for a grid that displays billing items, the store might look like this, where the description field is not displayed in the grid columns (although maybe as the row body), but still needs to be editable:


this.store = new Ext.data.JsonStore({
url: this.url,
baseParams: this.baseParams,
root: this.root,
autoDestroy: true,
autoLoad: true,
fields: [
{name: 'id'},
{name: 'title'},
{
name: 'description',
editor: {
xtype: 'textarea',
height: 70,
anchor: '100%',
hideLabel: true
}
},
{name: 'price'},
{name: 'quantity'}
]
});

Here is a full example based on the row editor example linked to above. Most of the code is included on this page, but check the CSS and Javascript links to make sure your resources are in the right spot:


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Row Editor</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<style type="text/css">
#loading-mask{
position:absolute;
left:0;
top:0;
width:100%;
height:100%;
z-index:20000;
background-color:white;
}
#loading{
position:absolute;
left:45%;
top:40%;
padding:2px;
z-index:20001;
height:auto;
width: 200px;
}
#loading a {
color:#225588;
}
#loading .loading-indicator{
background:white;
color:#444;
font:bold 13px tahoma,arial,helvetica;
padding:10px;
margin:0;
height:auto;
}
#loading-msg {
font: normal 10px arial,tahoma,sans-serif;
}

/*!
* Ext JS Library 3.2.1
* Copyright(c) 2006-2010 Ext JS, Inc.
* licensing@extjs.com
* http://www.extjs.com/license
*/
.ext-ie .x-row-editor .x-form-text {
margin:0 !important;
}
.x-row-editor-header {
height:2px;
overflow:hidden;
background: transparent url(images/row-editor-bg.gif) repeat-x 0 0;
}
.x-row-editor-footer {
height:2px;
overflow:hidden;
background: transparent url(images/row-editor-bg.gif) repeat-x 0 -2px;
}
.ext-ie .x-row-editor-footer {
margin-top:-1px;
}

.x-row-editor-body {
overflow:hidden;
zoom:1;
background: #ebf2fb;
padding-top:2px;
}
.x-row-editor .x-btns {
position:absolute;
top:28px;
left:20px;
padding-left:5px;
background: transparent url(images/row-editor-btns.gif) no-repeat 0 0;
}
.x-row-editor .x-btns .x-plain-bwrap {
padding-right:5px;
background: transparent url(images/row-editor-btns.gif) no-repeat right -31px;
}
.x-row-editor .x-btns .x-plain-body {
background: transparent url(images/row-editor-btns.gif) repeat-x 0 -62px;
height:31px;
}
.x-row-editor .x-btns .x-table-layout-cell {
padding:3px;
}

/* Fixes for IE6/7 trigger fields */
.ext-ie6 .x-row-editor .x-form-field-wrap .x-form-trigger, .ext-ie7 .x-row-editor .x-form-field-wrap .x-form-trigger {
top: 1px;
}

.ext-ie6 .x-row-editor .x-form-field-trigger-wrap, .ext-ie7 .x-row-editor .x-form-field-trigger-wrap {
margin-top: -1px;
}

.errorTip .x-tip-body ul{
list-style-type:disc;
margin-left:15px;
}
</style>
</head>
<body>
<div id="loading-mask" style=""></div>
<div id="loading">
<div class="loading-indicator"><img src="images/extanim32.gif" style="width:32px;height:32px;margin-right:8px;float:left;vertical-align:top;">Loading...<br><span id="loading-msg">Loading styles and images...</span></div>
</div>



<script type="text/javascript">document.getElementById('loading-msg').innerHTML = 'Loading Core API...';</script>
<script type="text/javascript" src="ext/adapter/ext/ext-base-debug.js"></script>
<script type="text/javascript">document.getElementById('loading-msg').innerHTML = 'Loading UI Components...';</script>
<script type="text/javascript" src="ext/ext-all-debug.js"></script>
<link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css">
<script type="text/javascript">document.getElementById('loading-msg').innerHTML = 'Loading Extensions...';</script>

<script type="text/javascript">
/*!
* Ext JS Library 3.2.1
* Copyright(c) 2006-2010 Ext JS, Inc.
* licensing@extjs.com
* http://www.extjs.com/license
*/
Ext.ns('Ext.ux.grid');

/**
* @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
*/
Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
floating: true,
shadow: false,
// extraFields //
layout: 'anchor',
//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,

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(
/**
* @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',
/**
* @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',
/**
* @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',
/**
* @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);
}
}

// 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, []));

// extraFields //
this.extraFields = [];
var fields = this.grid.getStore().fields.items;
for (var i = 0; i < fields.length; i++)
{
if (Ext.isDefined(fields[i]) && Ext.isDefined(fields[i].editor))
{
this.extraFields.push(fields[i]);
}
}
// extraFields //
},

beforedestroy: function() {
this.stopMonitoring();
this.grid.getStore().un('remove', this.onStoreRemove, this);
this.stopEditing(false);
Ext.destroy(this.btns, this.tooltip);
},

refreshFields: function(){
this.initFields();
this.verifyLayout();
},

isDirty: function(){
var dirty;

// extraFields //
this.items.items[0].items.each(function(f){
if(String(this.values[f.id]) !== String(f.getValue())){
dirty = true;
return false;
}
}, this);
if (this.extraFields.length > 0)
{
this.items.items[1].items.each(function(f){
if(String(this.values[f.id]) !== String(f.getValue())){
dirty = true;
return false;
}
}, this);
}
// extraFields //

return dirty;
},

startEditing: function(rowIndex, doFocus){
if(this.editing && this.isDirty()){
this.showTooltip(this.commitChangesText);
return;
}
if(Ext.isObject(rowIndex)){
rowIndex = this.grid.getStore().indexOf(rowIndex);
}
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();
}

// extraFields //
var cm = g.getColumnModel(), fields = this.items.items[0].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;
}
for (i = 0; i < this.extraFields.length; i++)
{
val = this.preEditValue(record, this.extraFields[i].name);
f = this.items.items[1].items.items[i];
f.setValue(val);
this.values[f.id] = Ext.isEmpty(val) ? '' : val;
}
// extraFields //

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);
}
}
},

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,
// extraFields //
fields = this.items.items[0].items.items;
//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;
}
}
}
}

// extraFields //
for (i = 0; i < this.extraFields.length; i++)
{
oldValue = r.data[this.extraFields[i].name];
value = this.postEditValue(this.items.items[1].items.items[i].getValue(), oldValue, r, this.extraFields[i].name);
if(String(oldValue) !== String(value)){
changes[this.extraFields[i].name] = value;
hasChange = true;
}
}
// extraFields //

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.isIE ? Ext.fly(row).getHeight() + 9 : undefined);
// extraFields //
var cm = this.grid.colModel, fields = this.items.items[0].items.items;
//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();
}
},

slideHide : function(){
this.hide();
},

initFields: function(){
var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
this.removeAll(false);

// extraFields //
var fieldCt = new Ext.Panel({
layout: 'hbox',
anchor: '100%',
border: false,
baseCls: 'x-row-editor'
});
this.add(fieldCt);
if (this.extraFields.length > 0)
{
var bodyCt = new Ext.Panel({
layout: 'form',
labelWidth: 100,
anchor: '100%',
border: false,
baseCls: 'x-row-editor'
});
this.add(bodyCt);
}
// extraFields //

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{
if (Ext.isIE) {
ed.margins = pm('0 0 2 0');
}
else {
ed.margins = pm('0 1 2 0');
}
}
ed.setWidth(cm.getColumnWidth(i));
ed.column = c;
if(ed.ownerCt !== this){
ed.on('focus', this.ensureVisible, this);
ed.on('specialkey', this.onKey, this);
}
// extraFields //
fieldCt.insert(i, ed);
//this.insert(i, ed);
}

// extraFields //
for (i = 0; i < this.extraFields.length; i++)
{
ed = Ext.ComponentMgr.create(this.extraFields[i].editor);
ed.dataIndex = this.extraFields[i].name;
if(ed.ownerCt !== this){
ed.on('focus', this.ensureVisible, this);
ed.on('specialkey', this.onKey, this);
}
bodyCt.add(ed);
}
// extraFields //

this.initialized = true;
},

onKey: function(f, e){
if(e.getKey() === e.ENTER){
this.stopEditing(true);
e.stopPropagation();
}
},

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();
}
}
},

ensureVisible: function(editor){
// extraFields //
if(this.isVisible() && Ext.isDefined(editor.column)){
this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
}
else{
this.grid.getView().ensureVisible(this.rowIndex, 0, true);
}
// extraFields //
},

onRowClick: function(g, rowIndex, e){
if(this.clicksToEdit == 'auto'){
var li = this.lastClickIndex;
this.lastClickIndex = rowIndex;
if(li != rowIndex && !this.isVisible()){
return;
}
}
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])
}]
});
this.btns.render(this.bwrap);
},

afterRender: function(){
Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
this.positionButtons();
if(this.monitorValid){
this.startMonitoring();
}
},

onShow: function(){
if(this.monitorValid){
this.startMonitoring();
}
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);
},

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});
}
},

// private
preEditValue : function(r, field){
var value = r.data[field];
return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
},

// private
postEditValue : function(value, originalValue, r, field){
return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
},

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;
}
}
}
},

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;
}
}
}
return match;
},

startMonitoring : function(){
if(!this.bound && this.monitorValid){
this.bound = true;
Ext.TaskMgr.start({
run : this.bindHandler,
interval : this.monitorPoll || 200,
scope: this
});
}
},

stopMonitoring : function(){
this.bound = false;
if(this.tooltip){
this.tooltip.hide();
}
},

isValid: function(){
var valid = true;

// extraFields //
this.items.items[0].items.each(function(f){
if(!f.isValid(true)){
valid = false;
return false;
}
});
if (this.extraFields.length > 0)
{
this.items.items[1].items.each(function(f){
if(!f.isValid(true)){
valid = false;
return false;
}
});
}
// extraFields //

return valid;
},

// private
bindHandler : function(){
if(!this.bound){
return false; // stops binding
}
var valid = this.isValid();
if(!valid && this.errorSummary){
this.showTooltip(this.getErrorText().join(''));
}
this.btns.saveBtn.setDisabled(!valid);
this.fireEvent('validation', this, valid);
},

lastVisibleColumn : function() {
// extraFields //
var i = this.items.items[0].items.getCount() - 1,
c;
for(; i >= 0; i--) {
c = this.items.items[0].items.items[i];
if (!c.hidden) {
return c;
}
}
// extraFields //
},

showTooltip: function(msg){
var t = this.tooltip;
if(!t){
t = this.tooltip = new Ext.ToolTip({
maxWidth: 600,
cls: 'errorTip',
width: 300,
title: this.errorText,
autoHide: false,
anchor: 'left',
anchorToTarget: true,
mouseOffset: [40,0]
});
}
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.lastVisibleColumn().getEl());
if(!t.rendered){
t.show();
t.hide();
}
t.body.update(msg);
t.doAutoWidth(20);
t.show();
}else if(t.rendered){
t.hide();
}
},

getErrorText: function(){
var data = ['<ul>'];
// extraFields //
this.items.items[0].items.each(function(f){
if(!f.isValid(true)){
data.push('<li>', f.getActiveError(), '</li>');
}
});
if (this.extraFields.length > 0)
{
this.items.items[1].items.each(function(f){
if(!f.isValid(true)){
data.push('<li>', f.getActiveError(), '</li>');
}
});
}
// extraFields //
data.push('</ul>');
return data;
}
});
Ext.preg('roweditor', Ext.ux.grid.RowEditor);
</script>

<script type="text/javascript">document.getElementById('loading-msg').innerHTML = 'Initializing...';</script>

<script type="text/javascript">
/*!
* Ext JS Library 3.2.1
* Copyright(c) 2006-2010 Ext JS, Inc.
* licensing@extjs.com
* http://www.extjs.com/license
*/
(function(){
var lasts = ['Jones', 'Smith', 'Lee', 'Wilson', 'Black', 'Williams', 'Lewis', 'Johnson', 'Foot', 'Little', 'Vee', 'Train', 'Hot', 'Mutt'];
var firsts = ['Fred', 'Julie', 'Bill', 'Ted', 'Jack', 'John', 'Mark', 'Mike', 'Chris', 'Bob', 'Travis', 'Kelly', 'Sara'];
var lastLen = lasts.length, firstLen = firsts.length;

Ext.ux.getRandomInt = function(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min;
}

Ext.ux.generateName = function(){
var name = firsts[Ext.ux.getRandomInt(0, firstLen-1)] + ' ' + lasts[Ext.ux.getRandomInt(0, lastLen-1)];
if(Ext.ux.generateName.usedNames[name]){
return Ext.ux.generateName();
}
Ext.ux.generateName.usedNames[name] = true;
return name;
}
Ext.ux.generateName.usedNames = {};

})();

Ext.onReady(function(){
Ext.destroy(Ext.get("loading"));
Ext.destroy(Ext.get("loading-mask"));

Ext.QuickTips.init();

var Employee = Ext.data.Record.create([{
name: 'name',
type: 'string'
}, {
name: 'email',
type: 'string'
}, {
name: 'start',
type: 'date',
dateFormat: 'n/j/Y'
},{
name: 'salary',
type: 'float'
},{
name: 'active',
type: 'bool'
},{
name: 'description',
type: 'string',
editor: {
xtype: 'textarea',
height: 70,
anchor: '100%',
hideLabel: true
}
},{
name: 'cbox',
type: 'bool',
editor: {
xtype: 'checkbox',
fieldLabel: 'Some Checkbox'
}
}]);


// hideous function to generate employee data
var genData = function(){
var data = [];
var s = new Date(2007, 0, 1);
var now = new Date(), i = -1;
while(s.getTime() < now.getTime()){
var ecount = Ext.ux.getRandomInt(0, 3);
for(var i = 0; i < ecount; i++){
var name = Ext.ux.generateName();
data.push({
start : s.clearTime(true).add(Date.DAY, Ext.ux.getRandomInt(0, 27)),
name : name,
email: name.toLowerCase().replace(' ', '.') + '@exttest.com',
active: true,
salary: Math.floor(Ext.ux.getRandomInt(35000, 85000)/1000)*1000,
description: 'Description for ' + name,
cbox: i % 2 == 0
});
}
s = s.add(Date.MONTH, 1);
}
return data;
}


var store = new Ext.data.GroupingStore({
reader: new Ext.data.JsonReader({fields: Employee}),
data: genData(),
sortInfo: {field: 'start', direction: 'ASC'}
});

var editor = new Ext.ux.grid.RowEditor({
saveText: 'Update'
});

var grid = new Ext.grid.GridPanel({
store: store,
width: 600,
region:'center',
margins: '0 5 5 5',
autoExpandColumn: 'name',
plugins: [editor],
view: new Ext.grid.GroupingView({
markDirty: false
}),
tbar: [{
iconCls: 'icon-user-add',
text: 'Add Employee',
handler: function(){
var e = new Employee({
name: 'New Guy',
email: 'new@exttest.com',
start: (new Date()).clearTime(),
salary: 50000,
active: true
});
editor.stopEditing();
store.insert(0, e);
grid.getView().refresh();
grid.getSelectionModel().selectRow(0);
editor.startEditing(0);
}
},{
ref: '../removeBtn',
iconCls: 'icon-user-delete',
text: 'Remove Employee',
disabled: true,
handler: function(){
editor.stopEditing();
var s = grid.getSelectionModel().getSelections();
for(var i = 0, r; r = s[i]; i++){
store.remove(r);
}
}
}],

columns: [
new Ext.grid.RowNumberer(),
{
id: 'name',
header: 'First Name',
dataIndex: 'name',
width: 220,
sortable: true,
editor: {
xtype: 'textfield',
allowBlank: false
}
},{
header: 'Email',
dataIndex: 'email',
width: 150,
sortable: true,
editor: {
xtype: 'textfield',
allowBlank: false,
vtype: 'email'
}
},{
xtype: 'datecolumn',
header: 'Start Date',
dataIndex: 'start',
format: 'm/d/Y',
width: 100,
sortable: true,
groupRenderer: Ext.util.Format.dateRenderer('M y'),
editor: {
xtype: 'datefield',
allowBlank: false,
minValue: '01/01/2006',
minText: 'Can\'t have a start date before the company existed!',
maxValue: (new Date()).format('m/d/Y')
}
},{
xtype: 'numbercolumn',
header: 'Salary',
dataIndex: 'salary',
format: '$0,0.00',
width: 100,
sortable: true,
editor: {
xtype: 'numberfield',
allowBlank: false,
minValue: 1,
maxValue: 150000
}
},{
xtype: 'booleancolumn',
header: 'Active',
dataIndex: 'active',
align: 'center',
width: 50,
trueText: 'Yes',
falseText: 'No',
editor: {
xtype: 'checkbox'
}
}]
});

var cstore = new Ext.data.JsonStore({
fields:['month', 'employees', 'salary'],
data:[],
refreshData: function(){
var o = {}, data = [];
var s = new Date(2007, 0, 1);
var now = new Date(), i = -1;
while(s.getTime() < now.getTime()){
var m = {
month: s.format('M y'),
employees: 0,
salary: 0,
index: ++i
}
data.push(m);
o[m.month] = m;
s = s.add(Date.MONTH, 1);
}
store.each(function(r){
var m = o[r.data.start.format('M y')];
for(var i = m.index, mo; mo = data[i]; i++){
mo.employees += 10000;
mo.salary += r.data.salary;
}
});
this.loadData(data);
}
});
cstore.refreshData();
store.on('add', cstore.refreshData, cstore);
store.on('remove', cstore.refreshData, cstore);
store.on('update', cstore.refreshData, cstore);

var layout = new Ext.Panel({
title: 'Employee Salary by Month',
layout: 'border',
layoutConfig: {
columns: 1
},
width:600,
height: 600,
items: [grid]
});
layout.render(Ext.getBody());

grid.getSelectionModel().on('selectionchange', function(sm){
grid.removeBtn.setDisabled(sm.getCount() < 1);
});
});
</script>

<h1>Row Editor Grid Example</h1>
<p>
This example shows how to create a grid with inline row based editing using a custom row-editor - now with extra fields!
</p>

</body>
</html>