Skip to content

Commit

Permalink
Refactored the controller loading and wrote the book chapter about it
Browse files Browse the repository at this point in the history
  • Loading branch information
Boris committed Feb 4, 2013
1 parent 4346cf0 commit 623b07c
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 10 deletions.
63 changes: 63 additions & 0 deletions book.md
Expand Up @@ -121,3 +121,66 @@ The full loading order is therefore (--> means "loads" or "depends on"):
controller.js --> some_model.js
some_view.js --> template
````

### mini-app internal code structure
Each mini-app represents a closed functionality of the application.
The external API of the mini-app is the `Marionette.Controller` which is responsible to creating and loading all the models and views.
The controller serves as an internal and external events vent for all the events within the miniapp. The mini-app directory contains the
`views` and the `models` directories. The `views` directory contains all the modules which return the constructor function for any kind of Marionette view (e.g. `Marionette.CompositeView`).
Inside the `views` directory we have the `templates` directory which contains all the templates consumed by the views of the mini-app. The `models` directory contains all the modules
which return either `Backbone.Model` or `Backbone.Collection` constructor functions. It is also possible to separate each construct to its own folder but since they are so tightly coupled
together we decided to put everything in one folder.
(Note: A constructor function is what you get when you call Backbone.XXX.extend({}).)

# Application Flow
### Loading your mini-apps
We start the discussion of this section after the loader loads all the controllers of all the mini-apps.
At this point the loader calls the `controller.start()` function on each controller which, in turn, returns a jQuery promise.
The loader waits until all the promises are resolved and then starts the application. We dive into the load flow of the todo-list controller
as this flow should be similar for all controllers. In the controller `start` function we do two main things:

* The collections are created and loaded via the `fetch` function.

* The topmost views are loaded via the async `require` statement.

* The router of the mini-app is loaded via the async `require` statement.

After the async loads are done:

* An instance of each view is created and shown in the appropriate `Marionette.Region`.

* We register on the promise of the `fetch` function returned from the collection so that when it has been resolved
we update the todoList.

```js
todosCollection = new TodoItemCollection(),
todoPromise = todosCollection.fetch();
todoPromise.done(function () {
_this.vent.trigger("todosUpdated", { collection: todosCollection });
});
```

An important thing to notice is that we don't wait for the collection to finish loading before we show the views.
We want an empty UI (or some "loading..." screen) to be visible to the user and when the collection is loaded it will fill the elements in the UI.

### Views and Sub-Views
In the previous section the controller loaded the topmost views (usually only the main
`Marionette.Layout` is loaded in this stage). Each view of type
`Marionette.Layout`, `Marionette.CompositeView`, and
`Marionette.CollectionView` (i.e. the parent view) is responsible for
loading all the sub-views (sub-views are the views that are embedded within
the parent view) through the `define` statement of the module where the parent view
is defined. In our example the controller loads the `MainLayoutView` into the main
region of the page called `section`. `MainLayoutView`, as the name suggests, extends `Marionette.Layout`
and therefore it loads all the embedded views: `main_header_view`, `main_content_view`, and `main_footer_view`.

Each view of type `Marionette.Layout`, `Marionette.CompositeView`,
and `Marionette.ItemView` is responsible
for loading its template. We will discuss views in details in the next section but for now
we can see that the template is loaded trough the `define` statement of the parent view
much like the sub-views.

```js
define(['marionette', 'hbs!./templates/main_layout', './main_header_view', './main_content_view', './main_footer_view', './../controller'],
function (Marionette, layoutTemplate, MainHeaderView, MainContentView, MainFooterView, controller) {
```
10 changes: 5 additions & 5 deletions js/loader.js
@@ -1,15 +1,15 @@
/*global define*/

