Sencha Inc. | HTML5 Apps

Architecting Your App in Ext JS 4, Part 3

Published Sep 19, 2011 | Tommy Maintz | Tutorial | Medium
Last Updated Oct 20, 2011

This Tutorial is most relevant to Ext JS, 4.x.

In the previous series of articles Part 1 and Part 2, we explored architecting a Pandora-style application using the new features of Ext JS 4. We started by applying the Model-View-Controller architecture to a complex UI that has multiple views, stores and models. We looked at the basic techniques of architecting your application, like controlling your views from Controllers, and firing application-wide events that controllers can listen to. In this part of the series, we will continue implementing controller logic inside of the application’s MVC architecture.

Getting References

Before we continue implementing our application, we should review some of the more advanced functionality available in the Ext JS 4 MVC package. In the previous part of this series, we showed how you could automatically load stores and models in your application by adding them to the stores and models arrays in your Ext.application configuration. We also explained that an instance would be created for each store loaded in this way, giving it a storeId equal to its name.

app/Application.js

Ext.application({
    ...
    models: ['Station', 'Song'],    
    stores: ['Stations', 'RecentSongs', 'SearchResults']
    ...
});

In addition to loading and instantiating these classes, adding stores and models into these arrays also automatically creates getters for you. This is also the case for controllers and views. The stores, models, controllers and views configurations also exist in Controllers and work exactly the same way as they do in the Application instance. This means that in order to get a reference to the Stations store inside of the Station controller, all we need to do is add the store to the stores array.

app/controller/Station.js

...
stores: ['Stations'],
...

Now we can get a reference to the Stations store from anywhere in the controller using the automatically generated getter named getStationsStore. The convention is straightforward and predictable:

views: ['StationsList'] // creates getter named 'getStationsListView' -> returns reference to StationsList class
models: ['Station']     // creates getter named 'getStationModel'     -> returns reference to Station model class
controllers: ['Song']   // creates getter named 'getSongController'   -> returns the Song controller instance
stores: ['Stations']    // creates getter named 'getStationsStore'    -> returns the Stations store instance

It’s important to note that the getters for both views and models return a reference to the class (requiring you to instantiate your own instances), while the getters for stores and controllers return actual instances.

Referencing view instances

In the previous section, we described how the stores, models, controllers and views configurations automatically create getters allowing you to easily retrieve references to them. The getStationsListView getter will return a reference to the view class. In our application flow, we would like to select the first item in our StationsList. In this case, we don’t want a reference to the view class; instead, we want a reference to the actual StationsList instance that is inside our viewport.

In Ext JS 3, a very common approach to getting a reference to an existing component instance on the page was the Ext.getCmp method. While this method continues to work, it’s not the recommended method in Ext JS 4. Using Ext.getCmp requires you to give every component a unique ID in order to reference it in your application. In the new MVC package, we can put a reference to a view instance (component) inside of a controller by leveraging a new feature in Ext JS 4: ComponentQuery.

app/controller/Station.js

...
refs: [{
    // A component query
    selector: 'viewport > #west-region > stationslist',
    ref: 'stationsList'
}]
...

In the refs configuration, you can set up references to view instances. This allows you to retrieve and manipulate components on the page inside of your controller’s actions. To describe the component that you want to reference, you can use a ComponentQuery inside the selector property. The other required information inside of this object is the ref property. This will be used as part of the name of the getter that will be generated automatically for each item inside the refs array. For example, by defining ref: ‘stationsList’ (note the capital L), a getter will be generated on the controller called getStationsList. Alternatively, if you did not set up a reference inside your controller, you could continue to use Ext.getCmp inside of the controller actions. However, we discourage you from doing this because it forces you to manage unique component ID’s in your project, often leading to problems as your project grows.

It’s important to remember that these getters will be created independent of whether the view actually exists on the page. When you call the getter and the selector successfully matches a component on the page, it caches the result so that subsequent calls to the getter will be fast. However, when the selector doesn’t match any views on the page, the getter will return null. This means that if you have logic that depends on a view and there is a possibility that the view does not exist on the page yet, you need to add a check around your logic to ensure it only executes if the getter returned a result. In addition, if multiple components match the selector, only the first one will be returned. Thus, it’s good practice to make your selectors specific to the single view you wish to get. Lastly, when you destroy a component you are referencing, calls to the getter will start returning null again until there is another component matching the selector on the page.

Cascading your controller logic on application launch.

