Blog.

Blessing and Curse of refs

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.

saki
Follow me:
Latest posts by saki (see all)

5 Responses

  1. I’m interested in putting a container inside HTML like your inline “Create Grid Window” I have put

    like you, and inside the container I have

    renderTo: ‘saki-ext-Refs’,

    but I must be doing something wrong. Any help would be appreciated as this is the only place I’ve seen this.

    1. First of all make sure that the container with ‘saki-ext-Refs’ exists, for example <div id="saki-ext-Refs"></div>

      If you still do not see the expected component, try to renderTo:Ext.getBody() first to debug the code and after it works in body, render it back into the container of your liking.

      1. The element is in index.html inside the body tags. I think my problem is that I have everything inside the main.js which I know isn’t correct, but that’s how the http://docs.sencha.com/extjs/5.0/getting_started/getting_started.html told me to. From what I understand main tries to create a viewport which I don’t want.

        I finally have the container rendering when I open up index.html, but the container is always on top of everything no matter what I do or what I designate it to “renderTo:”

        My problem is I do not have enough experience (I just started last week with no knowledge of css/html/js) to be able to make custom models and views and controllers since I don’t yet know how they all work, but I am trudging through. Thanks for your comment and this amazing site!

        1. Yeah, I do understand feelings of a beginner, I had also been one. I’ll probably make a video to get you and other beginners started if you want to embed an Ext app in an existing page.

          1. That would be great! What do you do about createAutoViewport In app.js? From what I read when a viewport is made it tries to take up the whole webpage.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Enter your username and password to log into your account. Don't have an account? Sign up.

Want to collaborate on an upcoming project?