547 lines
17 KiB
JavaScript
547 lines
17 KiB
JavaScript
/*
|
|
This file is part of Ext JS 3.4
|
|
|
|
Copyright (c) 2011-2013 Sencha Inc
|
|
|
|
Contact: http://www.sencha.com/contact
|
|
|
|
GNU General Public License Usage
|
|
This file may be used under the terms of the GNU General Public License version 3.0 as
|
|
published by the Free Software Foundation and appearing in the file LICENSE included in the
|
|
packaging of this file.
|
|
|
|
Please review the following information to ensure the GNU General Public License version 3.0
|
|
requirements will be met: http://www.gnu.org/copyleft/gpl.html.
|
|
|
|
If you are unsure which license is appropriate for your use, please contact the sales department
|
|
at http://www.sencha.com/contact.
|
|
|
|
Build date: 2013-04-03 15:07:25
|
|
*/
|
|
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,
|
|
|
|
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, []));
|
|
},
|
|
|
|
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;
|
|
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){
|
|
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();
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
},
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
} else {
|
|
this.fireEvent('canceledit', this, false);
|
|
}
|
|
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);
|
|
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);
|
|
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);
|
|
}
|
|
this.insert(i, ed);
|
|
}
|
|
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){
|
|
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,
|
|
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;
|
|
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);
|
|
},
|
|
|
|
lastVisibleColumn : function() {
|
|
var i = this.items.getCount() - 1,
|
|
c;
|
|
for(; i >= 0; i--) {
|
|
c = this.items.items[i];
|
|
if (!c.hidden) {
|
|
return c;
|
|
}
|
|
}
|
|
},
|
|
|
|
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>'];
|
|
this.items.each(function(f){
|
|
if(!f.isValid(true)){
|
|
data.push('<li>', f.getActiveError(), '</li>');
|
|
}
|
|
});
|
|
data.push('</ul>');
|
|
return data;
|
|
}
|
|
});
|
|
Ext.preg('roweditor', Ext.ux.grid.RowEditor);
|