6

Details

I have a grid used to display invoice information. The grid is populated using the Invoice store, the Invoice store uses the Invoice model, the Invoice model has a "has one" association with the InvoiceStatus model with a primary key of 'id' and a foren key of 'invoice_status_id'.

Problem

I'm not sure how to make the display value of the Invoice Grid's 'Status' column use the associated models 'name' inserted of the invoice_status_id. I know I need to create a renderer to do this however I still get a null value. Both the Invoice and InvoiceStatus stors are populating with the correct values.

Status Column Render

renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
    return record.getStatus().get('name');
},

Invoice Store

Ext.define('MyApp.store.Invoice', {
    extend: 'Ext.data.Store',

    requires: [
        'MyApp.model.InvoiceModel'
    ],

    constructor: function(cfg) {
        var me = this;
        cfg = cfg || {};
        me.callParent([Ext.apply({
            autoLoad: true,
            autoSync: true,
            model: 'MyApp.model.InvoiceModel',
            remoteSort: true,
            storeId: 'StoreInvoce',
            proxy: {
                type: 'rest',
                url: '/api/invoice',
                reader: {
                    type: 'json',
                    root: 'data'
                }
            }
        }, cfg)]);
    }
});

InvoiceStatus Store

Ext.define('MyApp.store.InvoiceStatus', {
    extend: 'Ext.data.Store',
    alias: 'store.InvoiceStatus',

    requires: [
        'MyApp.model.InvoiceStatus'
    ],

    constructor: function(cfg) {
        var me = this;
        cfg = cfg || {};
        me.callParent([Ext.apply({
            autoLoad: true,
            autoSync: true,
            model: 'MyApp.model.InvoiceStatus',
            remoteSort: true,
            storeId: 'MyJsonStore1',
            proxy: {
                type: 'rest',
                url: '/api/invoice_status',
                reader: {
                    type: 'json',
                    root: 'data'
                }
            }
        }, cfg)]);
    }
});

Invoice Model

Ext.define('MyApp.model.InvoiceModel', {
    extend: 'Ext.data.Model',
 
    uses: [
        'MyApp.model.InvoiceStatus'
    ],
 
    fields: [
        {
            mapping: 'id',
            name: 'id',
            type: 'int'
        },
        {
            mapping: 'client_id',
            name: 'client_id',
            type: 'int'
        },
        {
            mapping: 'client_name',
            name: 'client_name',
            type: 'string'
        },
        {
            dateFormat: 'Y-m-d',
            dateReadFormat: '',
            mapping: 'issue_date',
            name: 'issue_date',
            sortType: 'asDate',
            type: 'date'
        },
        {
            dateFormat: 'Y-m-d',
            mapping: 'due_date',
            name: 'due_date',
            sortType: 'asDate',
            type: 'date'
        },
        {
            mapping: 'payment_date',
            name: 'payment_date',
            sortType: 'asDate',
            type: 'date',
            useNull: true
        },
        {
            name: 'amount'
        },
        {
            mapping: 'invoice_status_id',
            name: 'invoice_status_id',
            sortType: 'asInt',
            type: 'int'
        }
    ],
 
    hasOne: {
        model: 'MyApp.model.InvoiceStatus',
        foreignKey: 'invoice_status_id',
        getterName: 'getStatus'
    }
});

InvoiceStatus Model

Ext.define('MyApp.model.InvoiceStatus', {
    extend: 'Ext.data.Model',

    fields: [
        {
            mapping: 'id',
            name: 'id',
            type: 'int'
        },
        {
            mapping: 'name',
            name: 'name',
            type: 'string'
        }
    ]
});

Invoice Grid

