7
votes

Using Extjs 4.2

I have read many docs,google,forums trying to understand how components load and where to place them such as stores, models, ect but still confused.

This is an example I am trying to get working

Application description

Main menu with Contacts,Projects,People etc etc, app first loads to display a static non data-driven, then use clicks on Contacts which displays a grid with list of contacts. User then clicks on a contact row and popup edit view is displayed.

In contacteditview the contact is loaded into the form in addition the form has a combobox that loads ContactTypes store. The ContactType should be set to contacttype value for that contact record.

What is the common way to do this considering this is a large application and I only want to load the data when it is need ie the view is displayed.

Here are some of my confusions

  1. You can define all of you Controllers, Stores, Models, Views in the app config, but then everything is loaded no matter what page you are looking at. In addition if you have autoLoad: true on your stores then calls for all the stores are made to the database even though you are not looking at that particular view.

  2. When you specify a store or model in the stores or models property in your controller, what exactly does that do? Is it just letting you easily reference the store but not actually creating it or does it create it or is it just setting getter and setter functions for convenience. Example if I specify a store in the store property of my controller and have autoLoad true all data is loaded with no need to do anything else. But what I really want is for that store to only load when I click on Contact and the list view is displayed. So I set autoLoad:false and in my list function I manually get store using this.getStore('Contacts'). This works fine, but what is purpose of using stores and models array property of controller. I see in debugger that if I do not use the store/model properties a get request is made for those js files.

    Sorry I know this is a lot but it is very confusing to me and have been struggling with it for awhile.

  3. Lastly, I have the Contact grid loading only when you click on the Contacts button by setting autoLoad:false and manually loading. Now when a user clicks edit the record loads fine but how do I get the combobox to load and then select the correct value. I think part of my problem is understanding how stores models get loaded and instantiated. In my combobox store property I have specified ContactType as the store but I get error store undefined, so either the js file is not loaded or store is not instantiated.


Here is my code so far.

App Code

Ext.Loader.setConfig({
    enabled: true,
    paths: {
        'Ext.ux': "lib/extux",
        'Wakanda': "lib/extux/wakanda"
    }
});
Ext.application({
    name: 'SimplyFundraising',
    autoCreateViewport: true,

    requires: ['Ext.ux.Router', // Require the UX
        'Wakanda.model'
    ],

    controllers: ['Contacts'],
});

Contacts Controller

Ext.define('SimplyFundraising.controller.Contacts', {
    extend: 'Ext.app.Controller',


    views: ['contacts.List', 'contacts.Edit'],
    init: function() {
        this.control({
            'contactslist': {
                itemdblclick: this.editContact,
                removeitem: this.removeContact
            },
            'contactslist > toolbar > button[action=create]': {
                click: this.onCreateContact
            },
            // 'contactsadd button[action=save]': {
            // click: this.doCreateContact
            // },
            'contactsedit button[action=save]': {
                click: this.updateContact
            }
        });
    },
    list: function() {

        var mystore = this.getStore('Contacts')
        mystore.load();
        // mystore.proxy.extraParams = { $expand: 'ContactType'};
        //        var User = this.getContactModel();
        //        User.load(258, {
        //            success: function (user) {
        //                console.log("Loaded user 258: " + user.get('lastName'));
        //            }
        //        });
    },
    editContact: function(grid, record) {
        var view = Ext.widget('contactsedit');
        view.down('form').loadRecord(record);
        this.addnew = false
    },
    removeContact: function(Contact) {
        Ext.Msg.confirm('Remove Contact ' + Contact.data.lastName, 'Are you sure?', function(button) {
            if (button == 'yes') {
                this.getContactsStore().remove(Contact);
            }
        }, this);
    },
    onCreateContact: function() {
        var view = Ext.widget('contactsedit');
        this.addnew = true
    },
    // doCreateContact: function (button) {
    // var win = button.up('window'),
    // form = win.down('form'),
    // values = form.getValues(),
    // store = this.getContactsStore();
    // if (form.getForm().isValid()) {
    // store.add(values);
    // win.close();
    // }
    // },
    updateContact: function(button) {
        var win = button.up('window'),
            form = win.down('form'),
            record = form.getRecord(),
            values = form.getValues(),
            store = this.getContactsStore();
        if (form.getForm().isValid()) {
            if (this.addnew == true) {
                store.add(values);
            } else {
                record.set(values);
            }
            win.close();
        }
    }
});

Contacts view list

