From 623b07c02f4657ec6fd9ec58864db43e149df935 Mon Sep 17 00:00:00 2001 From: Boris Date: Mon, 4 Feb 2013 10:26:14 +0200 Subject: [PATCH] Refactored the controller loading and wrote the book chapter about it --- book.md | 63 +++++++++++++++++++++++++ js/loader.js | 10 ++-- js/todo-list/controller.js | 9 ++-- js/todo-list/views/main_layout_view.js | 2 +- js/todo-list/views/templates/footer.hbs | 2 +- 5 files changed, 76 insertions(+), 10 deletions(-) diff --git a/book.md b/book.md index 689c095..17070ed 100644 --- a/book.md +++ b/book.md @@ -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) { +``` diff --git a/js/loader.js b/js/loader.js index 39e59ca..7b450f8 100644 --- a/js/loader.js +++ b/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; diff --git a/js/todo-list/controller.js b/js/todo-list/controller.js index e6d4db1..94273b4 100644 --- a/js/todo-list/controller.js +++ b/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), @@ -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(); } }); diff --git a/js/todo-list/views/main_layout_view.js b/js/todo-list/views/main_layout_view.js index a5d9642..f31183f 100644 --- a/js/todo-list/views/main_layout_view.js +++ b/js/todo-list/views/main_layout_view.js @@ -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', diff --git a/js/todo-list/views/templates/footer.hbs b/js/todo-list/views/templates/footer.hbs index 0f4fe7f..2d8bccb 100644 --- a/js/todo-list/views/templates/footer.hbs +++ b/js/todo-list/views/templates/footer.hbs @@ -1,3 +1,3 @@ 

Double-click to edit a todo

-

Created by Boris Kozorovitzky

+

A project for the HP JS cookbook by Boris Kozorovitzky and Elad Moshe

Part of TodoMVC

\ No newline at end of file