Ext.define('MyApp.view.ApplicationViewport', {
    extend: 'Ext.container.Viewport',

    requires: [
        'MyApp.view.ClearTriggerField'
    ],

    layout: {
        type: 'border'
    },

    initComponent: function() {
        var me = this;

        Ext.applyIf(me, {
            items: [
                {
                    xtype: 'header',
                    region: 'north',
                    height: 100,
                    items: [
                        {
                            xtype: 'image',
                            height: 100,
                            width: 250,
                            alt: 'Logo',
                            src: 'images/logo.gif',
                            title: 'Logo'
                        }
                    ]
                },
                {
                    xtype: 'container',
                    region: 'center',
                    layout: {
                        type: 'card'
                    },
                    items: [
                        {
                            xtype: 'container',
                            width: 150,
                            layout: {
                                type: 'border'
                            },
                            items: [
                                {
                                    xtype: 'gridpanel',
                                    collapseMode: 'mini',
                                    region: 'west',
                                    split: true,
                                    autoRender: false,
                                    maxWidth: 300,
                                    width: 250,
                                    bodyBorder: false,
                                    animCollapse: false,
                                    collapsed: false,
                                    collapsible: true,
                                    hideCollapseTool: true,
                                    overlapHeader: false,
                                    titleCollapse: true,
                                    allowDeselect: true,
                                    columnLines: false,
                                    forceFit: true,
                                    store: 'ClientDataStor',
                                    dockedItems: [
                                        {
                                            xtype: 'toolbar',
                                            dock: 'top',
                                            items: [
                                                {
                                                    xtype: 'cleartrigger'
                                                },
                                                {
                                                    xtype: 'tbfill'
                                                },
                                                {
                                                    xtype: 'button',
                                                    icon: '/images/settings.png'
                                                }
                                            ]
                                        }
                                    ],
                                    columns: [
                                        {
                                            xtype: 'templatecolumn',
                                            tpl: [
                                                '<img class="pull-left client-menu-image" src="/images/{type}.png"><div class="client-menu-name">{name}</div><div class="client-menu-type">{type}</div>'
                                            ],
                                            dataIndex: 'id',
                                            text: 'Client'
                                        }
                                    ],
                                    selModel: Ext.create('Ext.selection.RowModel', {

                                    }),
                                    plugins: [
                                        Ext.create('Ext.grid.plugin.BufferedRenderer', {

                                        })
                                    ]
                                },
                                {
                                    xtype: 'gridpanel',
                                    region: 'center',
                                    title: 'Invoices',
                                    titleCollapse: false,
                                    forceFit: true,
                                    store: 'Invoice',
                                    columns: [
                                        {
                                            xtype: 'numbercolumn',
                                            maxWidth: 120,
                                            minWidth: 50,
                                            dataIndex: 'id',
                                            groupable: false,
                                            lockable: true,
                                            text: 'ID',
                                            tooltip: 'Invoice ID',
                                            format: '0'
                                        },
                                        {
                                            xtype: 'numbercolumn',
                                            hidden: true,
                                            maxWidth: 120,
                                            minWidth: 50,
                                            dataIndex: 'client_id',
                                            groupable: true,
                                            text: 'Client ID',
                                            format: '0'
                                        },
                                        {
                                            xtype: 'gridcolumn',
                                            renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
                                                return record.getStatus().get('name');
                                            },
                                            maxWidth: 200,
                                            minWidth: 100,
                                            dataIndex: 'invoice_status_id',
                                            text: 'Status'
                                        },
                                        {
                                            xtype: 'datecolumn',
                                            maxWidth: 200,
                                            minWidth: 100,
                                            dataIndex: 'issue_date',
                                            text: 'Issue Date',
                                            format: 'd M Y'
                                        },
                                        {
                                            xtype: 'datecolumn',
                                            maxWidth: 200,
                                            minWidth: 100,
                                            dataIndex: 'due_date',
                                            text: 'Due Date',
                                            format: 'd M Y'
                                        },
                                        {
                                            xtype: 'datecolumn',
                                            maxWidth: 200,
                                            minWidth: 100,
                                            dataIndex: 'payment_date',
                                            text: 'Payment Date',
                                            format: 'd M Y'
                                        },
                                        {
                                            xtype: 'templatecolumn',
                                            summaryType: 'sum',
                                            maxWidth: 150,
                                            minWidth: 50,
                                            tpl: [
                                                '${amount}'
                                            ],
                                            defaultWidth: 80,
                                            dataIndex: 'amount',
                                            groupable: true,
                                            text: 'Amount'
                                        }
                                    ],
                                    features: [
                                        {
                                            ftype: 'grouping'
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        });

        me.callParent(arguments);
    }

});
Community
  • 1
  • 1
Levi Putna
  • 2,863
  • 6
  • 30
  • 47

3 Answers3

10

I managed to get the association lookup working by using a callback function but found it much easier to simply do the lookup from the store myself.

Step One

I moved the Proxy from the InvoiceStatus store and onto the InvoiceStatus model and made the InvoiceStatus store autoload.

Step Two

I changed the render method of the Status column to lookup the display name from the InvoiceStatus store like so.

renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
    var store = Ext.data.StoreManager.lookup('InvoiceStatus');
    return store.getById(value).get('name');
},

This proved to be a much simpeler solution.

Community
  • 1
  • 1
Levi Putna
  • 2,863
  • 6
  • 30
  • 47
  • nice workaround! thanks. Obs: remember to check nulls in order to avoid errors, e.g: if (value && value > 0) { /*...*/ } – Jone Polvora Nov 20 '13 at 15:49
3

It looks like you need to set the belongsTo association on the InvoiceStatus child model. You would think that defining the association in one direction would automatically create the association in the other direction, but apparently that's not the case and you must define the association on both parent and children. See here for a more detailed explanation: Why isn't my ExtJS Store Association Working

Community
  • 1
  • 1
James
  • 4,117
  • 2
  • 18
  • 13
  • Thanks James, this was part of the answer. The real problem was that my associated store was a json store and it needed to load before can value could be garbed from it. This required a callback method as part of the render function, this made for a messy load process and I endued up finding an easier method of rendering lookup values. See my answer. – Levi Putna Apr 15 '13 at 03:14
3

Your hasOne should be like this:

hasOne: {
    name:           'status',
    instanceName:   'status',
    associationKey: 'status',
    model:          'MyApp.model.InvoiceStatus',
    foreignKey:     'invoice_status_id',
    getterName:     'getStatus',
    setterName:     'setStatus'
}

The following patch for ExtJS 4.2.2 will let you set dataIndex: 'status.name' without any additional renderers. Grid will show everything OK but sorting will not work.

This solution does not work with async (lazy) status load.

Ext.view.Table.prototype.renderCell = function(column, record, recordIndex, rowIndex, columnIndex, out) {
    var me = this,
        selModel = me.selModel,
        cellValues = me.cellValues,
        classes = cellValues.classes,
        // fieldValue = record.data[column.dataIndex]; // patched
        fieldValue = null,
        cellTpl = me.cellTpl,
        fullIndex, value, clsInsertPoint;

    // Patch start
    if (column.dataIndex && column.dataIndex.indexOf('.') > 0) {
        var associationParts = column.dataIndex.split('.'),
            v = record;

        for (var i = 0; i < associationParts.length-1; i++) {
            v = v['get' + associationParts[i].charAt(0).toUpperCase() + associationParts[i].slice(1)]();
        }
        fieldValue = v.get(associationParts[associationParts.length-1]);
    }
    else {
        fieldValue = record.data[column.dataIndex];
    }
    // Patch end

    cellValues.record = record;
    cellValues.column = column;
    cellValues.recordIndex = recordIndex;
    cellValues.rowIndex = rowIndex;
    cellValues.columnIndex = columnIndex;
    cellValues.cellIndex = columnIndex;
    cellValues.align = column.align;
    cellValues.tdCls = column.tdCls;
    cellValues.innerCls = column.innerCls;
    cellValues.style = cellValues.tdAttr = "";
    cellValues.unselectableAttr = me.enableTextSelection ? '' : 'unselectable="on"';

    if (column.renderer && column.renderer.call) {
        fullIndex = me.ownerCt.columnManager.getHeaderIndex(column);
        value = column.renderer.call(column.scope || me.ownerCt, fieldValue, cellValues, record, recordIndex, fullIndex, me.dataSource, me);
        if (cellValues.css) {
            // This warning attribute is used by the compat layer
            // TODO: remove when compat layer becomes deprecated
            record.cssWarning = true;
            cellValues.tdCls += ' ' + cellValues.css;
            delete cellValues.css;
        }
    } else {
        value = fieldValue;
    }
    cellValues.value = (value == null || value === '') ? '&#160;' : value;

    // Calculate classes to add to cell
    classes[1] = column.getCellId();

    // On IE8, array[len] = 'foo' is twice as fast as array.push('foo')
    // So keep an insertion point and use assignment to help IE!
    clsInsertPoint = 2;

    if (column.tdCls) {
        classes[clsInsertPoint++] = column.tdCls;
    }
    if (me.markDirty && record.isModified(column.dataIndex)) {
        classes[clsInsertPoint++] = me.dirtyCls;
    }
    if (column.isFirstVisible) {
        classes[clsInsertPoint++] = me.firstCls;
    }
    if (column.isLastVisible) {
        classes[clsInsertPoint++] = me.lastCls;
    }
    if (!me.enableTextSelection) {
        classes[clsInsertPoint++] = me.unselectableCls;
    }
    if (cellValues.tdCls) {
        classes[clsInsertPoint++] = cellValues.tdCls;
    }
    if (selModel && selModel.isCellModel && selModel.isCellSelected(me, recordIndex, columnIndex)) {
        classes[clsInsertPoint++] = (me.selectedCellCls);
    }

    // Chop back array to only what we've set
    classes.length = clsInsertPoint;

    cellValues.tdCls = classes.join(' ');

    cellTpl.applyOut(cellValues, out);

    // Dereference objects since cellValues is a persistent var in the XTemplate's scope chain
        cellValues.column = null;
};
Viacheslav Dobromyslov
  • 3,168
  • 1
  • 33
  • 45