When the application starts, we want to load the user’s existing stations. While you could put this logic inside of the application’s onReady method, the MVC architecture provides you with an onLaunch method which fires on each controller as soon as all the controllers, models and stores are instantiated, and your initial views are rendered. This provides you with a clean separation between global application logic and logic specific to a controller.

Step 1

app/controller/Station.js

...
onLaunch: function() {
    // Use the automatically generated getter to get the store
    var stationsStore = this.getStationsStore();        
    stationsStore.load({
        callback: this.onStationsLoad,
        scope: this
    });
}
...

The onLaunch method of the Station controller seems like the perfect place to call the Station store’s load method. As you can see, we have also set up a callback which gets executed as soon as our store is loaded.

Step 2

app/controller/Station.js

...
onStationsLoad: function() {
    var stationsList = this.getStationsList();
    stationsList.getSelectionModel().select(0);
}
...

In this callback we get the StationsList instance using the automatically generated getter, and select the first item. This will trigger a selectionchange event on the StationsList.

Step 3

app/controller/Station.js

...
init: function() {
    this.control({
        'stationslist': {
            selectionchange: this.onStationSelect
        },
        ...
    });
},

onStationSelect: function(selModel, selection) {
    this.application.fireEvent('stationstart', selection[0]);
},
...

Application events are extremely useful when you have many controllers in your application that are interested in an event. Instead of listening for the same view event in each of these controllers, only one controller will listen for the view event and fire an application-wide event that the others can listen for. This also allows controllers to communicate to one another without knowing about or depending on each other’s existence. In the onStationSelect action, we fire an application event called stationstart.

Step 4

app/controller/Song.js

...
refs: [{
    ref: 'songInfo',
    selector: 'songinfo'
}, {
    ref: 'recentlyPlayedScroller',
    selector: 'recentlyplayedscroller'
}],

stores: ['RecentSongs'],

init: function() {
    ...
    // We listen for the application-wide stationstart event
    this.application.on({
        stationstart: this.onStationStart,
        scope: this
    });
},

onStationStart: function(station) {
    var store = this.getRecentSongsStore();

    store.load({
        callback: this.onRecentSongsLoad,
        params: {
            station: station.get('id')
        },            
        scope: this
    });
}
...

As part of the init method of the Song controller, we have set up a listener to the stationstart application event. When this happens, we need to load the songs for this station into our RecentSongs store. We do this in the onStationStart method. We get a reference to the RecentSongs store and call the load method on it, defining the controller action that needs to get fired as soon as the loading has finished.

Step 5

app/controller/Song.js

...
onRecentSongsLoad: function(songs, request) {
    var store = this.getRecentSongsStore(),
        selModel = this.getRecentlyPlayedScroller().getSelectionModel();   

    selModel.select(store.last());
}
...

When the songs for the station are loaded into the RecentSongs store, we select the last song in the RecentlyPlayedScroller. We do this by getting the selection model on the RecentlyPlayedScroller dataview and calling the select method on it, passing the last record in the RecentSongs store.

Step 6

app/controller/Song.js

...
init: function() {
    this.control({
        'recentlyplayedscroller': {
            selectionchange: this.onSongSelect
        }
    });
    ...
},

onSongSelect: function(selModel, selection) {
    this.getSongInfo().update(selection[0]);
}
...

When we select the last song in the scroller, it will fire a selectionchange event. In the control method, we already set up a listener for this event; and in the onSongSelect method, we complete the application flow by updating the data in the SongInfo view.

Starting a new station

Now, it becomes pretty easy to implement additional application flows. Adding logic to create and select a new station looks like:

app/controller/Station.js

...
refs: [{
    ref: 'stationsList',
    selector: 'stationslist'
}],

init: function() {
    // Listen for the select event on the NewStation combobox
    this.control({
        ...
        'newstation': {
            select: this.onNewStationSelect
        }
    });
},

onNewStationSelect: function(field, selection) {
    var selected = selection[0],
        store = this.getStationsStore(),
        list = this.getStationsList();

    if (selected && !store.getById(selected.get('id'))) {
        // If the newly selected station does not exist in our station store we add it
        store.add(selected);
    }

    // We select the station in the Station list
    list.getSelectionModel().select(selected);
}
...

Summary

We have illustrated that by using some advanced controller techniques and keeping your logic separate from your views, the application’s architecture becomes easier to understand and maintain. At this stage, the application is already quite functional. We can search for and add new stations, and we can start stations by selecting them. Songs for the station will be loaded, and we show the song and artist information.

We will continue to refine our application in the next part of this series, with the focus on styling and custom component creation. However, we have reached a point where we can share the source code in it’s current state for your review. Enjoy and please provide feedback.

