Hi All

I have been going through the classes we have developed over the last few years and sharing back with the community some of them that I think will be useful to others. These components have been tested extensively in ext-3.2.1.

Here is: Ext.ux.grid.RenderDelayedElements

In developing our rapid application development environment we needed to let our interface builder be able to dynamically to assign working ext components to grid cells (complete with javascript that would be invoked by interacting with these components). We found a fast and flexible solution by creating grid cell formaters that creates components based on data returned from the server and then put place holder divs in the grid cells for these components to be rendered into after the grid loads. While this solution has severed us very well and was fast and flexible to develop, it really isn't quite as elegant a solution as we would have liked it to be.

Below is the plugin you apply to your grid to give it the delayed render capability and then the formatter overrides we use to create the components.

Ext.ux.grid.RenderDelayedElements.js
Code:
/**
 * @author Will Ferrer, Ethan Brooks
 * @copyright (c) 2012, Intellectual Property Private Equity Group
 * @licensee 2012 developed under license for Switchsoft LLC http://www.switchsoft.com a "Direct response telephony company" as part of it's "VOIP Call distribution, ROI analysis platform, call recording, and IVR for inbound and outbound sales" and Run the Business Systems LLC a "Technology development investment group" as part of it's "PHP, Javascript rapid application development framework and MySQL analysis tools"
 * @license licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open
Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
* We are pretty nice just ask. We want to meet our licensees
*/

/**
 * @class Ext.ux.grid.RenderDelayedElements
 * @extends Ext.util.Observable
 * Some of my custom renderes add delayedRenders on records in the store. This plugin will fill the grid cells with the html stored in the delayedRenders property of the records
 * @param {Object} config The config object
 * @ptype ux-grid-renderdelayeelements
 */