Ext.define('SimplyFundraising.view.contacts.List', {
    extend: 'Ext.grid.Panel',
    xtype: 'contactslist',
    title: 'All Contacts',
    store: 'Contacts',
    autoHeight: true,
    autoScroll: true,
    viewConfig: {
        loadMask: true
    },
    initComponent: function() {
        this.tbar = [{
            text: 'Create Contact',
            action: 'create'
        }];
        this.columns = [{
                header: 'Id',
                dataIndex: '__KEY',
                width: 50
            }, {
                header: 'First Name',
                dataIndex: 'firstName',
                flex: 1
            }, {
                header: 'Middle Name',
                dataIndex: 'middleName',
                flex: 1
            }, {
                header: 'Last Name',
                dataIndex: 'lastName',
                flex: 1
            },
            {
                header: 'Type',
                dataIndex: 'ContactType.name',
                flex: 1
            }
        ];
        this.addEvents('removeitem');
        this.actions = {
            removeitem: Ext.create('Ext.Action', {
                text: 'Remove Contact',
                handler: function() {
                    this.fireEvent('removeitem', this.getSelected())
                },
                scope: this
            })
        };
        var contextMenu = Ext.create('Ext.menu.Menu', {
            items: [this.actions.removeitem]
        });
        this.on({
            itemcontextmenu: function(view, rec, node, index, e) {
                e.stopEvent();
                contextMenu.showAt(e.getXY());
                return false;
            }
        });
        this.callParent(arguments);
    },
    getSelected: function() {
        var sm = this.getSelectionModel();
        var rs = sm.getSelection();
        if (rs.length) {
            return rs[0];
        }
        return null;
    }
});

Contacts view edit

Ext.define('SimplyFundraising.view.contacts.Edit', {
    extend: 'Ext.window.Window',
    xtype: 'contactsedit',
    title: 'Edit Contacts',
    layout: 'fit',
    autoShow: true,
    initComponent: function() {
        this.items = [{
            xtype: 'form',
            bodyStyle: {
                background: 'none',
                padding: '10px',
                border: '0'
            },
            items: [{
                xtype: 'textfield',
                name: 'firstName',
                allowBlank: false,
                fieldLabel: 'Name'
            }, {
                xtype: 'textfield',
                name: 'lastName',
                allowBlank: false,
                fieldLabel: 'Last Name'
            }, {
                xtype: 'combobox',
                fieldLabel: 'Contact Type',
                name: 'contactType',
                store: 'ContactTypes',
                displayField: 'name',
                typeAhead: true,
                queryMode: 'local',
                emptyText: 'Select a type...'
            }]
        }];
        this.buttons = [{
            text: 'Save',
            action: 'save'
        }, {
            text: 'Cancel',
            scope: this,
            handler: this.close
        }];
        this.callParent(arguments);
    }
});

1
Did you find any answer to your question? I am facing similar problems and am confused about how Stores are managed in Ext.js MVC paradigm.dan_l
No unfortunately not. I have stopped using Extjs because of bad docs and not good support and am now using qooxdoo, it is similiar to Extjs and so far I have not have any problems plus the documentation and examples are much better. And it is completely free LGPL license meaning you can use it in commercial products. Check it out qooxdoo.org. The support is community driven and is pretty good especially when compared to Sencha support. And it is actively being developed + active blog updates.Only problem I had was the initial setup just understanding the build/project creation.dan

1 Answers

2
votes

Don't miss up with ExtJs. I know, it could be a pain ...

For your problem, I solved this way:

I have a grid that list municipalities of Italy. I want to filter by country, region and province, so I put three comboboxes on a docked container. In the controller I have:

     ,init : function (application) {
        this.control({
           ,"#municipalitiesGrid": { afterrender: this.onMunicipalitiesGridAfterRender }
         });
      }
 ,onMunicipalitiesGridAfterRender: function(grid, opts) {
  console.info('GVD.controller.Geo->onMunicipalitiesGridAfterRender');
  var store = grid.getStore(),
      comboCountriesMunicipalities = this.getComboCountriesMunicipalities(),
      storeCountries = comboCountriesMunicipalities.getStore(),
      comboRegionsMunicipalities = this.getComboRegionsMunicipalities(),
      storeRegions = comboRegionsMunicipalities.getStore(),
      comboProvincesMunicipalities = this.getComboProvincesMunicipalities(),
      storeProvinces = comboProvincesMunicipalities.getStore();

      store.clearFilter(true);
      storeCountries.clearFilter(true);
      storeRegions.clearFilter(true);
      storeProvinces.clearFilter(true);

      storeRegions.filter("idCountry", 114); // 114 = Italia
      storeProvinces.filter("idRegion",8);   // 8 = Emilia Romagna
      store.filter("idProvince", 37);        // 37 = Bologna

      storeCountries.load({
        scope: this,
    callback: function(records, operation, success) {
      storeRegions.load({
        scope: this,
        callback: function(records, operation, success) {
          storeProvinces.load({
            scope: this,
        callback: function(records, operation, success) {
           store.load({
             scope: this,
            callback: function(records, operation, success) {
               comboCountriesMunicipalities.setValue(114); // 114 = Italia
               comboRegionsMunicipalities.setValue(8);     // 8 = Emilia Romagna
               comboProvincesMunicipalities.setValue(37);  // 37 = Bologna      
            }
           });
        }
          });
        }
         });
       }
      });
}