Download Project Files

Share this post:
Leave a reply

Written by Tommy Maintz
Tommy Maintz is the original lead of Sencha Touch. With extensive knowledge of Object Oriented JavaScript and mobile browser idiosyncracies, he pushes the boundaries of what is possible within mobile browsers. Tommy brings a unique view point and an ambitious philosophy to creating engaging user interfaces. His attention to detail drives his desire to make the perfect framework for developers to enjoy.
Follow Tommy on Twitter

26 Comments

Anton Fischer

3 years ago

Many thanks! This is a very useful series. I look forward to continuing..

Adam Ratcliffe

3 years ago

Really liking this series of articles Tommy. Presume the pandora directory from the download is expected to be copied into the examples directory of your Ext JS 4 installation?

I also needed to enable Ext.Loader by adding the following statement to app.js:

Ext.Loader.setConfig({enabled:true});

Javier Caride

3 years ago

I’ve been developing ExtJS Applications since 2.0 version was published but I have to admit that your articles are very useful not only for new developers but also for experienced ones.

I hadn’t read anything about ExtJS 4.x changes until I read this articles. Now I’m pleasantly surprised by the effort made by ExtJS team to make the architecting of ExtJS Apps easier.

In previous versions this task was a very big headache.

Thanks for your articles Tommy.

Rob Boerman

3 years ago

Very nice and clear tutorial again Tommy! Web application development is getting easier by the minute with Sencha’s masterful products. Looking forward to applying these design patterns to Sencha Touch (2) too.

The are some typos in your article:
- you are adding a stationsList ref (capital L) in the stations controller but are referencing ‘stationslist’ when adding a listener to is using this.control
- in your last example you are again creating the stationsList ref which should be a ref to the newstation component

Keep up the great writeups, looking forward to hearing more in Austin

Julien Moisan

3 years ago

Nice article, Thanks tommy

I’m searching for good(best) practice to cascade controller in a big application.
I used the way to declare store & view into controller, and model into store, but i want to cut my application in many small controller and i want this to be call as view when they are used. For moment i use Require with callback function but it seems to be not the good solution.

Maybe next time we will have an article over this !

reb_elba

3 years ago

Great article.
I hope in the next part, I learn even the ability to edit and store data on the server. For instance to change the song information. That would be a great thing for me.

Thanks for the great introduction to extjs

Robert Squire

3 years ago

Thank you.  I’ve been looking forward to this installment and to downloading your source files.

One question, when I try to load it I get the following error in firebug:

uncaught exception: Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. Missing required class: Pandora.store.Stations

I added the following to app.js and it worked:

Ext.Loader.setConfig({enabled:true});

Is that configuration necessary in each app or can that be done in the ext library itself?

Ronnie

3 years ago

Very good tutorial. Imo the most important text you can currently read on extjs4. Looking forward to next installment in the series. Keep up the good work…

Tommy Maintz

3 years ago

@Adam Ratcliffe
Yes, it’s supposed to go into the examples folder. I was under the assumption myself that the loader was enabled by default. In any case it’s good to know that I should enable it in my code before distributing it.

@Rob Boerman
1. The ref property is used to generate a getter. Thus ref: ‘stationsList’ generates a getter called getStationsList. In the control method, every key in the object is a component query, not the name of a reference.
2. I never need a reference to the NewStation component, thats why I’m not setting up a ref for it. In the onNewStationSelect method I am getting a reference to the StationsList. Thats why I showed that reference once more in that code snippet.

@Julien
Check part 2 in the article. I mention a technique to dynamically load additional controllers. You can get on at any point by calling the this.getController(‘nameofcontroller’) method in another Controller.

@reb
I will make sure to talk about modifying/adding data to stores, and synchronizing it to the server side in one of the upcoming articles in this series.

@Robert
As soon as you make a custom build for the app, all the source files needed by the app should be loaded at startup time. During development it is much eachier however to enable the Ext.Loader. The reason I forgot to put it in is because I developed the sample in the Ext JS git repo. In our repo we always have the Loader enabled by default.

@Ronnie
Thanks, I will try smile

Rob Boerman

3 years ago

Hi Tommy,

Thanks for the feedback. Again I learned something new smile
With the ref’s, I was confused because when you created the stationsList ref you used the more verbose ‘viewport > #west-region > stationslist’ componentQuery whereas in the control method you only used ‘stationslist’.
Would’t it be cleaner to work with view refs that are set in one place in the controller and hooking control’s up on those? This way componentQueries will get scattered all through the controller.
Just a suggestion smile thanks for the great tutorials.

