One of the most common tasks for a developer when developing an Ext JS application is dealing with the complexity of nested components. For example, let’s say that you have a simple Ext.panel.Form definition that resembles something like this:

  • Form Panel
    • Toolbar
      • Save Button
      • Cancel Button
    • Field Set
      • Field Container
        • Field 1
        • Field 2
      • Field Container
        • Field 3
        • Field 4
    • Field Set
      • Field Container
        • Field 5

Leaving aside all that Ext JS does behind the scenes to simplify management of forms in general, we see that even a simple form definition like this has a fairly complex structure.

Now to the task at hand: given this complex structure, how do we go about retrieving a given component from within this hierarchy? For example, let’s say that we want to specifically target Field 5. How do we do it?

By Id

One approach that was used in the past was to manually assign an id to the component and use Ext.getCmp() to retrieve it.

// field definition
...
{
    xtype: 'textfield',
    id: 'myfield'
}
// let's get it...
var field = Ext.getCmp( 'myfield' );

The really nice part of this is that it’s fast. The downside, however, can be pretty significant. Since the id has to be unique, you not only have to manually assign unique ids to your lookup-able components (imagine doing this in a huge, complex application!), but you can easily run into errors when trying to create multiple instances of the same component…precisely because of the unique id collision.

NOTE: Ext.getCmp() still works, and works really well. However, it is considered best practices to avoid using it unless you absolutely need to or can confidently guarantee the uniqueness of the id.

Component Query

Another approach that is very common in Ext JS 4 applications is a component query (via refs, traversal methods like down(), up(), etc.). Consider this example:

// form definition 
{
    xtype: 'form',
    title: 'My Form',
    items: [
       {
           xtype: 'fieldset',
           title: 'Personal Info',
           itemId: 'personalInfo'
           ...
       }
    ]
}
// get the fieldset
var fieldset = myform.down( '#personalInfo' );

This approach is nice because itemIds are scoped to the instance of their container, so having multiple components with the same itemId will not cause errors like duplicate ids would. Another big benefit is that this approach is super-flexible. Using component queries, you can match on any valid selector–itemId, xtype, and much more–which can come in very handy.

The downside, of course, is performance. When using down() in our example, the descendant components of myForm will be traversed until a component matching our selector (an itemId of “personalInfo”) is found. In fairness, our example will still be very fast. However, such traversal does come with a cost and should be kept in mind when considering the best way to approach certain tasks.

References

Ext JS 5 introduces a new way of dealing with these scenarios. The reference config instructs the component to register itself with its owning view. If you’re using a ViewController, this is done automatically. Otherwise, you’ll want to assign a referenceHolder config to the owning view.

// form definition
{
    xtype: 'form',
    referenceHolder: true,
    title: 'My Form',
    items: [
        {
            xtype: 'fieldset',
            title: 'Personal Info',
            reference: 'personalInfo'
        }
    ]
}

Now that we’ve assigned the reference, getting our fieldset is as simple as using the lookupReference() method

// get the fieldset by reference
var fieldset = myForm.lookupReference( 'personalInfo' )

On the face of it, this looks very similar to the query approach we looked at before. In the background, however, it’s quite a bit different and more efficient. As discussed above, the query method requires the descendants of myForm to be traversed until a component matching the selector is found. WIth references, no search is needed since the registration cache is simply consulted and the reference is returned.

More About referenceHolder

As mentioned above, you can set the referenceHolder config to specify that the container will be the point in the hierarchy where references to items with the reference config will be held (hence, “referenceHolder”). An important thing to keep in mind, however, is that there can be multiple reference holders configured within a hierarchy. Consider these two examples:

Ext.define('MyForm', {
    extend: 'Ext.form.Panel',
    referenceHolder: true,
    items: [
        {
            xtype: 'fieldcontainer',
            reference: 'name',
            items: [
                {
                    xtype: 'textfield',
                    name: 'firstName',
                    reference: 'firstName',
                    fieldLabel:'First Name'
                },
                {
                    xtype: 'textfield',
                    name: 'lastName',
                    reference: 'lastName',
                    fieldLabel: 'Last Name'
                }
            ]
        },
        {
            xtype: 'fieldcontainer',
            reference: 'personal',
            items: [
                {
                    xtype: 'datefield',
                    name: 'DOB',
                    reference: 'DOB',
                    fieldLabel: 'Date of Birth'
                },
                {
                    xtype: 'textfield',
                    name: 'email',
                    reference: 'email',
                    fieldLabel: 'Email'
                }
            ]
        }
    ]
});

In this first example, we have a single referenceHolder at the top of the hierarchy. If we were to use getReferences() to retrieve all references for this view, we’d get 6: “name, firstName, lastName, personal, DOB, email”.

Ext.define('MyForm', {
    extend: 'Ext.form.Panel',
    referenceHolder: true,
    items: [
        {
            xtype: 'fieldcontainer',
            reference: 'name',
            referenceHolder: true,
            items: [
                {
                    xtype: 'textfield',
                    name: 'firstName',
                    reference: 'firstName',
                    fieldLabel:'First Name'
                },
                {
                    xtype: 'textfield',
                    name: 'lastName',
                    reference: 'lastName',
                    fieldLabel: 'Last Name'
                }
            ]
        },
        {
            xtype: 'fieldcontainer',
            reference: 'personal',
            referenceHolder: true,
            items: [
                {
                    xtype: 'datefield',
                    name: 'DOB',
                    reference: 'DOB',
                    fieldLabel: 'Date of Birth'
                },
                {
                    xtype: 'textfield',
                    name: 'email',
                    reference: 'email',
                    fieldLabel: 'Email'
                }
            ]
        }
    ]
});

This example is pretty much the same, with two important differences. Instead of having a single referenceHolder at the top of our hierarchy, we have two additional reference holders configured. If we were to do getReferences() again from the top of our hierarchy, instead of 6 references, we’ll only get two: ‘name’ and ‘personal’.

This happens because reference holders are encapsulated. Any references in the hierarchy will be “held” by the closest reference holder ancestor. In our example, then, the “name” and “personal” references are themselves reference holders, and so we will need to retrieve their descendant references by calling lookupReference() on their ancestor reference holder:

// try to get "DOB" reference
myForm.lookupReference( 'DOB' ); // won't work, because of reference holder encapsulation
// get "name" reference holder as reference from form
var nameFieldset = myForm.lookupReference( 'name' );
// try to get "DOB" reference
nameFieldset.lookupReference( 'DOB' ); //works because "name" is referenceHolder of DOB

NOTE: The use of getReferences() above was for demonstration purposes only. Please see the important notes about the usage of this method in the documentation.

Conclusion

As we’ve seen, the addition of references within Ext JS 5 provide a powerful (and fast!) new approach to managing complex component hierarchies. I hope this has been a useful introduction to the concept. Thoughts or questions on using references? Leave a comment!

Thanks!