In the controller, of course, I have other listeners for comboboxes's 'select' event, so I can filter and reload combo according to the values selected.

Following MVC patter, my stores are similar to this:

Ext.define('GVD.store.Municipalities', {
     extend: 'Ext.data.Store'
    ,constructor: function(cfg) {
        console.info('GVD.store.Municipalities->constructor');
        var me = this;
        cfg = cfg || {};
        me.callParent([Ext.apply({
             autoLoad: false
            ,autoSync: true
            ,model: 'GVD.model.Municipalities'
            ,pageSize: 20
        }, cfg)]);
    }
});

And models similar to:

Ext.define('GVD.model.Municipalities', {
    extend: 'Ext.data.Model',

    fields: [
        {   name: 'id',             type: 'int'         },
        {   name: 'idIstat',        type: 'int'         },
        {   name: 'idCountry',      type: 'int'         },
        {   name: 'idRegion',       type: 'int'         },
        {   name: 'idProvince',     type: 'int'         },
        {   name: 'name',           type: 'string'      },
        {   name: 'chief_town',     type: 'boolean'     },
        {   name: 'altitude_zone',  type: 'int'         },
        {   name: 'altitude',       type: 'int'         },
        {   name: 'coastal',        type: 'int'         },
        {   name: 'mountain',       type: 'int'         },
        {   name: 'surface',        type: 'double'      },
        {   name: 'residents',      type: 'int'         },
        {   name: 'icon',           type: 'string'      }
    ]
    ,proxy: {
        api: {
             create: 'Municipalities.create'
            ,destroy: 'Municipalities.destroy'
            ,read: 'Municipalities.read'
            ,update: 'Municipalities.update'
        }
        ,reader: {
             root: 'data'
            ,totalProperty: 'totalCount'
            ,type: 'json'
        }
        ,type: 'direct'
    }
});

And referenced in my grid this way:

