3 * Copyright(c) 2006-2010 Ext JS, LLC
5 * http://www.extjs.com/license
10 * @class Ext.ux.grid.RowEditor
12 * Plugin (ptype = 'roweditor') that adds the ability to rapidly edit full rows in a grid.
13 * A validation mode may be enabled which uses AnchorTips to notify the user of all
14 * validation errors at once.
18 Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
22 cls: 'x-small-editor',
23 buttonAlign: 'center',
24 baseCls: 'x-row-editor',
25 elements: 'header,footer,body',
35 commitChangesText: 'You need to commit or cancel your changes',
42 initComponent: function(){
43 Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
47 * Fired before the row editor is activated.
48 * If the listener returns <tt>false</tt> the editor will not be activated.
49 * @param {Ext.ux.grid.RowEditor} roweditor This object
50 * @param {Number} rowIndex The rowIndex of the row just edited
55 * Fired when the editor is cancelled.
56 * @param {Ext.ux.grid.RowEditor} roweditor This object
57 * @param {Boolean} forced True if the cancel button is pressed, false is the editor was invalid.
62 * Fired after a row is edited and passes validation.
63 * If the listener returns <tt>false</tt> changes to the record will not be set.
64 * @param {Ext.ux.grid.RowEditor} roweditor This object
65 * @param {Object} changes Object with changes made to the record.
66 * @param {Ext.data.Record} r The Record that was edited.
67 * @param {Number} rowIndex The rowIndex of the row just edited
72 * Fired after a row is edited and passes validation. This event is fired
73 * after the store's update event is fired with this edit.
74 * @param {Ext.ux.grid.RowEditor} roweditor This object
75 * @param {Object} changes Object with changes made to the record.
76 * @param {Ext.data.Record} r The Record that was edited.
77 * @param {Number} rowIndex The rowIndex of the row just edited
86 if(this.clicksToEdit === 2){
87 grid.on('rowdblclick', this.onRowDblClick, this);
89 grid.on('rowclick', this.onRowClick, this);
91 grid.on('rowdblclick', this.onRowDblClick, this);
95 // stopEditing without saving when a record is removed from Store.
96 grid.getStore().on('remove', function() {
97 this.stopEditing(false);
102 keydown: this.onGridKey,
103 columnresize: this.verifyLayout,
104 columnmove: this.refreshFields,
105 reconfigure: this.refreshFields,
106 beforedestroy : this.beforedestroy,
107 destroy : this.destroy,
110 fn: this.positionButtons
113 grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {delay:1});
114 grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
117 beforedestroy: function() {
118 this.grid.getStore().un('remove', this.onStoreRemove, this);
119 this.stopEditing(false);
120 Ext.destroy(this.btns);
123 refreshFields: function(){
130 this.items.each(function(f){
131 if(String(this.values[f.id]) !== String(f.getValue())){
139 startEditing: function(rowIndex, doFocus){
140 if(this.editing && this.isDirty()){
141 this.showTooltip(this.commitChangesText);
144 if(Ext.isObject(rowIndex)){
145 rowIndex = this.grid.getStore().indexOf(rowIndex);
147 if(this.fireEvent('beforeedit', this, rowIndex) !== false){
149 var g = this.grid, view = g.getView(),
150 row = view.getRow(rowIndex),
151 record = g.store.getAt(rowIndex);
153 this.record = record;
154 this.rowIndex = rowIndex;
157 this.render(view.getEditorParent());
159 var w = Ext.fly(row).getWidth();
161 if(!this.initialized){
164 var cm = g.getColumnModel(), fields = this.items.items, f, val;
165 for(var i = 0, len = cm.getColumnCount(); i < len; i++){
166 val = this.preEditValue(record, cm.getDataIndex(i));
169 this.values[f.id] = Ext.isEmpty(val) ? '' : val;
171 this.verifyLayout(true);
172 if(!this.isVisible()){
173 this.setPagePosition(Ext.fly(row).getXY());
175 this.el.setXY(Ext.fly(row).getXY(), {duration:0.15});
177 if(!this.isVisible()){
178 this.show().doLayout();
180 if(doFocus !== false){
181 this.doFocus.defer(this.focusDelay, this);
186 stopEditing : function(saveChanges){
187 this.editing = false;
188 if(!this.isVisible()){
191 if(saveChanges === false || !this.isValid()){
193 this.fireEvent('canceledit', this, saveChanges === false);
199 cm = this.grid.colModel,
200 fields = this.items.items;
201 for(var i = 0, len = cm.getColumnCount(); i < len; i++){
203 var dindex = cm.getDataIndex(i);
204 if(!Ext.isEmpty(dindex)){
205 var oldValue = r.data[dindex],
206 value = this.postEditValue(fields[i].getValue(), oldValue, r, dindex);
207 if(String(oldValue) !== String(value)){
208 changes[dindex] = value;
214 if(hasChange && this.fireEvent('validateedit', this, changes, r, this.rowIndex) !== false){
216 Ext.iterate(changes, function(name, value){
220 this.fireEvent('afteredit', this, changes, r, this.rowIndex);
225 verifyLayout: function(force){
226 if(this.el && (this.isVisible() || force === true)){
227 var row = this.grid.getView().getRow(this.rowIndex);
228 this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row).getHeight() + 9 : undefined);
229 var cm = this.grid.colModel, fields = this.items.items;
230 for(var i = 0, len = cm.getColumnCount(); i < len; i++){
234 adjust += 3; // outer padding
239 fields[i].setWidth(cm.getColumnWidth(i) - adjust);
245 this.positionButtons();
249 slideHide : function(){
253 initFields: function(){
254 var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
255 this.removeAll(false);
256 for(var i = 0, len = cm.getColumnCount(); i < len; i++){
257 var c = cm.getColumnAt(i),
260 ed = c.displayEditor || new Ext.form.DisplayField();
265 ed.margins = pm('0 1 2 1');
266 } else if(i == len - 1){
267 ed.margins = pm('0 0 2 1');
269 ed.margins = pm('0 1 2');
271 ed.setWidth(cm.getColumnWidth(i));
273 if(ed.ownerCt !== this){
274 ed.on('focus', this.ensureVisible, this);
275 ed.on('specialkey', this.onKey, this);
279 this.initialized = true;
282 onKey: function(f, e){
283 if(e.getKey() === e.ENTER){
284 this.stopEditing(true);
289 onGridKey: function(e){
290 if(e.getKey() === e.ENTER && !this.isVisible()){
291 var r = this.grid.getSelectionModel().getSelected();
293 var index = this.grid.store.indexOf(r);
294 this.startEditing(index);
300 ensureVisible: function(editor){
301 if(this.isVisible()){
302 this.grid.getView().ensureVisible(this.rowIndex, this.grid.colModel.getIndexById(editor.column.id), true);
306 onRowClick: function(g, rowIndex, e){
307 if(this.clicksToEdit == 'auto'){
308 var li = this.lastClickIndex;
309 this.lastClickIndex = rowIndex;
310 if(li != rowIndex && !this.isVisible()){
314 this.startEditing(rowIndex, false);
315 this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
318 onRowDblClick: function(g, rowIndex, e){
319 this.startEditing(rowIndex, false);
320 this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
323 onRender: function(){
324 Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
325 this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
326 this.btns = new Ext.Panel({
331 width: (this.minButtonWidth * 2) + (this.frameWidth * 2) + (this.buttonPad * 4), // width must be specified for IE
337 width: this.minButtonWidth,
338 handler: this.stopEditing.createDelegate(this, [true])
341 text: this.cancelText,
342 width: this.minButtonWidth,
343 handler: this.stopEditing.createDelegate(this, [false])
346 this.btns.render(this.bwrap);
349 afterRender: function(){
350 Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
351 this.positionButtons();
352 if(this.monitorValid){
353 this.startMonitoring();
358 if(this.monitorValid){
359 this.startMonitoring();
361 Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
365 Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
366 this.stopMonitoring();
367 this.grid.getView().focusRow(this.rowIndex);
370 positionButtons: function(){
373 h = this.el.dom.clientHeight,
375 scroll = view.scroller.dom.scrollLeft,
376 bw = this.btns.getWidth(),
377 width = Math.min(g.getWidth(), g.getColumnModel().getTotalWidth());
379 this.btns.el.shift({left: (width/2)-(bw/2)+scroll, top: h - 2, stopFx: true, duration:0.2});
384 preEditValue : function(r, field){
385 var value = r.data[field];
386 return this.autoEncode && typeof value === 'string' ? Ext.util.Format.htmlDecode(value) : value;
390 postEditValue : function(value, originalValue, r, field){
391 return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
394 doFocus: function(pt){
395 if(this.isVisible()){
397 cm = this.grid.getColumnModel(),
401 index = this.getTargetColumnIndex(pt);
403 for(var i = index||0, len = cm.getColumnCount(); i < len; i++){
404 c = cm.getColumnAt(i);
414 getTargetColumnIndex: function(pt){
415 var grid = this.grid,
418 cms = grid.colModel.config,
421 for(var len = cms.length, c; c = cms[i]; i++){
423 if(Ext.fly(v.getHeaderCell(i)).getRegion().right >= x){
432 startMonitoring : function(){
433 if(!this.bound && this.monitorValid){
436 run : this.bindHandler,
437 interval : this.monitorPoll || 200,
443 stopMonitoring : function(){
452 this.items.each(function(f){
453 if(!f.isValid(true)){
462 bindHandler : function(){
464 return false; // stops binding
466 var valid = this.isValid();
467 if(!valid && this.errorSummary){
468 this.showTooltip(this.getErrorText().join(''));
470 this.btns.saveBtn.setDisabled(!valid);
471 this.fireEvent('validation', this, valid);
474 showTooltip: function(msg){
475 var t = this.tooltip;
477 t = this.tooltip = new Ext.ToolTip({
481 title: this.errorText,
484 anchorToTarget: true,
488 var v = this.grid.getView(),
489 top = parseInt(this.el.dom.style.top, 10),
490 scroll = v.scroller.dom.scrollTop,
491 h = this.el.getHeight();
493 if(top + h >= scroll){
494 t.initTarget(this.items.last().getEl());
502 }else if(t.rendered){
507 getErrorText: function(){
509 this.items.each(function(f){
510 if(!f.isValid(true)){
511 data.push('<li>', f.getActiveError(), '</li>');
518 Ext.preg('roweditor', Ext.ux.grid.RowEditor);