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.
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.
Link | August 22nd, 2012 at 9:54 PM
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.
Link | August 22nd, 2012 at 10:42 PM
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.
Link | August 28th, 2012 at 10:35 AM
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 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.
Link | August 30th, 2012 at 10:45 AM
lOlive wrote:
So, in late 2013, what is your opinion about GluJS?
Link | September 10th, 2013 at 1:45 AM
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.
Link | September 10th, 2013 at 10:46 AM
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.
Link | February 10th, 2014 at 12:42 PM
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.
Link | February 10th, 2014 at 2:51 PM