Ext.define('GVD.view.system.geo.ListMunicipalities', {
     autoScroll: true
    ,constrain: true
    ,dockedItems: [{
        xtype: 'topBar'
    },{
         items: [{
                 allowBlank: true
                ,fieldLabel: 'Nazione'
                ,flex: 1
                ,id: 'comboCountriesMunicipalities'
                ,labelAlign: 'right'
                ,labelWidth: 50
                ,listConfig: {
                    getInnerTpl: function() {
                        return  '<img src="resources/images/countries/16/{icon}16.gif" align="left">&nbsp;&nbsp;{italianName}';
                    }
                }
                ,store: Ext.create('GVD.store.Countries', {pageSize: 999})
                ,xtype: 'comboCountries'    
         },{
                 allowBlank: true
                ,fieldLabel: 'Regione'
                ,flex: 1
                ,id: 'comboRegionsMunicipalities'
                ,labelAlign: 'right'
                ,labelWidth: 50
                ,listConfig: {
                    getInnerTpl: function() {
                        return  '<img src="resources/images/regions/16/{icon}16.gif" align="left">&nbsp;&nbsp;{name}';
                    }
                }
                ,store: Ext.create('GVD.store.Regions', {pageSize: 999})
                ,xtype: 'comboRegions'  
         },{
                 allowBlank: true
                ,fieldLabel: 'Provincia'
                ,flex: 1
                ,id: 'comboProvincesMunicipalities'
                ,labelAlign: 'right'
                ,labelWidth: 50
                ,listConfig: {
                    getInnerTpl: function() {
                        return  '<img src="resources/images/provinces/16/{icon}16.gif" align="left">&nbsp;&nbsp;{name}';
                    }
                }
                ,store: Ext.create('GVD.store.Provinces', {pageSize: 999})
                ,xtype: 'comboProvinces'    
         }]
        ,layout: 'hbox'
        ,xtype: 'container'
    }, {
         dock: 'bottom'
        ,itemId: 'municipalitiesPagingToolbar'
        ,store: 'Municipalities'
        ,xtype: 'pagingToolBar'
    }]  
    ,extend: 'Ext.window.Window'
    ,height: 400
    ,icon: 'resources/images/GVD/municipalities16.png'
    ,id: 'listMunicipalities'
    ,items: [{
         columns: [{
            xtype: 'rownumberer'
        },{
             align: 'right'
            ,dataIndex: 'id'
            ,format: '000'
            ,renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
                return '<img src="resources/images/municipalities/16/'+record.data.icon+'16.gif" align="left">&nbsp;&nbsp;'+record.data.id;
            }
            ,text: 'Id'
            ,width: 70
            ,xtype: 'numbercolumn'
        },{
             align: 'right'
            ,dataIndex: 'idIstat'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'numeric' }
            ,format: '000000000'
            ,text: 'Istat'
            ,width: 80
            ,xtype: 'numbercolumn'          
        },{
             dataIndex: 'name'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'string' }
            ,flex: 1
            ,text: 'Denominazione'
            ,xtype: 'gridcolumn'
        },{
             dataIndex: 'chief_town'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'numeric' }
            ,text: 'Capoluogo'
            ,width: 40
            ,xtype: 'numbercolumn'      
            },{
             dataIndex: 'altitude_zone'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'numeric' }
            ,format: '0'
            ,text: 'Zona alt.'
            ,width: 40
            ,xtype: 'numbercolumn'
        },{
             align: 'right'
            ,dataIndex: 'altitude'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'numeric' }
            ,format: '0000'
            ,text: 'Altitudine'
            ,width: 40
            ,xtype: 'numbercolumn'
        },{
             dataIndex: 'coastal'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'numeric' }
            ,format: '0'
            ,text: 'Costiero'
            ,width: 40
            ,xtype: 'numbercolumn'
        },{
             dataIndex: 'mountain'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'numeric' }
            ,format: '0'
            ,text: 'Montano'
            ,width: 40
            ,xtype: 'numbercolumn'
        },{
             align: 'right'
            ,dataIndex: 'surface'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'numeric' }
            ,format: '000,000.00'
            ,text: 'Superficie'
            ,xtype: 'numbercolumn'
        },{
             align: 'right'
            ,dataIndex: 'residents'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'numeric' }
            ,format: '0,000,000'
            ,text: 'residenti'
            ,xtype: 'numbercolumn'
        },{
             dataIndex: 'icon'
            ,editor: { allowBlank: false, selectOnFocus: true }
            ,filter: { type: 'string' }
            ,flex: 1
            ,text: 'Icona'
            ,xtype: 'gridcolumn'
        }]
        ,columnLines: true
        ,emptyText: '<font color="red"><b>Nessun comune in archivio</b></font>'
        ,features: [
            Ext.create('GVD.ux.grid.FiltersFeature', {  
                encode: true, 
                ftype: 'filters', 
                local: false,
                menuFilterText: 'Filtro'
            })
        ]       
        ,id: 'municipalitiesGrid'
        ,plugins: [ Ext.create('Ext.grid.plugin.RowEditing', { ptype: 'rowediting'  })  ]
        ,selModel: { selType: 'checkboxmodel', mode: 'MULTI' },store: 'Provinces'
        ,store: 'Municipalities'
        ,viewConfig: {
             loadingText: 'Caricamento dati'    
            ,stripeRows: true
            ,trackOver: true
        }
        ,xtype: 'grid'
    }]
    ,layout: {
         align: 'stretch'
        ,type: 'vbox'
    }
    ,margin: '0 0 2 0'
    ,maximizable: true
    ,minimizable: true
    ,requires: [
         'GVD.ux.combo.Countries'   
        ,'GVD.ux.combo.Provinces'
        ,'GVD.ux.combo.Regions' 
        ,'GVD.ux.PrintButton'
        ,'GVD.ux.toolbar.BottomBar'
        ,'GVD.ux.toolbar.PagingToolBar'
        ,'GVD.ux.toolbar.TopBar'
    ]   
    ,singleWindow: true
    ,title: 'Elenco comuni'
    ,tools: [
         { xtype: 'printButton',    title: 'Elenco Comuni', tooltip: 'Stampa elenco'    }
        ,{ type: 'help',    xtype: 'tool',  tooltip: 'Guida sulla funzione' }
    ]
    ,width: 760
});

Hope this can help.

bye