If you’ve been paying attention to anything Sencha has been talking about since the release of Ext JS 5, you’ve certainly noticed a shift in emphasis in application structure. With Ext JS 4, Sencha provided a pretty robust architecture for developing scalable applications in an MVC(ish) approach. This was a MASSIVE upgrade from the 2.x and 3.x days in which the kind of plumbing needed to pull off something like this was cumbersome to build and, of course, not native to the framework itself. Despite its shortcomings, 4.x’s MVC provided the tools to build applications on a solid foundation very quickly and in a manner that was scalable and reproducible.

But of course, scale always reveals the shortcomings in architecture. As great as Ext JS 4.x’s MVC is (I still like it…), sufficient scale coupled with lack of foresight on the part of application developers can lead to collisions in events and selectors. Additionally, without facilities for easily creating and destroying controllers, scaling a 4.x app always means a directly proportional increase in resources required to manage an application’s controllers, which (under normal circumstances) exist perpetually from the moment the application is initialized.

Partly in response to these challenges, and partly as a natural evolution of the framework itself (perhaps with a friendly nudge from libraries like Deft.js), Ext JS 5 shifts architecturally once again. Instead of just expanding the MVC-centric approach of 4.x, Ext JS 5’s main emphasis has moved to an MVVM (Model/View/ViewModel) approach. In this new emphasis, however, the controller is not forgotten. Rather, a new subclass of controller has been introduced. The ViewController encapsulates the main uses of class 4.x controllers, but leave behind the bulk and inflexibility of a global, always “on” class.

The ViewController

As its name implies, the ViewController is a special controller which is designed to work integrally with a particular view. Whereas 4.x controllers were (out of the box) created along with the initialization of the application itself, ViewControllers live and die with the views to which they are bound. Moreover, ViewControllers are a sandbox of sorts. Since they are intertwined with an instance of a particular view, they are exclusively about the view (and child views) to which they are bound, and nothing else. Say goodbye to event collisions based on too-generic selectors; since ViewControllers are only aware of their particular view’s instance, they cannot collide with other event listeners of other views, even if those views are instances of the same class.

Structure

To see a ViewController in action, let’s more or less dupe the “User” grid which we created a few posts ago. To do this, create the following files:

  • app/view/people/People.js
  • app/view/people/PeopeController.js

Before we move on, notice the structure and naming conventions. As far as I can tell, it does not matter that the ViewController is named “Class+Controller”, nor that it exists in the same package as its corresponding view. That being said, I would recommend coming up with a good convention that works for you AND STICK WITH IT. The magical things added in Ext JS 5 will not help you if you do not think somewhat forward-ly about scale, naming conventions, and other standards!

Ok, first here is our People.js view class:

Ext.define('ExtJS5Explorer.view.people.People', {
    extend: 'Ext.panel.Panel',
    alias: 'widget.people',
    controller: 'people',
    items: [
        ...grid def here...
        ...detail def here....
    ]
}

We’ll look at the other parts later, but the most important thing to notice here is that the ONLY thing we need to change in our view is the new controller config in which we tell our app which ViewController we want to be bound to this view. Easy, huh? Now if only we had a ViewController…

PeopleController.js

Ext.define('ExtJS5Explorer.view.people.PeopleController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.people
}

I know it looks really crazy, but literally that’s it. With those lines, we have successfully created a View + ViewController All that’s left is to actually do something with them 🙂

Listening to Events: Choices, Choices, Choices!

In Ext JS 4.x, the introduction of MVC led to a very concerted messaging effort to get people to use control (and listen in 4.2x+) within their controllers to respond to component (and other domain) events. This made sense, of course: If you’re going to use a single controller to manage N views (which I think alot of us did…), having a common management point for all event handling worked pretty well. The challenge, of course, became scale. The bigger your app gets, the harder it was to keep all of these global, always-aware components away from each others’ selector. Careful planning had to be used to avoid collisions, and even then it was sometimes difficult to keep everything straight. And forget trying to explain your own concocted conventions to a stranger to the code…

In Ext JS 5, the *preference* seems to be to specify listeners on views themselves, instead of utilizing selectors ala the Ext JS 4.x standard. While I’m not entirely clear on what this preference is based, I think there is a certain level of logic to it:

{
    xtype: 'panel',
    ...
    listeners: {
        itemclick: 'onItemClick',
        scope: 'controller' (or 'this')
    }
}

An important note here. With Ext JS 5, the listeners have been upgraded to include named scopes. By default, all listeners will use the “controller” scope, which obviously specifies the view’s bound ViewController. However, if for some reason you want to specify a listener directly on the view (please don’t…), you can use the “this” scope (notice the quotation marks).

Whether you’re using listeners directly on the view itself, or defining listeners via control in the ViewController, you’ll still benefit directly from the one-to-one relationship between View and ViewController. Even if you were to create 2, 3 or more instances of our “people” grid at the same time, all of the listeners/selectors defined between the View and View Controller would be sandboxed within each View/ViewController pair’s instance. It’s simply not possible for events to “leak.”

Even if using the listeners config is the preferred approach, there are certainly times when you’ll still want to leverage control/listen within your ViewController. For example, let’s say that you have a bunch of form fields and you want to do some bit of logic every time one of them fires a change event. Obviously, you could define a listeners config on each field, but this would be tedious. Such would be an ideal scenario to define a generic listener for all field elements.

You might also continue using the control/listen paradigm, simply because you like it, or because you are migrating old, global controllers to be ViewControllers. If so, there is one fairly significant caveat you should keep in mind. For all children of your ViewController’s view, your normal syntax within control/listen will work just fine. For events on the view itself, however, a special selector has been added:

control: {
    '#': { // special selector '#' will match the view itself
        show: 'onPanelShow'
    },
    'button' { // will match *all* descendant buttons of this view
        click: 'onButtonClick'
    }
}

Finally, there might be times when you need to fire events on the view itself. Previously, you might just get a reference to the view and manually call fireEvent on it. In a ViewController, however, a nice, shortened version has been added:

this.fireViewEvent( 'selectperson', arg1, arg2, etc );

As its name suggests, this will fire the specified event on the view to which the ViewController is bound. Note, however, that this is specifically and exclusively for the view itself; fireViewEvent will not work for descendant listeners:

Ext.define('ExtJS5Explorer.view.people.People', {
   extend: 'Ext.panel.Panel',
   controller: 'people',
   ...
   listeners: {
       selectperson: 'onSelectPerson' // works
   },
   items: [
      {
         xtype: 'grid',
         listeners: {
            selectperson: 'onSelectPersonGrid' // won't work
         }
      }
   ]
})

...

Ext.define('ExtJS5Explorer.view.people.PeopleController', {
   ...
   onItemClick: function( ... ) {
      this.fireViewEvent( 'selectperson', ... );
   }
})

In this example, we have two listeners set up for the selectperson event: one on the view itself, and one on a child component of the view. If we use fireViewEvent() within our ViewController, only the event listener on the view itself will be successfully processed.

References

In the 4.x controller model, one of the persistent challenges was how to get references to specific view instances. Using the refs config, you could create references based on selectors. But of course, this ran aground pretty quickly on the multiple instance conundrum. Ultimately, a lot of 4.x controllers ended up littered with a lot of code whose only purpose was to resolve view references.

In Ext JS 5, a new references config has been added to ease the pain of this common issue. This special config instructs the component to register itself with its owning view (and by extension, the managing ViewController). The huge win here, of course, is efficiency. In 4.x, we often used the itemId config to provide a simple “hook” to use for ref registration. However, even with the itemId a component query still had to be made to retrieve it on demand. With the new references cache, however, no such component query is necessary. Again, since the ViewController has a one-to-one relationship with its view, any references that are defined are encapsulated within the View/ViewController instance. Therefore, lookup is dead simple and instantaneous, with zero chances of collisions, even with other instances of the same view.

Here’s our reference in action:

People.js

// detail def here
{
    xtype: 'people.detail',
    reference: 'detail',
    ...
}

PeopleController.js

{
    onItemClick: function( ... ) {
        this.lookupReference( 'detail' ).update( record.data );
    }
}

Easy, right? All we did was define a reference on our target component, and then in our ViewController used the lookupReference method to return the desired instance.

An important thing to keep in mind when using references, however, is that they should be used for child components/views, not the view itself. For example, consider this hierarchy:

// main view def
{
   controller: 'people',
   items: [
      {
         xtype: 'people.detail', // has own ViewController
         reference: 'detail'
      }
   ]
}

Since the people.detail view has its own ViewController, you might be tempted to think that the reference (detail) would be applicable to the DetailController. However, this is not the case. To the contrary, the reference is available only within the outer PeopleController. If you need to get the actual view which is bound to your ViewController, just use getView.

Conclusion

There we have it, a very brief introduction to ViewControllers. While I still have a lot to learn about the ins and outs of utilizing them, I am very excited about the prospect. I think they provide a lot of glue that was missing in Ext JS 4, and their absolute encapsulation of controller functionality to particular view instances will drastically reduce a number of the frustrations that naturally arose in the course of scaling apps in the Ext JS 4.x days.

So what about you? Are you excited for ViewControllers? Let me know in the comments!