define(['require', './app', './todo-list/controller', './todo-list/router'],
function (require, App) {
define(['require', 'jquery', './app', './todo-list/controller'],
function (require, $, App) {
'use strict';
var loader = {
start: function () {
var controller = require('./todo-list/controller');
//Load and start all the controllers
controller.start();
//Load and start all the controllers
$.when(controller.start()).then(function () {
App.start();

});
}
};
return loader;
Expand Down
9 changes: 6 additions & 3 deletions js/todo-list/controller.js
@@ -1,7 +1,7 @@
/*global define*/

define(['require','backbone', 'marionette', 'underscore', 'js/app', './models/todo_item_collection'],
function (require, Backbone, Marionette, _, App, TodoItemCollection) {
define(['require', 'jquery', 'backbone', 'marionette', 'underscore', 'js/app', './models/todo_item_collection'],
function (require, $, Backbone, Marionette, _, App, TodoItemCollection) {
'use strict';
var Controller = Marionette.Controller.extend({
vent: _.extend({}, Backbone.Events),
Expand Down Expand Up @@ -31,16 +31,19 @@ define(['require','backbone', 'marionette', 'underscore', 'js/app', './models/to

start: function () {
var _this = this,
result = $.Deferred(),
todosCollection = new TodoItemCollection(),
todoPromise = todosCollection.fetch();

require(['./views/main_layout_view', './views/footer_view'], function (MainLayoutView, FooterView) {
require(['./views/main_layout_view', './views/footer_view', './router'], function (MainLayoutView, FooterView) {
App.section.show(new MainLayoutView({ todosCollection: todosCollection }));
App.footer.show(new FooterView());
todoPromise.done(function () {
_this.vent.trigger("todosUpdated", { collection: todosCollection });
});
result.resolve();
});
return result.promise();
}

});
Expand Down
2 changes: 1 addition & 1 deletion js/todo-list/views/main_layout_view.js
Expand Up @@ -3,7 +3,7 @@
define(['marionette', 'hbs!./templates/main_layout', './main_header_view', './main_content_view', './main_footer_view', './../controller'],
function (Marionette, layoutTemplate, MainHeaderView, MainContentView, MainFooterView, controller) {
'use strict';
var SlideAnimationDuration = 200, MainLayoutView = Marionette.Layout.extend({
var SlideAnimationDuration = 0, MainLayoutView = Marionette.Layout.extend({
template: layoutTemplate,
regions: {
header: '#header',
Expand Down
2 changes: 1 addition & 1 deletion js/todo-list/views/templates/footer.hbs
@@ -1,3 +1,3 @@
<p>Double-click to edit a todo</p>
<p>Created by <a href="https://github.com/BorisKozo/myTodoMvc">Boris Kozorovitzky</a></p>
<p>A project for the HP JS cookbook by Boris Kozorovitzky and Elad Moshe</p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>

8 comments on commit 623b07c

@simkimsia
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Boris, I am still reading your book and trying to integrate your architecture here into my own app in order to learn your architectural philosophy.

I am influenced by the writings of Addy Osmani over here http://addyosmani.com/largescalejavascript/#mediatorpattern

I have bought two of his books Developing Backbone.js applications and Javascript patterns.

I am enamored by the mediator pattern, modules pattern and facade pattern he described in his largescalejavascript article.

I discovered his AuraJS is still facing a rewrite while I need something I can use right now, I searched for a long time till I found your book here, which is excellent.

After the past couple of days, I have come to realize that you are using controller in each mini app as a mediator within the mini-app. Correct me if I am wrong about this.

My question is:

do you then have an overall mediator for the various mini-apps to publish and subscribe events?

I have read your book.md several times and I have checked the history of this file. I cannot find an answer to this question. Please enlighten me.

Thank you!

@BorisKozo
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Thank you for your positive feedback on our book.
You are right, the controller is the mediator within the mini-app.
And you are right that there should be a mediator between the mini-apps.
I believe you should use the App object as the mediator (or create another object and assign it to the App) instantiate this.
As you can see the App object has no dependencies. This is an intentional design decision to allow it to become a mediator between mini-apps in the future.

We know about AuraJS but we didn't use it for this small project to keep things simple.

@simkimsia
Copy link

@simkimsia simkimsia commented on 623b07c Mar 6, 2013 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BorisKozo
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

We are not planning to do any changes to this project or the book. It was sort of "funded" by HP but now canceled.
If you want to, you can submit a pull request and I will review it.

Thanks,
Boris.

@simkimsia
Copy link

@simkimsia simkimsia commented on 623b07c Mar 6, 2013 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BorisKozo
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Addy is one of the pillars of the community and I think everyone knows him (he might know me from one of the pull requests, probably not).
You can talk with him directly via twitter @addyosmani , I am sure he will answer.
Also Derick (the creator of MarionetteJs) is one of the most awesome developers I know. He will surly answer you on twitter @derickbailey .

As I mentioned in one of my previous comments, you can get live assistance with anything you need via IRC (freenode) chat. The relevant rooms are #documentcloud for backbone and #marionette for Marionette.js

@addyosmani
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey guys. I just wanted to chime in to say that I'm happy to respond to any questions as time allows. It's usually easier over twitter these days but I'm humbled there's been continued interest in large-scale JS patterns. Both Marionette and Aura build upon concepts like the mediator pattern and I'm sure Derick would similarly be up for responding to questions depending on availability.

@BorisKozo
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addyosmani - Oops, I forgot you would get a notification on that mention. :)

Please sign in to comment.