Improving Ext JS MVC implementation

August 16, 2012

UPDATE: The ideas and code described in this article have been incorporated into the Ext JS MVC subsystem; the solution has been released as part of Ext JS 4.2. The Ext.ux.Application is still relevant for Ext JS 4.1 applications though.

 

 

Anybody who has been working with Ext JS 4.x probably tried to use what Sencha guys call MVC architecture. It looks good on paper, even better when you first try to get your feet wet with it… But then, when you have already decided that it’s the Right Thing and wrote your first production Controller, you realize that something’s wrong. When you experimented with it, you probably didn’t bother writing any tests, right? It’s just some toy code, an example, why bother? But production code should be tested thoroughly, we’re serious about our code quality… That’s when the bummer moment comes. You can’t unit-test Controller classes!

You plunge in Ext.app.Controller code and realize with dismay that it can’t be instantiated without satisfying its Ext.app.Application bindings. And Application will haul along its dependency tree, all Controllers and Views and Stores and Models, effectively loading up the whole application. That’s wrong, that’s very wrong. But what would you do? The decision to go with Ext JS can’t be changed, commitments are made and deadlines are set. You probably poke around for a bit and then just give in and skip Controller testing altogether, hoping that Sencha will come up with solution Real Soon Now.

Well, it’s been more than a year since Ext JS 4.0 came out but solution isn’t there. I don’t know if Sencha guys see this problem as unimportant, or just don’t have resources to work on it, but I just couldn’t wait anymore. The project I’m responsible for at work is too big to go untested, and test suite should be available right from the start, or else. Naturally I tried to see who was there first. To my dismay, I couldn’t find any ready to use solution. None! There are at least two projects claiming improvements to Ext JS MVC architecture, namely GluJS and DeftJS. Both look interesting, but GluJS code looks downright unreadable, and DeftJS is in 0.x versions, suggesting that code maturity may be of a problem here. Besides, both projects aim at significantly changing existing architecture, while I’m not convinced that it needs to be changed. Improved, yes. Changed? Not necessarily.

So I actually dug deep into MVC code and found out that it wasn’t that bad. In fact it wasn’t bad at all, it looked more like rushed to release but didn’t have any irrecoverable flaws. A bit of refactoring, some guts swapped between Controller and Application, and here we go: Controller is no longer dependent on Application, it can live on its own. There’s still not much sense in trying to use it standalone, but for testing purposes it’s more than enough. Refs? Working. Getters? Here. Control method? Reporting for duty, sir.

Besides testing there was one other thing that bugged me with Ext JS MVC, and that was the missing mechanism for Controller coupling. In simple words, a Controller can control() events from Views, but it can’t receive events fired in other Controllers! But wait, how do you pass events between parts of your application then? Um, well, you can always resort to callback chains… But again, this is wrong. You can have some coupling between your Controllers, or you can have a centralized event bus, but you got to have something, or your application becomes a bowl of spaghetti real quick.

Now let’s take the usual example of a Controller:

Ext.define('My.controller.Foo', {
    extend: 'Ext.app.Controller',

    views: [ 'Foo', ... ],

    init: function() {
        var me = this;

        me.control({
            'foo_xtype': {
                event: me.onFooEvent
            }
        });
    },

    onFooEvent: function() {
        var me = this;

        // Suppose event foo means we need to open another panel.
        // Sure, but that panel is under foreign Controller.
        // What do we do here? Usually, this:
        var panel = new My.view.Bar({ ... });

        // Or, in a sloth-defying case (we feel real smart today)
        me.getController('Bar').openPanelBar();

        // Well, if we are *really truly* against sloppy code
        // we could even do this:
        me.getController('Bar').fireEvent('letsOpenPanelBar');
    },

    ...
});

But neither of these is any good. Wouldn’t it be nice to just have Controllers listen to each other? Like this:

Ext.define('My.controller.Foo', {
    ...

    onFooEvent: function() {
        var me = this;

        me.fireEvent('heyBarLetsOpenAnotherPanel');
    }
});

Ext.define('My.controller.Bar', {
    extend: 'Ext.app.Controller',

    views: [ 'Bar', ... ],

    init: function() {
        var me = this;

        me.control({
            'controller#Foo': {
                heyBarLetsOpenAnotherPanel: me.openPanelBar
            },

            // Or maybe even listen to *all* Controllers?
            'controller': {
                globalEvent: me.achieveWorldDomination
            }
        });
    },

    openPanelBar: function() {
        var me = this,
            panel;

        panel = me.getView('Bar');

        if ( !panel ) {
            panel = new My.view.Bar({ ... });
        }
    }
});

That’s much better. No more tight coupling, no need to stub out Controllers you work with just to test your own, and we’re returning to the old faithful event model. Readability improves significantly, too; no more poking in other Controllers just to see what the heck was that method doing?

Oh, and now that we can listen to other Controllers, why not make Application the top level Controller? It has the traits through inheritance, so we may just as well use them.

Sounds nice, so let’s see how well it will fare in the Real World. The code’s in Github repo as usual. If you have any trouble with it, let me know.

