PDA

View Full Version : [3.0] RowEditor ux - Modified version



btcguy
12 Aug 2009, 5:52 AM
Hi everyone, i've made some changes for the ux RowEditor; just go easy on me because, I had to make this work as i intended, so i don't know if there are any coding rules around, i just modified the extension, and may not be so compliant to ext3 standards /:)

Anyways, it's here for those who need it....

[What's new?]

+ on ENTER keypress it checks if the form is valid first (in a dirty way)
+ Handles events for save/cancel


// Modified: 2009-08-11, added stuff marked with a +, or inside a block, easy to spot ;)

/*!
* Ext JS Library 3.0.0
* Copyright(c) 2006-2009 Ext JS, LLC
* 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,
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,

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 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',

/**
* @event cancel
* When user clicks the cancel button
* @param {Ext.ux.grid.RowEditor} roweditor This object
* @param {Ext.data.Record} r The Record that was edited.
*/
'cancel',

/**
* @event save
* When user clicks the save button
* @param {Ext.ux.grid.RowEditor} roweditor This object
* @param {Ext.data.Record} r The Record that was edited.
*/
'save'
);
},

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

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

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

startEditing: function(rowIndex, doFocus){
/* First, make sure the row gets selected... to ensure
the 'save' & 'cancel' events actually pass the record param */
this.grid.getSelectionModel().selectRow(rowIndex); // + added


if(this.editing && this.isDirty()){
this.showTooltip('Debe Actualizar o Cancelar los cambios');
return;
}
this.editing = true;
if(typeof rowIndex == 'object'){
rowIndex = this.grid.getStore().indexOf(rowIndex);
}
if(this.fireEvent('beforeedit', this, rowIndex) !== false){
var g = this.grid, view = g.getView();
var row = view.getRow(rowIndex);
var 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] = 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);
}
}
},

stopEditing: function(saveChanges){
this.editing = false;
if(!this.isVisible()){
return;
}
if(saveChanges === false || !this.isValid()){
this.hide();
return;
}
var changes = {}, r = this.record, hasChange = false;
var 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];
var value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
if(String(oldValue) !== String(value)){
changes[dindex] = value;
hasChange = true;
}
}
}
}
if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
r.beginEdit();
for(var k in changes){
if(changes.hasOwnProperty(k)){
r.set(k, changes[k]);
}
}
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() + (Ext.isBorderBox ? 9 : 0) : undefined);
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 === 0){
adjust += 0; // outer padding
}
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);
for(var i = 0, len = cm.getColumnCount(); i < len; i++){
var c = cm.getColumnAt(i);
var 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);
}
this.initialized = true;
},

onKey: function(f, e) {
if (e.getKey() === e.ENTER && !this.btns.saveBtn.disabled) {
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){
if(this.isVisible()){
this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
}
},

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 || 'Save',
width: this.minButtonWidth,
// handler: this.stopEditing.createDelegate(this, [true])
handler: this.onSaveClick.createDelegate(this, [true]) // + added
}, {
xtype: 'button',
text: this.cancelText || 'Cancel',
width: this.minButtonWidth,
// handler: this.stopEditing.createDelegate(this, [false])
handler: this.onCancelClick.createDelegate(this, [false]) // + added
}]
});
this.btns.render(this.bwrap);
},




/*
* + begins
*/
onCancelClick: function(params) {
// do your business...
this.stopEditing(params);

// assuming the record being edited is selected... or should i just go for the 0 in store?
record = this.grid.getSelectionModel().getSelected();
this.fireEvent('cancel', this, record || null);
},

onSaveClick: function(params) {
this.stopEditing(params);

// lalala....
record = this.grid.getSelectionModel().getSelected();
this.fireEvent('save', this, record || null);
},
/*
* + ends
*/



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 h = this.el.dom.clientHeight;
var view = this.grid.getView();
var scroll = view.scroller.dom.scrollLeft;
var width = view.mainBody.getWidth();
var bw = this.btns.getWidth();
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;
if(pt){
index = this.getTargetColumnIndex(pt);
}
var cm = this.grid.getColumnModel();
for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
var c = cm.getColumnAt(i);
if(!c.hidden && c.getEditor()){
c.getEditor().focus();
break;
}
}
}
},

getTargetColumnIndex: function(pt){
var grid = this.grid, v = grid.view;
var x = pt.left;
var cms = grid.colModel.config;
var 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;
this.items.each(function(f){
if(!f.isValid(true)){
valid = false;
return false;
}
});
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);
},

showTooltip: function(msg){
var t = this.tooltip;
if(!t){
t = this.tooltip = new Ext.ToolTip({
maxWidth: 600,
cls: 'errorTip',
width: 300,
title: 'Error',
autoHide: false,
anchor: 'left',
anchorToTarget: true,
mouseOffset: [40,0]
});
}
t.initTarget(this.items.last().getEl());
if(!t.rendered){
t.show();
t.hide();
}
t.body.update(msg);
t.doAutoWidth();
t.show();
},

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

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

// IE7 repaint bug on initial show
if(Ext.isIE7 && !this.repainted){
this.el.repaint();
this.repainted = true;
}
}
});

Lobos
5 Sep 2009, 11:38 AM
Thanks for that, works good - I basicially wanted so that it would remove an added record if it was canceled.

Jochen
7 Sep 2009, 4:59 AM
Really great the RowEditor, nice and easy to use!

I've got a suggestion: in startEditing, supply the rowIndex to the call to initFields().
So one can easily override the initFields() to create different input elements per row.
The initFields() will get called on every startEdit if you do not set the initialized flag within initFields.


Ext.ux.grid.RowEditor:141ff

if(!this.initialized){
this.initFields(rowIndex);
}

avallejo
30 May 2010, 3:19 PM
Hi, very useful, thanks for the contribution. :)

skbach_pointyhat
8 Jun 2010, 11:16 AM
even easier is just to do this:

'canceledit':function(edtr, bool) {
var store = edtr.grid.getStore();
if(store.getAt(0).phantom) store.removeAt(0);
}