--- /dev/null
+<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>
+</head>
+<body onload="prettyPrint();">
+ <pre class="prettyprint lang-js">/*!
+ * Ext JS Library 3.3.0
+ * 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,
+ 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(
+ <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);
+ }
+ }
+
+ // 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);
+ }
+ 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);
+</pre>
+</body>
+</html>
\ No newline at end of file