| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782 | <!DOCTYPE html><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>  <style type="text/css">    .highlight { display: block; background-color: #ddd; }  </style>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js">// Currently has the following issues:// - Does not handle postEditValue// - Fields without editors need to sync with their values in Store// - starting to edit another record while already editing and dirty should probably prevent it// - aggregating validation messages// - tabIndex is not managed bc we leave elements in dom, and simply move via positioning// - layout issues when changing sizes/width while hidden (layout bug)<span id='Ext-grid-RowEditor'>/**</span> * Internal utility class used to provide row editing functionality. For developers, they should use * the RowEditing plugin to use this functionality with a grid. * * @private */Ext.define('Ext.grid.RowEditor', {    extend: 'Ext.form.Panel',    requires: [        'Ext.tip.ToolTip',        'Ext.util.HashMap',        'Ext.util.KeyNav'    ],    //<locale>    saveBtnText  : 'Update',    //</locale>    //<locale>    cancelBtnText: 'Cancel',    //</locale>    //<locale>    errorsText: 'Errors',    //</locale>    //<locale>    dirtyText: 'You need to commit or cancel your changes',    //</locale>    lastScrollLeft: 0,    lastScrollTop: 0,    border: false,        // Change the hideMode to offsets so that we get accurate measurements when    // the roweditor is hidden for laying out things like a TriggerField.    hideMode: 'offsets',    initComponent: function() {        var me = this,            form;        me.cls = Ext.baseCSSPrefix + 'grid-row-editor';        me.layout = {            type: 'hbox',            align: 'middle'        };        // Maintain field-to-column mapping        // It's easy to get a field from a column, but not vice versa        me.columns = new Ext.util.HashMap();        me.columns.getKey = function(columnHeader) {            var f;            if (columnHeader.getEditor) {                f = columnHeader.getEditor();                if (f) {                    return f.id;                }            }            return columnHeader.id;        };        me.mon(me.columns, {            add: me.onFieldAdd,            remove: me.onFieldRemove,            replace: me.onFieldReplace,            scope: me        });        me.callParent(arguments);        if (me.fields) {            me.setField(me.fields);            delete me.fields;        }                me.mon(Ext.container.Container.hierarchyEventSource, {            scope: me,            show: me.repositionIfVisible        });        form = me.getForm();        form.trackResetOnLoad = true;    },    onFieldChange: function() {        var me = this,            form = me.getForm(),            valid = form.isValid();        if (me.errorSummary && me.isVisible()) {            me[valid ? 'hideToolTip' : 'showToolTip']();        }        me.updateButton(valid);        me.isValid = valid;    },        updateButton: function(valid){        var buttons = this.floatingButtons;         if (buttons) {            buttons.child('#update').setDisabled(!valid);        }        },    afterRender: function() {        var me = this,            plugin = me.editingPlugin;        me.callParent(arguments);        me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });        // Prevent from bubbling click events to the grid view        me.mon(me.el, {            click: Ext.emptyFn,            stopPropagation: true        });        me.el.swallowEvent([            'keypress',            'keydown'        ]);        me.keyNav = new Ext.util.KeyNav(me.el, {            enter: plugin.completeEdit,            esc: plugin.onEscKey,            scope: plugin        });        me.mon(plugin.view, {            beforerefresh: me.onBeforeViewRefresh,            refresh: me.onViewRefresh,            itemremove: me.onViewItemRemove,            scope: me        });    },    onBeforeViewRefresh: function(view) {        var me = this,            viewDom = view.el.dom;        if (me.el.dom.parentNode === viewDom) {            viewDom.removeChild(me.el.dom);        }    },    onViewRefresh: function(view) {        var me = this,            viewDom = view.el.dom,            context = me.context,            idx;        viewDom.appendChild(me.el.dom);        // Recover our row node after a view refresh        if (context && (idx = context.store.indexOf(context.record)) >= 0) {            context.row = view.getNode(idx);            me.reposition();            if (me.tooltip && me.tooltip.isVisible()) {                me.tooltip.setTarget(context.row);            }        } else {            me.editingPlugin.cancelEdit();        }    },    onViewItemRemove: function(record, index) {        var context = this.context;        if (context && record === context.record) {            // if the record being edited was removed, cancel editing            this.editingPlugin.cancelEdit();        }    },    onCtScroll: function(e, target) {        var me = this,            scrollTop  = target.scrollTop,            scrollLeft = target.scrollLeft;        if (scrollTop !== me.lastScrollTop) {            me.lastScrollTop = scrollTop;            if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {                me.repositionTip();            }        }        if (scrollLeft !== me.lastScrollLeft) {            me.lastScrollLeft = scrollLeft;            me.reposition();        }    },    onColumnAdd: function(column) {        if (!column.isGroupHeader) {            this.setField(column);        }    },    onColumnRemove: function(column) {        this.columns.remove(column);    },    onColumnResize: function(column, width) {        if (!column.isGroupHeader) {            column.getEditor().setWidth(width - 2);            this.repositionIfVisible();        }    },    onColumnHide: function(column) {        if (!column.isGroupHeader) {            column.getEditor().hide();            this.repositionIfVisible();        }    },    onColumnShow: function(column) {        var field = column.getEditor();        field.setWidth(column.getWidth() - 2).show();        this.repositionIfVisible();    },    onColumnMove: function(column, fromIdx, toIdx) {        if (!column.isGroupHeader) {            var field = column.getEditor();            if (this.items.indexOf(field) != toIdx) {                this.move(fromIdx, toIdx);            }        }    },    onFieldAdd: function(map, fieldId, column) {        var me = this,            colIdx,            field;        if (!column.isGroupHeader) {            colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column);            field = column.getEditor({ xtype: 'displayfield' });            me.insert(colIdx, field);        }    },    onFieldRemove: function(map, fieldId, column) {        var me = this,            field,            fieldEl;        if (!column.isGroupHeader) {            field = column.getEditor();            fieldEl = field.el;            me.remove(field, false);            if (fieldEl) {                fieldEl.remove();            }        }    },    onFieldReplace: function(map, fieldId, column, oldColumn) {        this.onFieldRemove(map, fieldId, oldColumn);    },    clearFields: function() {        var map = this.columns,            key;        for (key in map) {            if (map.hasOwnProperty(key)) {                map.removeAtKey(key);            }        }    },    getFloatingButtons: function() {        var me = this,            cssPrefix = Ext.baseCSSPrefix,            btnsCss = cssPrefix + 'grid-row-editor-buttons',            plugin = me.editingPlugin,            minWidth = Ext.panel.Panel.prototype.minButtonWidth,            btns;        if (!me.floatingButtons) {            btns = me.floatingButtons = new Ext.Container({                renderTpl: [                    '<div class="{baseCls}-ml"></div>',                    '<div class="{baseCls}-mr"></div>',                    '<div class="{baseCls}-bl"></div>',                    '<div class="{baseCls}-br"></div>',                    '<div class="{baseCls}-bc"></div>',                    '{%this.renderContainer(out,values)%}'                ],                width: 200,                renderTo: me.el,                baseCls: btnsCss,                layout: {                    type: 'hbox',                    align: 'middle'                },                defaults: {                    flex: 1,                    margins: '0 1 0 1'                },                items: [{                    itemId: 'update',                    xtype: 'button',                    handler: plugin.completeEdit,                    scope: plugin,                    text: me.saveBtnText,                    minWidth: minWidth                }, {                    xtype: 'button',                    handler: plugin.cancelEdit,                    scope: plugin,                    text: me.cancelBtnText,                    minWidth: minWidth                }]            });            // Prevent from bubbling click events to the grid view            me.mon(btns.el, {                // BrowserBug: Opera 11.01                //   causes the view to scroll when a button is focused from mousedown                mousedown: Ext.emptyFn,                click: Ext.emptyFn,                stopEvent: true            });        }        return me.floatingButtons;    },        repositionIfVisible: function(c){        var me = this,            view = me.view;                // If we're showing ourselves, jump out        // If the component we're showing doesn't contain the view        if (c && (c == me || !view.isDescendantOf(c))) {            return;        }                if (me.isVisible() && view.isVisible(true)) {            me.reposition();            }    },    reposition: function(animateConfig) {        var me = this,            context = me.context,            row = context && Ext.get(context.row),            btns = me.getFloatingButtons(),            btnEl = btns.el,            grid = me.editingPlugin.grid,            viewEl = grid.view.el,            // always get data from ColumnModel as its what drives            // the GridView's sizing            mainBodyWidth = grid.headerCt.getFullWidth(),            scrollerWidth = grid.getWidth(),            // use the minimum as the columns may not fill up the entire grid            // width            width = Math.min(mainBodyWidth, scrollerWidth),            scrollLeft = grid.view.el.dom.scrollLeft,            btnWidth = btns.getWidth(),            left = (width - btnWidth) / 2 + scrollLeft,            y, rowH, newHeight,            invalidateScroller = function() {                btnEl.scrollIntoView(viewEl, false);                if (animateConfig && animateConfig.callback) {                    animateConfig.callback.call(animateConfig.scope || me);                }            },                        animObj;        // need to set both top/left        if (row && Ext.isElement(row.dom)) {            // Bring our row into view if necessary, so a row editor that's already            // visible and animated to the row will appear smooth            row.scrollIntoView(viewEl, false);            // Get the y position of the row relative to its top-most static parent.            // offsetTop will be relative to the table, and is incorrect            // when mixed with certain grid features (e.g., grouping).            y = row.getXY()[1] - 5;            rowH = row.getHeight();            newHeight = rowH + (me.editingPlugin.grid.rowLines ? 9 : 10);            // Set editor height to match the row height            if (me.getHeight() != newHeight) {                me.setHeight(newHeight);                me.el.setLeft(0);            }            if (animateConfig) {                animObj = {                    to: {                        y: y                    },                    duration: animateConfig.duration || 125,                    listeners: {                        afteranimate: function() {                            invalidateScroller();                            y = row.getXY()[1] - 5;                        }                    }                };                me.el.animate(animObj);            } else {                me.el.setY(y);                invalidateScroller();            }        }        if (me.getWidth() != mainBodyWidth) {            me.setWidth(mainBodyWidth);        }        btnEl.setLeft(left);    },    getEditor: function(fieldInfo) {        var me = this;        if (Ext.isNumber(fieldInfo)) {            // Query only form fields. This just future-proofs us in case we add            // other components to RowEditor later on.  Don't want to mess with            // indices.            return me.query('>[isFormField]')[fieldInfo];        } else if (fieldInfo.isHeader && !fieldInfo.isGroupHeader) {            return fieldInfo.getEditor();        }    },    removeField: function(field) {        var me = this;        // Incase we pass a column instead, which is fine        field = me.getEditor(field);        me.mun(field, 'validitychange', me.onValidityChange, me);        // Remove field/column from our mapping, which will fire the event to        // remove the field from our container        me.columns.removeAtKey(field.id);        Ext.destroy(field);    },    setField: function(column) {        var me = this,            i,            length, field;        if (Ext.isArray(column)) {            length = column.length;            for (i = 0; i < length; i++) {                me.setField(column[i]);            }            return;        }        // Get a default display field if necessary        field = column.getEditor(null, {            xtype: 'displayfield',            // Override Field's implementation so that the default display fields will not return values. This is done because            // the display field will pick up column renderers from the grid.            getModelData: function() {                return null;            }        });        field.margins = '0 0 0 2';        me.mon(field, 'change', me.onFieldChange, me);                if (me.isVisible() && me.context) {            if (field.is('displayfield')) {                me.renderColumnData(field, me.context.record, column);            } else {                field.suspendEvents();                field.setValue(me.context.record.get(column.dataIndex));                field.resumeEvents();            }        }        // Maintain mapping of fields-to-columns        // This will fire events that maintain our container items                me.columns.add(field.id, column);        if (column.hidden) {            me.onColumnHide(column);        } else if (column.rendered) {            // Setting after initial render            me.onColumnShow(column);        }    },    loadRecord: function(record) {        var me     = this,            form   = me.getForm(),            fields = form.getFields(),            items  = fields.items,            length = items.length,            i, displayFields,            isValid;                    // temporarily suspend events on form fields before loading record to prevent the fields' change events from firing        for (i = 0; i < length; i++) {            items[i].suspendEvents();        }        form.loadRecord(record);        for (i = 0; i < length; i++) {            items[i].resumeEvents();        }        isValid = form.isValid();        if (me.errorSummary) {            if (isValid) {                me.hideToolTip();            } else {                me.showToolTip();            }        }                me.updateButton(isValid);        // render display fields so they honor the column renderer/template        displayFields = me.query('>displayfield');        length = displayFields.length;        for (i = 0; i < length; i++) {            me.renderColumnData(displayFields[i], record);        }    },    renderColumnData: function(field, record, activeColumn) {        var me = this,            grid = me.editingPlugin.grid,            headerCt = grid.headerCt,            view = grid.view,            store = view.store,            column = activeColumn || me.columns.get(field.id),            value = record.get(column.dataIndex),            renderer = column.editRenderer || column.renderer,            metaData,            rowIdx,            colIdx;        // honor our column's renderer (TemplateHeader sets renderer for us!)        if (renderer) {            metaData = { tdCls: '', style: '' };            rowIdx = store.indexOf(record);            colIdx = headerCt.getHeaderIndex(column);            value = renderer.call(                column.scope || headerCt.ownerCt,                value,                metaData,                record,                rowIdx,                colIdx,                store,                view            );        }        field.setRawValue(value);        field.resetOriginalValue();    },    beforeEdit: function() {        var me = this;        if (me.isVisible() && me.errorSummary && !me.autoCancel && me.isDirty()) {            me.showToolTip();            return false;        }    },<span id='Ext-grid-RowEditor-method-startEdit'>    /**</span>     * Start editing the specified grid at the specified position.     * @param {Ext.data.Model} record The Store data record which backs the row to be edited.     * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.     */    startEdit: function(record, columnHeader) {        var me = this,            grid = me.editingPlugin.grid,            store = grid.store,            context = me.context = Ext.apply(me.editingPlugin.context, {                view: grid.getView(),                store: store            });        // make sure our row is selected before editing        context.grid.getSelectionModel().select(record);        // Reload the record data        me.loadRecord(record);        if (!me.isVisible()) {            me.show();            me.focusContextCell();        } else {            me.reposition({                callback: this.focusContextCell            });        }    },    // Focus the cell on start edit based upon the current context    focusContextCell: function() {        var field = this.getEditor(this.context.colIdx);        if (field && field.focus) {            field.focus();        }    },    cancelEdit: function() {        var me     = this,            form   = me.getForm(),            fields = form.getFields(),            items  = fields.items,            length = items.length,            i;        me.hide();        form.clearInvalid();        // temporarily suspend events on form fields before reseting the form to prevent the fields' change events from firing        for (i = 0; i < length; i++) {            items[i].suspendEvents();        }        form.reset();        for (i = 0; i < length; i++) {            items[i].resumeEvents();        }    },    completeEdit: function() {        var me = this,            form = me.getForm();        if (!form.isValid()) {            return;        }        form.updateRecord(me.context.record);        me.hide();        return true;    },    onShow: function() {        this.callParent(arguments);        this.reposition();    },    onHide: function() {        var me = this;        me.callParent(arguments);        if (me.tooltip) {            me.hideToolTip();        }        if (me.context) {            me.context.view.focus();            me.context = null;        }    },    isDirty: function() {        var me = this,            form = me.getForm();        return form.isDirty();    },    getToolTip: function() {        return this.tooltip || (this.tooltip = new Ext.tip.ToolTip({            cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',            title: this.errorsText,            autoHide: false,            closable: true,            closeAction: 'disable',            anchor: 'left'        }));    },    hideToolTip: function() {        var me = this,            tip = me.getToolTip();        if (tip.rendered) {            tip.disable();        }        me.hiddenTip = false;    },    showToolTip: function() {        var me = this,            tip = me.getToolTip(),            context = me.context,            row = Ext.get(context.row),            viewEl = context.grid.view.el;        tip.setTarget(row);        tip.showAt([-10000, -10000]);        tip.update(me.getErrors());        tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];        me.repositionTip();        tip.doLayout();        tip.enable();    },    repositionTip: function() {        var me = this,            tip = me.getToolTip(),            context = me.context,            row = Ext.get(context.row),            viewEl = context.grid.view.el,            viewHeight = viewEl.getHeight(),            viewTop = me.lastScrollTop,            viewBottom = viewTop + viewHeight,            rowHeight = row.getHeight(),            rowTop = row.dom.offsetTop,            rowBottom = rowTop + rowHeight;        if (rowBottom > viewTop && rowTop < viewBottom) {            tip.show();            me.hiddenTip = false;        } else {            tip.hide();            me.hiddenTip = true;        }    },    getErrors: function() {        var me        = this,            dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',            errors    = [],            fields    = me.query('>[isFormField]'),            length    = fields.length,            i;        function createListItem(e) {            return '<li>' + e + '</li>';        }        for (i = 0; i < length; i++) {            errors = errors.concat(                Ext.Array.map(fields[i].getErrors(), createListItem)            );        }        return dirtyText + '<ul>' + errors.join('') + '</ul>';    },        beforeDestroy: function(){        Ext.destroy(this.floatingButtons, this.tooltip);        this.callParent();        }});</pre></body></html>
 |