Dan

3 years ago

Thanks for this great series!  I have a question though.  In the Docs the MVC Architecture Guide puts the Model, Store, and View declarations in the Controller, and here you put it all in app.js.  What are the pros/cons of these approaches?

extjsnewbie

3 years ago

Is it possible to load the field names in the model dynamically? For instance, in the Users example the fields are declared like this
fields: [‘id’,‘name’, ‘email’]
In my application,all these fields would be coming from database, In that case how would I set the model?

Leonardo Redmond

3 years ago

I’ve tried the downloaded files and I couldn’t get the web page, i had got only white page and using Firefox developer tools, i had checked the error. Error is following:

c is not a constructor
[Break On This Error] (function(){var e=this,a=Object.protot…ate(“Ext.XTemplate”,j,g)}return j}});
ext-all.js (line 15)

How can I solve this problem?

Thanks/

Gary

3 years ago

Thanks for this series!
(Leonardo - I got the same error -  it looks like we need to have this run in a webserver - I tried with nginx and it worked - probably apache would also work. ( it did not like the “file://” protocol with the stores - said “cross-site scripting only allowed with http” - in Chrome)
also with as noted above—Ext.Loader.setConfig({enabled:true}); added to app.js and the “Architecting…” directory located in the examples subdirectory. )

Kent Pettersson

3 years ago

Does anyone know what steps are required to be able to use this code-example with the SDK - sencha command: “sencha create jsb”? I´ve switched from “bootstrap.js” to “ext-debug.js” but still all required js files are not included in the .jsb3 file.

Ian Hayes

3 years ago

I’ve downloaded this Pandora demo code and created a web project inside MyEclipse r9.1, but when I run the web app all I get is a blank page.

Elsewhere I’ve run the simple demo app that displays some a short html message so I know that the basic configuration is in place.

Any ideas?

Ian

3 years ago

Scratch that last comment… when I rebooted MyEclipse the web app now displays the javascript app using MyEclipse’s own internal version of Tomcat.

Arthur Park

2 years ago

Excellent guide.

I had few glitches though. In Station controller, a callback functionon (onStationsLoad) is not fired after stationsStore is loaded. (I think it’s supposed to select the first row once the store is loaded)

If I change callback: onStationsLoad to callback: function() { ... }, the functions is fired but I still get “Uncaught TypeError: Cannot call method ‘getSelectionModel’ of undefined”

Has anyone else experienced that? And any solution?

Also, how do I build this project?

Majid

2 years ago

Hi,
I am new here, thank your for this article,
Do I have to use a web server (tomcat) to run this app ?
I just downloaded this example, when I saw only html and js files I just double clicked in the index.html, but I only got a blanc page.

please your help ia appreciated.
Thanks

Paul

2 years ago

Hello, could you please explain more about the getters. I don’t understand when and where they can be used, and am currently resorting to storing references to all controllers in a global object…

rampager

2 years ago

looking forward for the styling and custom component creation part ^^

Robert

2 years ago

I would like to use modules as part of the MVC structure, similar to Zend Framework. For example:

app/modules/blog/controller/
app/modules/blog/view/
app/modules/blog/model/
app/modules/blog/store/

app/modules/user/controller/
app/modules/user/view/
app/modules/user/model/
app/modules/user/store/

etc.

As you can see, each module has its own MVC structure. This allow us to have a better structure in large projects, and easily reuse/share modules in different projects.

Any idea where to start ?

nextSTEP

2 years ago

Create a new application for each module. This is the way I’m dealing with add-ins:

Ext.Loader.syncModeEnabled = true;

var app = Ext.create(“Ext.app.Application”, {
name     : addIn.name,
appFolder : addIn.path,

controllers : addIn.controllers
});

Ext.Loader.syncModeEnabled = false;

MyApp.addIns = MyApp.addIns || {};
MyApp.addIns[addIn.name] = app;

Robert

2 years ago

Thanks. I will try that.

prasanna

2 years ago

Hi, i’m new to Extjs. I have gone through your tutorial it’s look cool, but i’m not clear with many things. So i thought after running the attached project files will help me in understand the concept.

But i’m facing following error while running the project files inside tomcat webserver.

“uncaught exception: Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. Missing required class: Pandora.store.Stations”
Line 0”

So my personal obligation, if you also provide steps on running this examle project will help many people like me grin

Please consider.

Mats

2 years ago

@prasanna: There’s one line missing in the app.js unfortunately:

Ext.Loader.setConfig({enabled:true});

Hope that helps smile

Leave a comment:

Commenting is not available in this channel entry.