The MVC Architecture for Ext JS and Touch provides a solid foundation for building large and scalable apps; but if you follow the guides and implement in the way described you’ll notice that controllers and views are highly coupled. Controllers associate listeners to view events using component queries, thus making them aware of how the view is composed. As result we have a MVC architecture where the view is very light and does nothing but define components, and controllers will do all the heavy lifting by responding to user gestures, updating the view, dealing with models, business logic and more.
The current MVC
Currently:
- the view might contain several components;
- each one of these components fire events;
- the controller, using component query, will listen to these events;
- once the event is fired, the controller is notified.
This could be code translated by:
Ext.define('AM.controller.Users', { ... init: function() { this.control({ 'viewport > userlist': { itemdblclick: this.editUser }, 'useredit button[action=save]': { click: this.updateUser } }); }, ... updateUser: function(button) { console.log('clicked the Save button'); } ... });
The problem here is that the controller is fully aware of the structure of the view. It knows that inside the useredit view there’s a button with action=save.
This is a silly example, but I’ve seen worst cases where a controller class had 100 view references. Basically the whole controller was very associated with the view. Any changes on the view would result in refactoring the controller too.
DeftJS
Some months ago the guys from DeftJS wrote an awesome guest post at Sencha’s blog entitled Deft JS: Loosely Coupled MVC through Dependency Injection. They notice the same problematic exposed above, and figured it out that using Inversion-of-Control implemented by Dependency Injection would be the way to solve this.
The post intro was very interesting:
That application you just deployed? As experienced software developers, we all know it won’t be long before you’re going to need make to significant UI changes. Regardless of the amount of painstaking forethought, consensus gathering and planning backing it, no software design ever survives first contact with its users unscathed. To deliver truly effective software, we have to be prepared to adapt to an evolving understating of our users’ needs.
So… how do we architect our software, so we can rapidly implement UI changes without breaking the underlying business logic?
You can read more details on the blog post or their website, but basically there’s something called ViewController, which is a controller scoped to a particular view other than the full application. As a result, all your component queries can be simplified.
Ext.define( 'ContactsApp.controller.ContactGridViewController', extend: 'Deft.mvc.ViewController', mixins: [ 'Deft.mixin.Injectable' ], inject: [ 'contactStore' ], config: { contactStore: null }, control: { contactsGrid: { click: 'onContactsGridClick' } editButton: { click: 'onEditButtonClick' } }, ... destroy: function() { if (this.hasUnsavedChanges) { // cancel destruction return false; } // allow destruction return this.callParent( arguments ); }, ... onEditButtonClick: function () { this.getEditButton.setDisabled( false ); }, onContactsGridClick: function () { // add a ContactEditorView to the TabPanel for the selected item ... }, );
Control on Containers
Surprisingly, Sencha Touch not only can use control queries inside Ext.app.Controller but also use control for Ext.Container. So we can move all component queries from Controller to View.
Ext.create('Ext.Container', { control: { 'button': { tap: 'hideMe' } }, hideMe: function() { this.hide() } });
Why would you do that? Well, follow the code below:
/* * Create order form view */ Ext.create('App.view.OrderForm', { extend: 'Ext.form.Panel', xtype: 'orderform', control: { 'button[action=submit]': { tap: 'onBtnSubmitTap' } }, //... /** * Get the values from the form, add the id's selected * from the categories list, and fire the save event */ onBtnSubmitTap: function(btn) { var values = this.getValues(), selectedCategories = this.down('#list-categories').getSelection(), categories = []; Ext.each(selectedCategories, function(category) { categories.push(category.getId()); }); values.categories = categories; this.fireEvent('save', this, values); } }); /** * Order Controller */ Ext.create('App.view.OrderController', { extend: 'Ext.app.Controller', control: { 'orderform': { save: 'onOrderFormSave' } }, /** * The controller does not need to know how the view is structred. * All it needs is the values, nothing else. */ onOrderFormSave: function(formView, values) { Ext.Ajax.request({ url: 'orders/save', params: values // ... }); } });
Basically we are inverting the control as well. Instead of having the Controller accessing the internals of the view and pulling out the values, we have the view dealing with its own logic to get the values, and then passing them for the controller to execute the business logic.
This leads to two major improvements:
- Changes in the view don’t affect controllers. If I change the way users select categories from a list to a radio button group, nothing changes in the controller.
- Controllers are easily testable. In test specs we just need to mock the values and test if the controller method is responding correctly.
And all of this without using any extensions, just changing the way we approach our architecture. Well, this is not completely true for Ext JS. Unfortunately the event system in Ext JS does not allow delegation in an easy way like Touch. You’d have to inform an array of events on the bubbleEvents config option for each component, something not very feasible. Perhaps in the future releases.
There’s still room for improvements…
This post just exposes some different approaches to the default Sencha MVC. Whether you’re going to use the default MVC or choose an alternative approach like DeftJS, Container Control or something customized, is up to you.
I have some more ideas that I’ll try to exemplify in future posts. Meanwhile let me know your thoughts about the whole subject with your comments.
Nice Blog Post “@extdesenv: Alternatives for decoupling Controllers and View on #Sencha Touch #ExtJS http://t.co/jeg99wsm”
Thanks for the article. I wrote a post about my concerns on the MVC way of doing things in ExtJS 4:
http://www.sencha.com/forum/showthread.php?235886-DISCUSS-MVC-Architecture-in-ExtJS-4
One thing I still don’t understand, how can you get the reference to the orderForm? Seems that it is through the xtype, which means that still you have a weak reference to it, since you can have more than one orderForm in your page, which would break it, thus then you would need to use selectors to identify which instance of orderForm you are referring to.
This conceptually has the same problem as the itemId reference.
Also, in one way you are solving the problem of the controller being unware of the view, which is good, but on another way you are pushing the controller on the view as kind of a middle layer.
I’m not sure how to solve the problem, I just think conceptually the MVC in ExtJS 4 is a bit broken, because it’s very prone to error.
I cannot find a way of Controllers in ExtJS to act similar to a normal Spring MVC controllers that just react to one thing: Url’s
I like your thoughts on the forum post, it’s really the same concern I have. And you’re right, the controller will listen to view events still using Component Query, but only by the view xtype, never going deeper than that. This solves the issue with modifying the view layout & components and breaking the controller.
I don’t see how having more than one instance of the OrderForm at the same time would break it. Controllers will receive the data, perhaps even the view instance, and act on that particular instance. That way they don’t need to differentiate between each view.
Regarding having controllers react to URL’s, this is easier on Touch since you have the Routing system (that perhaps will get into Ext in future releases). Meanwhile I wrote an extension Ext.ux.Router for having routes on Ext. If you spend some time on it, perhaps you can create some crazy good approach for Ext JS MVC :) Let me know…
Cheers!
Hi Bruno,
Thanks for answering, it’s good to be able to discuss the Architecture in ExtJS.
Here’s an example where querying by xtype would break it:
Ext.ns(‘Test’);
Test.MainPanel = Ext.extend(Ext.Container, {
initComponent:function () {
this.gridPanel1 = new Test.GridPanel({
listeners:{
eventForRowSelection:function (record) {
this.gridPanel2.getSelectionModel().selectRecords([record]);
},
scope:this
}
});
this.gridPanel2 = new Test.GridPanel();
this.items = [this.gridPanel1, this.gridPanel2];
Test.MainPanel.superclass.initComponent.call(this);
}
});
Test.GridPanel = Ext.extend(Ext.grid.GridPanel, {
store:new Ext.data.Store(),
columns:[],
initComponent:function () {
this.sm = new Ext.grid.RowSelectionModel({
singleSelect:true,
listeners:{
rowselect:function (selModel, rowIndex, record) {
this.fireEvent(‘eventForRowSelection’, record)
},
scope:this
}
});
Test.GridPanel.superclass.initComponent.call(this);
}
});
As you can see, although both grids have the same xtype, and are exactly the same component, I’m interested in whenever I select something on the first grid it selects the same record on the second grid. So, following DeftJS approach, I would be listening in my controller for events of type “eventForRowSelection” but who would fire it? Both grids would fire the event since I’m listening for events for xtype:’testgrid’, where in my case I’m only interested in the event from the first grid. Of course, you can object that on the event I can just pass the reference for the grid that fired the event, but then I still need to distinguish to which grid I’m getting the event from which means I need a way of distinguishing the grids.
Sorry for the formatting, but I have no clue how to format the code in your blog to be displayed properly…
Thanks, great article! Now I think I finally understand the role of DeftJS. I am discussing how I solve these issues in a series of posts: http://tunein.yap.tv/javascript/2012/08/06/sencha-events/
Alternatives for decoupling Controllers and View on #Sencha Touch #ExtJS http://t.co/bOHUoSA7 via @extdesenv
Also using ID attributes, which are local to the current view and not the whole application, lets you move the view componentents into different places/containers without changing the controller.
I’m also interested on a better architecture for both Sencha frameworks as we have to standarize for our future applications.
From the comments of a Sencha blog post (http://www.sencha.com/blog/automating-unit-tests/) I found http://www.glujs.com which seems a pretty good attempt to solve things introducing a MVVM architecture to ExtJS/Sencha Touch
I’m not confident enough to fully embrace it yet but seems promising
Nice angel, thanks! I will take a look at GluJs. Didn’t know of it’s existence… I would love to be able to implement some sort of MVC on my older 3.x applications.
Angel,
I’ve actually been using GluJS for a few months now, and have been absolutely loving it! We decided to go full force with it in our organization rather than trying to deal with the selectors and component queries and its made a huge impact on our products. DeftJS, it seems, tries to fix the inherent problems with ExtJS’s MVC model by pushing logic from the view to the controller, but why bother with any of that at all? If you are able to build your business logic in a viewmodel, then you can bind its state to the view and you let gluJS deal with all the hassle of which events to hook things up to. You don’t have to write view components that push information to the controller, that’s what gluJS does automatically for you so you don’t have to worry about it. And it removes all the inconsistencies in ExtJS while its at it (attributes like ‘pressed’ are controlled by ‘toggle’ in buttons for example) It allows you to focus on building (and testing) the business logic of your application without having to worry about the view at all. In fact, we build out all the viewmodels and test them with specifications (that even our managers can read) to prove that we have the business logic built and ready to go. Then we simply throw in a view (the actual extjs components) and gluJS binds the attributes of those controls to the business viewmodel automatically for us. It actually took us a few weeks to get the hang of it, but once you are able to fundamentally switch your brain away from the patterns that ExtJS has (MVC, or what they had in 2.x and 3.x) then you really get a feel for the power that gluJS has and you’ll wonder why you haven’t been doing it since the beginning. I would definitely give it a shot if you can!
Hi Smith,
in your experience have you found a lot of issues using glujs? I have to recommend a new framework (and give support) for a large community of programmers and my concern comes from the inmaturity of the solution. It’s a very young project and I don’t know if it has been proven “in the wild” for long. I will keep an eye on it and try it a bit more for future adoption.
Besides it still holds a pre-release tag for Sencha Touch to which I have to give support.
Hi Angel,
I know what you mean about a “young” project, but in reality this has been in the works for several years now. Its just young because it was recently released to the world. It started when Ext 3.x was first released, and is continuing now with Ext 4.x and Sencha Touch. I don’t know what the state of Touch support is exactly, but I know that the guys at CoNarrative are working on it.
In my personal experience, I have found it to be extremely stable. When things aren’t working, 99% of the time its because I’ve screwed something up. The learning curve is a bit challenging because you have to start thinking in terms of MVVM instead of MVC or older paradigms, but the forums are up and if you have any questions about it I know they would be more than happy to help you through it (they have been EXCELLENT with me!).
And if you do find a bug in their code (which is simpler than you might expect) they are really good about taking pulls to improve the product. Like anything, it will always need work, but its going into our production environment and I would HIGHLY recommend it to anyone. Once you make the switch you’ll wonder how you ever got anything done before and why you ever thought it was OK to write that much code :)
Angel,
GluJS has come a long way since its beginning and will continue to evolve with the support of its creators at CoNarrative and other contributors on GitHub.
In the first version of GluJS we laid the groundwork for adding Sencha Touch support. Recently a new branch was created, “Touch”, that includes the start of fully supporting Sencha Touch. The branch also expands on the todoMVC example by using the same viewmodel for Ext JS and Sencha Touch.
It’s still a work in progress, but with a few more contributors it could be done very soon. :)
Travis
I found out about GluJS before I had posted this one. I didn’t play with it yet, but it seems pretty awesome. Changing the whole architecture to MVVM is awesome, and helps make our environment even more rich with different options.
I’m waiting for a spare time to play with it and share some thoughts, but I really appreciate your comment.
Hi Bruno,
Will any of this approach be present in ExtJS 5?
Cheers!
Very cool. I’m in the middle of refactoring a large project and found myself wishing there was an additional layer of control between the controller and view. The idea that the view itself could act as a defacto controller, responsible for its own behavior as well as well as the layout is certainly an interesting take and would definitely help clean some things up. i’ll have to look into options.
You wo81&nd#u2l7;t believe how many emails I got on this column, some were sad, some were angry, some were filled with regret, and some were even saying they were glad they went for the kill with their lawyer. Me thinks thou dost protest too much.
I see a lot of interesting content on your page. You have to spend a lot of time writing, i know how to save you a lot of time,
there is a tool that creates unique, SEO friendly articles in couple of minutes,
just type in google – laranita’s free content source