tags: , , ,
posted in Software development by nohuhu

Follow comments via the RSS Feed | Leave a comment | Trackback URL

8 Comments to "Improving Ext JS MVC implementation"

  1. mistaecko wrote:

    Neat idea! In my current application I separate UI events (EventBus + control) from ‘application’ events (fired and captured on the application object). However, this distinction is kind of fuzzy at best and it would probably be a step forward to move these ‘application’ events to the EventBus.

    However, I am not 100% convinced that selectors should reference the originating controller – isn’t that a unnecessary coupling that brings more inflexibility? That’s why I like the possibility to listen to all controller events.

    Personally, I like to abstract events from the underlying source. The controller should not necessarily require to know the layout/structure of the UI components it listens to. One easy way to achieve this is to facilitate ‘item ids’ (e.g. #saveBtn: { click: ___ }).
    The other way is to treat a specific container with its child components as a self-sufficient ‘widget’ event-wise and re-fire relevant events from child components on the parent container. E.g. refire the btn click as ‘save’ event on the panel.
    This way the controller only listens to one source (the panel). E.g. ‘#userList: { ‘save’: ___ }’.

    For the same reason I would probably prefer not to listen to specific controllers for events (e.g. controller#foo: { loginFailed: ___ }, but rather abstract the event to controller: { ‘loginFailed’: ___ } .

    Not sure where I want to go from here, I guess just share my thoughts on event handling and see if you have any thoughts on it as well.

  2. nohuhu wrote:

    Thanks for the feedback, it’s most welcome. This is work in progress and I definitely can’t claim that it won’t change; in fact I may be ready for the next round. This time I’d like to split ref generation out of Controller and make it a generic mixin; this way I could easily reuse that code in my Views that I like to keep self-contained. I’m still thinking this over but it looks more seductive by the day.

    Re: selectors that reference originating controller; not sure what you mean by that. Controller events (or global events, I’m not settled on terminology here) can be seen both by Views and Controllers, you choose if they react or not. I don’t see a big problem in controller reacting to its own events, in my view that’s a good side effect that helps with unit-testing.

    Event abstraction is good, I’m all for keeping balance between Views and Controllers. From what I see, people mostly tend to go into extremes and either put all code into Views so Controllers aren’t necessary (messy way) or dumb Views down to the bare bones and load ’em Controllers (another style of mess). I like my Views to care for themselves when it’s justified, and delegate inter-View functionality to Controllers. The rule of thumb is, when event is fired and consumed within the same View, there’s no point in exposing it; on the other hand, when a View needs to interact with another part of the application, that’s clearly outside of its scope. Some folks argue that putting all code in Controllers makes it easy to replace one View with another, and they usually mean desktop and touch ones. From my point of view, the difference between desktop and touch applications is too big to be covered with View juggling. These two are completely different from UI and UX point of view, and sharing Controller code would probably be the least of worries when you try to port your desktop app to touch devices. At least, that was my impression from taking a quick look at Sencha Touch.

    Selecting on id’s or itemId’s is good, to a point. If you want to reuse that component down the road, it’ll blow up in your face and you’ll have to refactor that; why not do it right the first time? Use selectors in a Controller (or a View if it’s self-contained) with a generic property like ‘purpose’ or ‘event’ for buttons, etc. It keeps your code declarative, concise and really easy to read. Compare:


    me.button = new Ext.button.Button({ itemId: 'saveBtn', ... });
    me.control({
    '#saveBtn': {
    click: function() { this.fireEvent('save'); }
    }

    // And so on for each button, till your fingers hurt
    ...
    });

    and:


    me.button = new Ext.button.Button({ event: 'save' });

    // Once for all buttons
    me.control({
    button: {
    click: function(btn) { this.fireEvent(btn.event) }
    }
    });

    I like that a lot better, especially in complex Views with lots of interacting components.

    As for listening to specific controllers versus all controllers, that’s up to you to decide. Listening to specific controllers was nearly free feature to implement along with global controller events and it may come handy at times, although it may not be the general pattern after all.

    Anyway, thanks for the input!

    Regards,
    Alex.

  3. Smith wrote:

    Alex,

    I’ve been using GluJS (www.glujs.com) for a little while now, and I’ve found that if you are able to make the mental shift to MVVM, it is a great way to develop applications (despite initially being very difficult to read). It uses a different pattern though because the guys at CoNarrative noticed the same problems you saw with the MVC pattern, and they realized that you shouldn’t have to write selectors or references; the framework should automatically handle all of that for you. Then they tackled the other enterprise problems (testing and translation) to make it even easier to build applications.

    Because its a different paradigm, you end up writing code that at first glance looks like magic (read: difficult to understand and maintain), but once you give your mind time to understand the framework, the magic disappears and you realize that its all meant to simplify your life so that you don’t have to fight the extjs controls, and you can focus on the business logic.

    The major shift that I found was rather than having a Controller where you have to write references or selectors to get to the controls in the view and react to events, you have the business logic (your ViewModel) that automatically binds to your view and hooks up all the events properly for you. It has the added bonus of being able to set the view components because the bindings go both ways. So for example when a user changes the value in a control, the value is changed in your viewmodel, and if you change the value in your viewmodel, then its changed in the control the user sees too. Its all handled for you automatically so that you can code in very complex business rules to your application, and let the framework keep things in sync for you.

    The syntax of GluJS is really difficult to look at when you first see it, but I would definitely give it another shot if you can. Once you are able to embrace it for all it is, you really appreciate the simplicity and clean code you end up writing in real world applications. And the syntax itself isn’t much different from other MVVM frameworks like AngularJS or Knockout, but GluJS is designed to work with UI frameworks instead of raw html like those frameworks.

  4. nohuhu wrote:

    Ryan,

    I’ve taken a long, hard look at GluJS. My first impression was very good, but in the end I decided against using it. Maybe the two days I spent reading the docs and looking through the code weren’t enough to convince me, but then again I can’t justify spending another week doing that. Here are some things I didn’t like:

    • I don’t see any problem with controlling my Views’ events, the problem was how to unit-test my selectors and refs. In fact, I like the way selectors work; it gives me great flexibility as to what to control and when, and how to react. I’m not sure I could have something like that with GluJS, and I fail to see any reason to bring in a major dependency just to fight it down the road.
    • The rigidness doesn’t appeal to me very much. It looks like GluJS doesn’t allow mixing Ext JS style MVC with MVVM; it’s either their way or the highway. I don’t like to be forced into a pattern, I like to choose whatever pattern suits my needs. This way GluJS looks pretty bleak.
    • Testing and translation are not big selling points for me. From what I see, GluJS testing approach is totally GluJS-centric and won’t help me with testing what I actually need to test. I18N is a big problem, sure. But I have my own solution for that already in place, why should I replace it with something totally unfamiliar to me? I don’t have any reasons to suspect it will be any better than mine, and it will merely add maintenance burden without making my life radically easier.
    • As you correctly pointed out, GluJS bindings look like magic. I’m not afraid of magic, my milk tongue was Perl and I still love the way I can write crisp and perfectly readable code full of fluffy white magic. On the other hand, GluJS magic doesn’t taste quite like that. It’s hard to read, and the fact that it requires major effort to understand it tells me to avoid it. I don’t know who will end up maintaining the code I write today, and I don’t want to require them to go through major paradigm shifts just to be able to understand what the controller code is doing. Heck, anything but controller! I can stand having spaghetti code inside some opaque 3rd party widget, but controllers are crucial. No voodoo magic is tolerated there, ever.
    • Half of GluJS code looks like implementation of features that were missing from Ext core in 3.x days, done in orthogonal way to what we have now in 4.x. I don’t need this legacy and I don’t want additional maintenance burden. Modular approach would probably work better but from what I see GluJS is pretty much monolithic.
    • The code itself is hardly an example of clarity and good style. I don’t rely on documentation only; readable core code I can browse and understand is a hard requirement. That was the main reason for me to decide against Dojo framework in favor of Ext JS a while ago, and now one of the main reasons against GluJS. Seriously people, can you invest some of your time into cleanup and refactoring? So far I fail to see how this mess could help me write simpler and cleaner code.

    I certainly don’t dismiss GluJS and I plan to revisit it in the future. Maybe I just haven’t bumped into exactly the kind of problem it’s bound to solve, so who knows. But at this point there were more cons than pros in it for me.

    Regards,
    Alex.

  5. lOlive wrote:

    So, in late 2013, what is your opinion about GluJS?

  6. nohuhu wrote:

    No opinion really, I’ve never used it and can’t say how well it fares in Real Life. I will probably not use it in my projects going forward.

  7. Les wrote:

    nohuhu, I’d like to be able to specify in the controller store alias and then be able to listen to events from stores that use this alias.

    Currently, we can control only a store with a particular store id or all stores. I’d like to be able to specify a store type (by using store alias) and listen to a group of stores.

    The current option to listen a specific or all stores is too limiting.

  8. nohuhu wrote:

    Hi Les,

    Yeah, I’ve seen this feature request several times in the forums. I plan to add something like this when I have a chance; not sure when that could happen though. Meanwhile you can use a workaround, something like that:


    Ext.define('Override.app.domain.Store', {
    override: 'Ext.app.domain.Store',

    selectorRegex: /^\[([\w\-]+)\s*=\s*((['"])?((?:\\\]|.)*?)\3)?(?!\\)\]$/,

    match: function(target, selector) {
    var regex = this.selectorRegex,
    match, attr, compareTo;

    // Custom processing time when selector matches the [attr=value] form
    if (match = regex.exec(selector)) {
    attr = match[1];
    compareTo = match[3];

    // In this case, type coercion is deliberate -- we probably don't want
    // the match to fail just because compareTo is a string, and the actual
    // value may be of a different type for all we know.
    if (target[attr] == compareTo) {
    return true;
    }
    }

    // Otherwise, fall back to the default matching
    return this.callParent(arguments);
    }
    });

    I just whipped up this code without even trying it so it may not work on the first run, but this way you can implement any kind of custom syntax for selectors.

    Regards,
    Alex.

Leave Your Comment

 
Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org