Controller config option refs provides an easy way to get refereneces to views within the controller. However, there's a catch. In some situations refs do not work as expected or cannot be used at all.

Share This Post

refs
If you use MVC architecture in ExtJS or Sencha Touch you most likely already used refs configuration option of controller. The purpose of refs is to list views at the controller for easy later access. An accessor, a getter, method is automatically created for each listed ref.   For example:
Ext.define('My.Controller', {
     extend:'Ext.app.Controller'
    ,refs:[{
         ref:'myWindow'
        ,selector:'mywindow'
    }]
});
Having the above controller configuration, we can call the controller method getMyWindow to get a reference to the window instance. The following code works (provided it is run in the scope of the controller).
var win = this.getMyWindow();
win.show();
That’s the sunny part of life. No long ComponentQuery calls, no special handling of class variables, just a simple array of refs and that’s it.

Where’s the problem then?

It happens quite often, especially in larger apps, that we need more than one instance of the window. What then? Which one is returned by getMyWindow? First? Last? Or all in an array? I’ve created a little MVC application with one grid wrapped in an Ext window. You can create multiple independent instances of the grid. Grid has “Remove record” button in the top toolbar that is enabled by selecting a grid row and disabled by deselecting. First, create one instance of the grid to test it works that way.  
  Then create two instances and see what’s happening: The first grid works as before but selecting and deselecting a row in the second grid enables and disables button in the first grid. If you close first grid, second starts to work. Weird isn’t it? Now, let’s analyze the code to see why it is happening.

Grid configuration:

Ext.define('Saki.view.PersonGridView', {
     extend: 'Ext.grid.Panel'
    ,alias:'widget.persongrid'

    ,enableRemove:function(enable) {
        this.down('button#removerecord').setDisabled(!enable);
    } // eo function enableRemove

    // ... etc
});
There is nothing special here: we define the grid, give it an alias and implement button enable/disable public method.

Controller configuration:

Ext.define('Saki.controller.MainController', {
     extend: 'Ext.app.Controller'
    ,refs:[{
         selector:'persongrid'
        ,ref:'personGrid'
    }]
    ,init:function() {
        var me = this;
        me.control({
            persongrid:{
                selectionchange:me.onSelectionChange
            }
        })
    } // eo function init

    ,onSelectionChange:function(selModel, records) {
        var  me = this
            ,grid = me.getPersonGrid()
        ;
        grid.enableRemove(!!records.length);
    } // eo function onSelectionChange

    // ... etc
});
We define our refs that selects xtype persongrid so that we can later call getPersonGrid() method to grab a reference to the grid. We also listen to grid selectionchange event. The listener (onSelectionChange method) only tests that anything is selected and calls grid’s method to update the enable state of the button.

The culprit!

When user selects a record in any grid controller knows that a row in some grid has been selected, onSelectionChange runs but getPersonGrid returns the reference to the first grid only. Internally, Ext.ComponentQuery.query('persongrid') is run but although that returns the array of all instances, only the first one is returned by getPersonGrid.

Conclusion

You can and should use refs but with caution: The moment we need more than one instance of a view the controller refs are useless, perhaps worse than that because they can be the source of hard-to-find bugs.  
Refs are good if their selectors can never select more than one view.

The solution

Simply stated: use initComponent and custom events if you need multi-instance views.
Follow me:
Latest posts by saki (see all)

Want to collaborate on an upcoming project?