Ext.ns('Ext.ux.grid');
Ext.ux.grid.RenderDelayedElements = function(config){
    Ext.apply(this, config);
    Ext.ux.grid.RenderDelayedElements.superclass.constructor.call(this);
};
Ext.extend(Ext.ux.grid.RenderDelayedElements, Ext.util.Observable, {
    // @private
    parent : null,
    // @private
    init: function(parent) {
        this.parent = parent;
        this.parent.on('load', this.forceDelayedRenders, this);
    },
    /** renderDelayedElements
    * parses through all the records in the store and looks for component configs that are stored on their delayedRenders properties. It then created these elements and renders them to the div set on the records renderTo property
    * @private
    */
    renderDelayedElements : function () {
        var records = this.parent.store.data.items,
            children, renderTo, newComponent;
        for (n=0;n<records.length;n++) {
            curRecord = records[n];
            if (!Ext.isEmpty(curRecord.delayedRenders)) {
                for (n2=0;n2<curRecord.delayedRenders.length;n2++) {
                    curDelayedRender = curRecord.delayedRenders[n2];
                    newComponent = Ext.create(curDelayedRender);
                    
                    if(!Ext.isEmpty(newComponent.listeners)) {
                        newComponent.listeners['scope'] = newComponent;
                    }
                    
                    if (curDelayedRender.clearChildWidth == true) {
                        renderTo = Ext.get(curDelayedRender.renderTo);
                        child = Ext.DomQuery.selectNode('*', renderTo.dom);
                        Ext.fly(child).setWidth('');
                    }
                    
                    if (Ext.isEmpty(records[n]['components'])) {
                        records[n]['components'] = [];
                    }
                    records[n]['components'].push(newComponent);
                }
                curRecord.delayedRenders = [];
            }
        }
    },
    /** forceDelayedRenders
    * Under very rare circumstances the delayed renders need to be activated after a delay, this method handles this.
    * @private
    */
    forceDelayedRenders : function () {
        try {
            this.renderDelayedElements();
        } catch (e) {
            // This is a poor fix -- when a grid has been expanded, contracted, re expanded and refreshed the render delayed elements happens before the new elements are in place. this is handled by by catching the error here and rerunning this method after reload
            var delayedTry = new Ext.util.DelayedTask(this.hammerThrowDelayedRenders, this);
            delayedTry.delay(100);
        }
    },
});
Ext.preg('ux-grid-renderdelayeelements', Ext.ux.grid.RenderDelayedElements);
Ext.ux.DelayedRenderOverrides.js
Code:
Ext.apply(Ext.util.Format, {
    /** groupCombo
    * splits the value of the record on "," and turns it into a combo box component
    * @param {String} value
    * @param {Object} metaData
    * @param {Object} record
    * @param {Number} rowIndex
    * @param {Number} colIndex
    * @param {Object} store
    * @return {String}
    */
    groupCombo : function (value, metaData, record, rowIndex, colIndex, store) {
        var list,
            divId = 'delayedRender'+Math.floor(Math.random()*999999999),
            divHtml = '<div id=' + divId + '></div>',
            comboBox, n;
        
        if (!Ext.isEmpty(value)) {
            list = value.split(',');
        } else {
            list = [];
        }
        for(n=0;n<list.length;n++){
            list[n] = [list[n]];
        }

        comboBox = {
            xtype : 'combo',
            displayField:'disp',
            typeAhead: true,
            preventMark :true,
            mode: 'local',
            forceSelection: true,
            triggerAction: 'all',
            cls : 'x-combo-in-grid-cell',
            ctCls : 'x-combo-in-grid-cell',
            itemCls : 'x-combo-in-grid-cell',
            selectOnFocus:true,
            width : 'auto',
            clearChildWidth : true,
            //autoWidth : true,
            //layout : 'fit',
            autoLoad : true,
            value : list[0],
            editable : true,
            renderTo : divId,
            store : new Ext.data.ArrayStore({
                fields : ['disp'],
                data : list,
                autoLoad : true
            })
        };
        if (Ext.isEmpty(record.delayedRenders)) {
            record.delayedRenders = [];
        }
        record.delayedRenders.push(comboBox);

        return divHtml;
    },
    /** groupComboWS
    * splits the value of the record on a special deliniater "--*--" and turns it into a combo box component
    * @param {String} value
    * @param {Object} metaData
    * @param {Object} record
    * @param {Number} rowIndex
    * @param {Number} colIndex
    * @param {Object} store
    * @return {String}
    */
    groupComboWS : function (value, metaData, record, rowIndex, colIndex, store) {
        var list,
            divId = 'delayedRender'+Math.floor(Math.random()*999999999),
            divHtml = '<div id=' + divId + '></div>',
            comboBox, n;
        
        if (!Ext.isEmpty(value)) {
            list = value.split('--*--');
        } else {
            list = [];
        }
        for(n=0;n<list.length;n++){
            list[n] = [list[n]];
        }
        
        comboBox = {
                xtype : 'combo',
                displayField:'disp',
                typeAhead: true,
                preventMark :true,
                mode: 'local',
                forceSelection: true,
                triggerAction: 'all',
                cls : 'x-combo-in-grid-cell',
                ctCls : 'x-combo-in-grid-cell',
                itemCls : 'x-combo-in-grid-cell',
                selectOnFocus:true,
                autoLoad : true,
                autoWidth : true,
                editable : true,
                value : list[0],
                renderTo : divId,
                store : new Ext.data.ArrayStore({
                    fields : ['disp'],
                    data : list,
                    autoLoad : true
                })
        };
        if (Ext.isEmpty(record.delayedRenders)) {
            record.delayedRenders = [];
        }
        record.delayedRenders.push(comboBox);

        return divHtml;
    },
    /** variableCellRenderer
    * takes a value like so: [formater_name]|value to formate. The value is passed to the formater specified and the result is returned. This is used to allow the return from the server to dictate what formater should be used for the cell.
    * @param {String} value
    * @param {Object} metaData
    * @param {Object} record
    * @param {Number} rowIndex
    * @param {Number} colIndex
    * @param {Object} store
    */
    variableCellRenderer : function (value, metaData, record, rowIndex, colIndex, store) {
        //delayedRender are handled in the onload function of analytics grid
        if(!Ext.isEmpty(value)) {
            var stringSplit = value.split('|'),
                renderer = stringSplit[0],
                value = stringSplit[1],
                returnValue = Ext.util.Format[renderer](value, metaData, record, rowIndex, colIndex, store);
                
    
    
            return returnValue;
        }
    },
    
    /** cellAction
    * creates a button named after the grid column. When clicked the button will execute javascript that was stored in the value.
    * This formattter is deprecated in favor of cellButtonAction.
    * @param {String} value
    * @param {Object} metaData
    * @param {Object} record
    * @param {Number} rowIndex
    * @param {Number} colIndex
    * @param {Object} store
    * @return {String}
    */
    cellAction : function (value, metaData, record, rowIndex, colIndex, store) {
        var divId = 'delayedRender'+Math.floor(Math.random()*999999999),
            divHtml = '<div id=' + divId + '></div>', button;
            
        button = {
            xtype : 'button',
            text : metaData.id,
            evalString : value,
            renderTo : divId,
            handler : function () {
                eval(this.evalString);
            }
        };

        if (Ext.isEmpty(record.delayedRenders)) {
            record.delayedRenders = [];
        }
        record.delayedRenders.push(button);

        return divHtml;
    },
    
    
    /** mp3Player
    * Creates a dew player mp3 player where the location of the mp3 file to play is the value of the cell. !For use in 3rd party products the location of the dewplayer.swl will need to be modified. 
    * @param {String} value
    * @param {Object} metaData
    * @param {Object} record
    * @param {Number} rowIndex
    * @param {Number} colIndex
    * @param {Object} store
    * @return {String}
    */
    mp3Player : function (value, metaData, record, rowIndex, colIndex, store) {
        if(!Ext.isEmpty(value)) {
            var divId = 'delayedRender'+Math.floor(Math.random()*999999999),
                divHtml = '<div id=' + divId + '></div>', panel,
                playerHtml = '<object data="/js_library/dewplayer/dewplayer.swf" width="200" height="20" name="dewplayer" id="dewplayer" type="application/x-shockwave-flash"><param name="movie" value="dewplayer.swf" /><param name="flashvars" value="mp3=' + value + '&javascript=on" /><param name="wmode" value="transparent" /></object>';

            panel = {
                xtype : 'panel',
                //We may need a call recording url in the origination table becuse the call will not alway be in the same account so the account id and the call id will be needed
                html: playerHtml,

                dataIndex : metaData.id,
                sourceRecord : record,
                width : 'auto',
                clearChildWidth : true,
                cls : 'x-button-in-grid-cell',
                ctCls : 'x-button-in-grid-cell',
                itemCls : 'x-button-in-grid-cell',
                renderTo : divId
            };
            
            if (Ext.isEmpty(record.delayedRenders)) {
                record.delayedRenders = [];
            }

            record.delayedRenders.push(panel);
    
            return divHtml;
        }
    },
    
    /** cellButtonAction
    * Creates button based on the return from the server. value should be formated: [test to display on the button]|[function to call when button is pressed]|[scope to evaluate the function call in]
    * @param {String} value
    * @param {Object} metaData
    * @param {Object} record
    * @param {Number} rowIndex
    * @param {Number} colIndex
    * @param {Object} store
    * @return {String}
    */
    cellButtonAction : function (value, metaData, record, rowIndex, colIndex, store) {
        if(!Ext.isEmpty(value)) {
            var divId = 'delayedRender'+Math.floor(Math.random()*999999999),
                divHtml = '<div id=' + divId + '></div>', button,
                stringSplit = value.split('|'),
                text = (stringSplit[0]=='null')?metaData.id:stringSplit[0],
                functionName = stringSplit[1],
                arguments = stringSplit[2],
                evalScope = (Ext.isEmpty(stringSplit[3]))?'this':stringSplit[3];

            button = {
                xtype : 'button',
                text : text,
                dataIndex : metaData.id,
                sourceRecord : record,
                iconCls: "icon-cellaction",
                width : 'auto',
                clearChildWidth : true,
                cls : 'x-button-in-grid-cell',
                ctCls : 'x-button-in-grid-cell',
                itemCls : 'x-button-in-grid-cell',
                evalFunction : eval(functionName),
                evalArguments : eval(arguments),
                evalScope : eval(evalScope),
                renderTo : divId,
                handler : Ext.util.Format.evalCellAction
            };
    
            if (Ext.isEmpty(record.delayedRenders)) {
                record.delayedRenders = [];
            }
            record.delayedRenders.push(button);
    
            return divHtml;
        }
    },
    
    /** cellCheckBoxAction
    * Creates checb box based on the return from the server. value should be formated: [checked true or false]|[function to call when button is pressed]|[scope to evaluate the function call in]
    * @param {String} value
    * @param {Object} metaData
    * @param {Object} record
    * @param {Number} rowIndex
    * @param {Number} colIndex
    * @param {Object} store
    * @return {String}
    */
    cellCheckBoxAction : function (value, metaData, record, rowIndex, colIndex, store) {
        if(!Ext.isEmpty(value)) {
            //delayedRender are handled in the onload function of analytics grid
            var divId = 'delayedRender'+Math.floor(Math.random()*999999999),
                divHtml = '<div id=' + divId + '></div>', button,
                stringSplit = value.split('|'),
                checked = (stringSplit[0]=='true'||stringSplit[0]==1||stringSplit[0]=='1')?true:false,
                functionName = stringSplit[1],
                arguments = stringSplit[2],
                evalScope = (Ext.isEmpty(stringSplit[3]))?'this':stringSplit[3];
    
            checkBox = {
                xtype : 'checkbox',
                checked : checked,
                value : checked,
                dataIndex : metaData.id,
                sourceRecord : record,
                evalFunction : eval(functionName),
                evalArguments : eval(arguments),
                evalScope : eval(evalScope),
                renderTo : divId,
                useGetRawValue : true,
                listeners : {
                    'click' : Ext.util.Format.evalCellAction
                }
            };
            if (Ext.isEmpty(record.delayedRenders)) {
                record.delayedRenders = [];
            }
            record.delayedRenders.push(checkBox);
    
            return divHtml;
        }
    },
    
    /** cellComboBoxAction
    * Creates ComboBox box based on the return from the server. value should be formated: [initially selected value]|[an array in json format of names to go in the combo box]|[an array in json format of values to go in the combo box]
    * @param {String} value
    * @param {Object} metaData
    * @param {Object} record
    * @param {Number} rowIndex
    * @param {Number} colIndex
    * @param {Object} store
    * @return {String}
    */
    cellComboBoxAction : function (value, metaData, record, rowIndex, colIndex, store) {
        if(!Ext.isEmpty(value)) {
            //delayedRender are handled in the onload function of analytics grid
            var divId = 'delayedRender'+Math.floor(Math.random()*999999999),
                divHtml = '<div id=' + divId + '></div>', button,
                stringSplit = value.split('|'),
                value = stringSplit[0],
                names = Ext.decode(stringSplit[1]),
                values = Ext.decode(stringSplit[2]),
                functionName = stringSplit[3],
                arguments = stringSplit[4],
                evalScope = (Ext.isEmpty(stringSplit[5]))?'this':stringSplit[5],
                data=[], n;

            for(n=0;n<names.length;n++){
                data.push({
                    name : names[n],
                    value : values[n]
                });
            }
                
            comboBox = {
                xtype : 'combo',
                value : value,
                dataIndex : metaData.id,
                typeAhead: true,
                mode: 'local',
                forceSelection: true,
                triggerAction: 'all',
                selectOnFocus:true,
                sourceRecord : record,
                evalFunction : eval(functionName),
                evalArguments : eval(arguments),
                evalScope : eval(evalScope),
                renderTo : divId,
                useGetRawValue : false,
                displayField:'name',
                valueField:'value',
                listeners : {
                    'select' : Ext.util.Format.evalCellAction
                },
                store : new Ext.data.JsonStore({
                    fields : ['name', 'value'],
                    data : data
                })
            };
    
            
            if (Ext.isEmpty(record.delayedRenders)) {
                record.delayedRenders = [];
            }
            record.delayedRenders.push(comboBox);
    
            return divHtml;
        }
    },
    /** evalCellAction
    * Evaluates an function attached to component component created as a delayed render component 
    * @private
    */
    evalCellAction : function () {
        var evalFunction = this.evalFunction,
            arguments = this.evalArguments,
            evalScope = this.evalScope,
            record = this.sourceRecord,
            components = record.components,
            params = (typeof(arguments[0])=='string' && /^['"]?[\[\{].+[\]\}]['"]?$/.test(arguments[0]))?Ext.decode(arguments[0]):arguments[0],
            obj, n, colValue, capturedValues={};
        if (Ext.isObject(params)) {
            for (obj in record.data) {
                colValue = null;
                if (!Ext.isEmpty(components)) {
                    for (n=0; n<components.length; n++) {
                        if (components[n].dataIndex == obj) {
                            if (!Ext.isEmpty(components[n].useGetRawValue) && components[n].useGetRawValue) {
                                if (!Ext.isEmpty(components[n].getRawValue)) {
                                    colValue = components[n].getRawValue();
                                    break;
                                }
                            } else if (!Ext.isEmpty(components[n].useGetOutputValue) && components[n].useGetOutputValue) {
                                if (!Ext.isEmpty(components[n].useGetOutputValue)) {
                                    colValue = components[n].getOutputValue();
                                    break;
                                }
                            } else {
                                if (!Ext.isEmpty(components[n].getValue)) {
                                    colValue = components[n].getValue();
                                    break;
                                }
                            }
                        }
                    }
                }
                if (Ext.isEmpty(colValue)) {
                    colValue = record.data[obj];
                }
                capturedValues[obj] = colValue;
            }
            params['caputredValues'] = capturedValues;
            arguments[0] = params;
        }

        evalFunction.apply(evalScope, arguments);
    }
});
Best regards

Will Ferrer (Run the Business)