layout | title | module_path |
---|---|---|
default |
Chaplin.Composer |
src/chaplin/composer.coffee |
Grants the ability for views (and related data) to be persisted beyond one controller action.
If a view is composed in a controller action method it will be instantiated and rendered if the view has not been composed in the current or previous action methods.
If a view was composed in the previous action method and is not composed in the current action method, it will be disposed and removed from the DOM.
A common use case is a login page. This login page is a simple centered form. However, the main application needs both header and footer controllers.
Here is this use case demonstrated in coffeescript pseudocode:
# routes.coffee
(match) ->
match 'login', 'login#show'
match '', 'index#show'
match 'about', 'about#show'
# controllers/login_controller.coffee
Login = require 'views/login'
class LoginController extends Chaplin.Controller
show: ->
# Simple view, just want to show the login screen
@view = new Login()
# controllers/site_controller.coffee
Site = require 'views/site'
Header = require 'views/header'
Footer = require 'views/footer'
class SiteController extends Chaplin.Controller
beforeAction: ->
# Compose the Site view, which is a simple 3-row stacked layout that
# provides the header, footer, and body regions
@compose 'site', Site
# Compose the Header view, which binds itself to whatever container
# is exposed in Site under the header region
@compose 'header', Header, region: 'header'
# Likewise for the footer region
@compose 'footer', Footer, region: 'footer'
# controllers/index_controller.coffee
Index = require 'views/index'
SiteController = require 'controllers/site_controller'
class IndexController extends SiteController
show: ->
# Instantiate this simple index view at the body region
@view = new Index region: 'body'
# controllers/about_me_controller.coffee
AboutMe = require 'views/aboutme'
SiteController = require 'controllers/site_controller'
class AboutMeController extends SiteController
show: ->
# Instantiate this simple about me view at the body region
@view = new AboutMe region: 'body'
// routes.js
function(match) {
match('login', 'login#show');
match('', 'index#show');
match('about', 'about#show');
}
// controllers/login_controller.js
var Login = require('views/login');
var LoginController = Chaplin.Controller.extend({
show: function() {
// Simple view, just want to show the login screen.
this.view = new Login();
}
});
// controllers/site_controller.js
var Site = require('views/site');
var Header = require('views/header');
var Footer = require('views/footer');
var SiteController = Chaplin.Controller.extend({
beforeAction: function() {
// Compose the Site view, which is a simple 3-row stacked layout that
// provides the header, footer, and body regions
this.compose('site', Site);
// Compose the Header view, which binds itself to whatever container
// is exposed in Site under the header region
this.compose('header', Header, {region: 'header'});
// Likewise for the footer region
this.compose('footer', Footer, {region: 'footer'});
}
});
// controllers/index_controller.js
var Index = require('views/index');
var SiteController = require('controllers/site_controller');
var IndexController = SiteController.extend({
show: function() {
// Instantiate this simple index view at the body region.
this.view = new Index({region: 'body'});
}
});
// controllers/about_me_controller.js
var AboutMe = require('views/aboutme');
var SiteController = require('controllers/site_controller');
var AboutMeController = SiteController.extend({
show: function() {
// Instantiate this simple about me view at the body region.
this.view = new AboutMe({region: 'body'});
}
});
Given the controllers above here is what would happen each time the URL is routed:
route('login')
# 'views/login' is initialized and rendered
route('')
# 'views/site' is initialized and rendered
# 'views/header' is initialized and rendered
# 'views/footer' is initialized and rendered
# 'views/index' is initialized and rendered
# 'views/login' is disposed
route('about')
# 'views/aboutme' is initialized and rendered
# 'views/index' is disposed
route('login')
# 'views/login' is initialized and rendered
# 'views/index' is disposed
# 'views/footer' is disposed
# 'views/header' is disposed
# 'views/site' is disposed
By default, when a controller requests a view to be composed, the composer checks if the view instance exists and the options hash is equal. If that is true the view is destroyed and composed.
By default, the compose method only allows for composing views.
The following example shows another way to use the compose method to allow for just about anything. The check method should return true when it wishes the composition to be disposed and the compose method to be called. The composer will track and ensure proper disposal of whatever is returned from the compose method (be it a view or an object with properties that have dispose methods).
@compose 'something-strange',
compose: ->
@model = new Model {id: 42}
@view = new View {@model}
@model.fetch()
check: -> @model.id is 42
this.compose('something-strange', {
compose: function() {
this.model = new Model({id: 42});
this.view = new View({model: this.model});
this.model.fetch();
}
check: function() {return this.model.id === 42;}
});