ViewJS


Introduction

Write applications using the latest HTML5 and JavaScript technologies, serving them as static HTML to legacy browsers.

Installation

  1. mkdir project_name
  2. cd project_name
  3. npm install view
  4. node_modules/view/bin/bootstrap
  5. node app.js
  1. mkdir(project_name);
  2. cd(project_name);
  3. npm(install(view));
  4. node_modules / view / bin / bootstrap;
  5. node(app.js);

Development

Run this command from your project:

  1. cake watch
  1. cake(watch);

This will compile the following stylus and coffeescript files:

Note that you need to restart the cake watch process when you create new files, this will be automatic in the future.

Client

View

The View class represents a DOM Element, a set of behaviors associated with it (event handlers, custom initializers, custom methods, etc) and possibly a Model or Collection that is bound to the view. All views are mixins and can be extended and sub classed at any time.

View view_name

Find a view by name:

  1. ContactView = View 'ContactView'
  1. var ContactView;
  2. ContactView = View('ContactView');

View view_name: ->

Passing in name: callback pairs will call the callback with the view as it's context when the view is available:

  1. View
  2. ContactView: ->
  3. @initialize()
  1. View({
  2. ContactView: function() {
  3. return this.initialize();
  4. }
  5. });

@create view_name: mixins

Create a named view with the given mixins. You can pass a single mixin object, or an array of mixins. Nested arrays will be flattened.

  1. View.create ParentView:
  2. render: 'parent.eco'
  3. on:
  4. render: ->
  5. console.log 'render event triggered'
  6. View.create BuilderView: [Builder,
  7. render: ->
  8. @div()
  9. ]
  1. View.create({
  2. ParentView: {
  3. render: 'parent.eco',
  4. on: {
  5. render: function() {
  6. return console.log('render event triggered');
  7. }
  8. }
  9. }
  10. });
  11. View.create({
  12. BuilderView: [
  13. Builder, {
  14. render: function() {
  15. return this.div();
  16. }
  17. }
  18. ]
  19. });

Calling create on a named view creates a child view that will process all of the mixins of the parent view (effectively cloning it) in addition to the mixins passed into create:

  1. {ChildView} = ParentView.create ChildView:
  2. render: 'child.eco'
  3. on:
  4. render: ->
  5. console.log 'render event triggered'
  6. #render now has two event handlers
  1. var ChildView;
  2. ChildView = ParentView.create({
  3. ChildView: {
  4. render: 'child.eco',
  5. on: {
  6. render: function() {
  7. return console.log('render event triggered');
  8. }
  9. }
  10. }
  11. }).ChildView;

Some mixin directives like render will overwrite an existing callback, while others like on will bind additional events without unregistering old events.

Calling create on a named view with no arguments creates an unnamed clone of the view.

  1. anonymous_view = ChildView.create()
  1. var anonymous_view;
  2. anonymous_view = ChildView.create();

@extend mixin

All mixins passed to create() are then passed to extend. @extend looks for directives to process the attributes in the mixin (render,initialize,on, etc are all directives). If none are found the attribute becomes an attribute of the view.

  1. {PostView} = View.create PostView:
  2. render: 'post.eco' #processed with render directive
  3. key: 'value'
  4. PostView.key is 'value'
  1. var PostView;
  2. PostView = View.create({
  3. PostView: {
  4. render: 'post.eco',
  5. key: 'value'
  6. }
  7. }).PostView;
  8. PostView.key === 'value';

@extend extend: directive_name: (directive_value) ->

Extend itself can be extended to process new directives. If you view the ViewJS source code you will see this is how ViewJS itself is constructed. The "on" directive is implemented roughly like this:

  1. View.extend extend:on: (events) ->
  2. for event_name, callback of events
  3. @bind event_name, callback
  4. #now "on" can be passed to extend
  5. View.extend
  6. on:
  7. render: ->
  8. console.log 'Called when any view renders'
  1. View.extend({
  2. extend: {
  3. on: function(events) {
  4. var callback, event_name, _results;
  5. _results = [];
  6. for (event_name in events) {
  7. callback = events[event_name];
  8. _results.push(this.bind(event_name, callback));
  9. }
  10. return _results;
  11. }
  12. }
  13. });
  14. View.extend({
  15. on: {
  16. render: function() {
  17. return console.log('Called when any view renders');
  18. }
  19. }
  20. });

A discard callback is always passed as the second argument to a directive processor. Call this when a directive should only be processed once and not when child views are created.

  1. View.extend extend:routes: (routes,discard) ->
  2. #process routes once then
  3. discard()
  1. View.extend({
  2. extend: {
  3. routes: function(routes, discard) {
  4. return discard();
  5. }
  6. }
  7. });

@extend initialize: (next) ->

Add an asynchronous initialize callback to the view. The callback must call next or the view will never finish initializing. Note that initialize callbacks are added to a stack of existing callbacks. Child views specifying an initialize callback will not overwrite the parent's callback.

  1. {PostCollectionView} = View.create PostCollectionView:
  2. render: 'post_preview.eco'
  3. collection: PostCollection
  4. initialize: (next) ->
  5. @collection.fetch success: next
  1. var PostCollectionView;
  2. PostCollectionView = View.create({
  3. PostCollectionView: {
  4. render: 'post_preview.eco',
  5. collection: PostCollection,
  6. initialize: function(next) {
  7. return this.collection.fetch({
  8. success: next
  9. });
  10. }
  11. }
  12. }).PostCollectionView;

@initialize: attributes = {}, ->

Initializes a given view. Optional attributes will be passed to set() and an optional callback will be called when initialize is complete.

  1. $('body').append PostCollectionView
  2. PostCollectionView.initialize ->
  3. console.log 'called when initialize is complete'
  1. $('body').append(PostCollectionView);
  2. PostCollectionView.initialize(function() {
  3. return console.log('called when initialize is complete');
  4. });

@extend views: [views...]

Specifies dependent views that will be loaded. Each dependent view add an initialize callback. The parent view will not finish initializing until the dependent views have finished initializing. Once finished the dependent view names will become available as properties of the parent view:

  1. {ApplicationView} = View.create ApplicationView: [Builder,
  2. views: ['PostCollectionView','SideBarView']
  3. render: ->
  4. @div(
  5. @div class: 'main', @PostCollectionView
  6. @div class: 'sidebar', @SideBarView
  7. )
  8. ]
  1. var ApplicationView;
  2. ApplicationView = View.create({
  3. ApplicationView: [
  4. Builder, {
  5. views: ['PostCollectionView', 'SideBarView'],
  6. render: function() {
  7. return this.div(this.div({
  8. "class": 'main',
  9. PostCollectionView: this.PostCollectionView
  10. }), this.div({
  11. "class": 'sidebar',
  12. SideBarView: this.SideBarView
  13. }));
  14. }
  15. }
  16. ]
  17. }).ApplicationView;

Data

@extend model: Backbone.Model

Specify a model for the view. Designed with Backbone.Model in mind, but can be any object that has an attributes property. The attributes property of the model will be passed as the context to a template if one is specified for the view. If using a callback with render the model will be accessed as @model.

  1. post = new Backbone.Model title: 'Post Title'
  2. {PostView} = View.create PostView:
  3. render: 'post.eco'
  4. model: post
  5. # post.eco
  6. # <h2><%= @title %></h2>
  1. var PostView, post;
  2. post = new Backbone.Model({
  3. title: 'Post Title'
  4. });
  5. PostView = View.create({
  6. PostView: {
  7. render: 'post.eco',
  8. model: post
  9. }
  10. }).PostView;

@extend collection: Backbone.Collection

render() will be called for each model in the collection, with the model as the context to a template, or as the first argument to a callback. Asynchronous collection logic must be put into an initialize callback or a change or change:key event.

  1. {PostCollectionView} = View.create PostCollectionView:
  2. render: 'post_preview.eco'
  3. collection: PostCollection
  4. initialize: (next) ->
  5. @collection.fetch success: next
  1. var PostCollectionView;
  2. PostCollectionView = View.create({
  3. PostCollectionView: {
  4. render: 'post_preview.eco',
  5. collection: PostCollection,
  6. initialize: function(next) {
  7. return this.collection.fetch({
  8. success: next
  9. });
  10. }
  11. }
  12. }).PostCollectionView;

collection is designed for use with a Backbone.Collection, but any class conforming to the following API will work:

@get key

  1. title = PostView.model.get 'title'
  1. var title;
  2. title = PostView.model.get('title');

@set attributes = {}, options = silent: false

Set attributes on a view. By default this will trigger change and change:key events. Pass "{silent:true}" as the second argument to prevent those events from being triggered. Note that these attributes are not persisted to a view's model if present.

  1. View PostView: ->
  2. @on change: -> @render()
  3. @set title: 'Title One' #render() called
  4. @set {title: 'Title Two'}, {silent: true} #render() not called
  1. View({
  2. PostView: function() {
  3. this.on({
  4. change: function() {
  5. return this.render();
  6. }
  7. });
  8. this.set({
  9. title: 'Title One'
  10. });
  11. return this.set({
  12. title: 'Title Two'
  13. }, {
  14. silent: true
  15. });
  16. }
  17. });

@name

The name of the view.

DOM

@extend $: Library

Set the DOM library to use (currently jQuery or Zepto are the only supported libraries). Must be set on the base View object. A DOM library is not required to use ViewJS.

  1. View.extend
  2. $: jQuery
  1. View.extend({
  2. $: jQuery
  3. });

@$ selector

When using a DOM library $ is available as a hybrid object and function. The function will act as the selector, and the object will contain all of the attributes of an Element wrapped by the DOM library (hide/show/addClass/etc). Note that the element object and $ method are always available, but the view will not contain any content until initialize has finished.

  1. PostView.$.hide() #hide while loading
  2. PostView.initialize ->
  3. @$.show()
  4. @$('li').addClass 'item'
  1. PostView.$.hide();
  2. PostView.initialize(function() {
  3. this.$.show();
  4. return this.$('li').addClass('item');
  5. });

@tag tag_name, attributes = {}, elements = [], content = '', ->

Generate a DOM Element. Accepts a variable number of hashes of attributes, elements, other views, strings of content or a functions to call, or arrays (which can be nested) of any of the above, in any order. If Builder is passed as a mixin all valid tag names become available as methods.

  1. {TableView} = View.create TableView: [Builder,
  2. render: ->
  3. @table cellpadding: 0, cellspacing: 0,
  4. @tbody(
  5. @tr(
  6. @td 'Cell One'
  7. @td 'Cell Two'
  8. )
  9. )
  10. ]
  11. #without Builder
  12. {ListView} = View.create ListView:
  13. render: ->
  14. @tag('ul',@tag('li','Item One'))
  1. var ListView, TableView;
  2. TableView = View.create({
  3. TableView: [
  4. Builder, {
  5. render: function() {
  6. return this.table({
  7. cellpadding: 0,
  8. cellspacing: 0
  9. }, this.tbody(this.tr(this.td('Cell One'), this.td('Cell Two'))));
  10. }
  11. }
  12. ]
  13. }).TableView;
  14. ListView = View.create({
  15. ListView: {
  16. render: function() {
  17. return this.tag('ul', this.tag('li', 'Item One'));
  18. }
  19. }
  20. }).ListView;

If a DOM library is present @tag and Builder methods will instead return a wrapped object containing the element (i.e. a jQuery or Zepto array). View will always look inside these objects for the actual elements so in practice you can use them as if they were the Element objects, with the added benefit of attaching event handlers inline, etc:

  1. @ul @li @a('Link',href:'#').click ->
  1. this.ul(this.li(this.a('Link', {
  2. href: '#'
  3. }).click(function() {})));

@extend render: template

Specify a template to render:

  1. View.create PostView:
  2. model: post
  3. render: 'post.eco'
  1. View.create({
  2. PostView: {
  3. model: post,
  4. render: 'post.eco'
  5. }
  6. });

@extend render: ->

or pass a callback. Callbacks work best with Builder, but . Asynchronous logic should be put inside of initialize or a change:key event handler.

  1. View.create PostView: [Builder,
  2. model: post
  3. render: ->
  4. @div class: 'post',
  5. @a('My Link',href: '#').click -> false
  6. @h2 @model.get 'title'
  7. ]
  1. View.create({
  2. PostView: [
  3. Builder, {
  4. model: post,
  5. render: function() {
  6. return this.div({
  7. "class": 'post'
  8. }, this.a('My Link', {
  9. href: '#'
  10. }).click(function() {
  11. return false;
  12. }), this.h2(this.model.get('title')));
  13. }
  14. }
  15. ]
  16. });

@render()

render() is automatically called when initialize() has completed. Subsequent calls to render() will replace the contents of the view's element.

  1. View.create PostView:
  2. render: 'post.eco'
  3. on:
  4. change:
  5. id: (id) ->
  6. Post.find id, (post) =>
  7. @model = post
  8. @render()
  1. var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  2. View.create({
  3. PostView: {
  4. render: 'post.eco',
  5. on: {
  6. change: {
  7. id: function(id) {
  8. return Post.find(id, __bind(function(post) {
  9. this.model = post;
  10. return this.render();
  11. }, this));
  12. }
  13. }
  14. }
  15. }
  16. });

@extend helpers: template_helpers

Adds helper methods to templates:

  1. View.extend helpers:
  2. url: (params) ->
  3. @url params
  1. View.extend({
  2. helpers: {
  3. url: function(params) {
  4. return this.url(params);
  5. }
  6. }
  7. });

@element

The outer most DOM Element in the view which is available as soon as the view is created (before initialize or render are called). The Element is never removed even when the view is re-rendered.

  1. $('body').append PostView
  2. PostView.$.html() #empty
  3. PostView.initialize ->
  4. @$.html() #contents of post.eco
  1. $('body').append(PostView);
  2. PostView.$.html();
  3. PostView.initialize(function() {
  4. return this.$.html();
  5. });

@extend element: -> Element

Override the default element which is a div with a className containing the name attribute of the view.

  1. {PostCollectionView} = View.create
  2. PostCollectionView: [Builder,
  3. collection: PostCollection
  4. element: ->
  5. @ul()
  6. render: (post) ->
  7. @li post.get 'title'
  8. ]
  1. var PostCollectionView;
  2. PostCollectionView = View.create({
  3. PostCollectionView: [
  4. Builder, {
  5. collection: PostCollection,
  6. element: function() {
  7. return this.ul();
  8. },
  9. render: function(post) {
  10. return this.li(post.get('title'));
  11. }
  12. }
  13. ]
  14. }).PostCollectionView;

@extend delegate: events

  1. View.create PostView:
  2. delegate:
  3. 'click a.class': (event) ->
  1. View.create({
  2. PostView: {
  3. delegate: {
  4. 'click a.class': function(event) {}
  5. }
  6. }
  7. });

@extend templates: template_name: ->

ViewServer will automatically compile the contents of the templates directory and set this property accordingly, but if using View standalone this property should contain filename: callback pairs. The callbacks must return HTML strings and not DOM Elements. The templates attribute must be set on the base View object.

  1. View.extend
  2. templates:
  3. 'post.eco': (attributes) ->
  4. "html output"
  1. View.extend({
  2. templates: {
  3. 'post.eco': function(attributes) {
  4. return "html output";
  5. }
  6. }
  7. });

Events

The following events are triggered internally by View:

@bind event_name, callback

@on event_name, callback

Bind a callback to an event. bind and on are aliases.

  1. PostView.bind 'render', ->
  2. console.log 'Post rendered'
  1. PostView.bind('render', function() {
  2. return console.log('Post rendered');
  3. });

An object containing event name, callback pairs can also be used:

  1. PostView.on
  2. render: ->
  3. console.log 'Post rendered'
  4. initialize: ->
  5. console.log 'Post initialized'
  1. PostView.on({
  2. render: function() {
  3. return console.log('Post rendered');
  4. },
  5. initialize: function() {
  6. return console.log('Post initialized');
  7. }
  8. });

@extend on: events

The same behavior can be achieved using a mixin:

  1. PostView.extend
  2. on:
  3. render: ->
  4. console.log 'Post rendered'
  1. PostView.extend({
  2. on: {
  3. render: function() {
  4. return console.log('Post rendered');
  5. }
  6. }
  7. });

@unbind/removeListener event_name = false, handler = ->

  1. PostView.unbind 'render', handler #unbinds a particular handler
  2. PostView.unbind 'render' #unbinds all render handlers
  3. PostView.unbind() #unbinds all
  1. PostView.unbind('render', handler);
  2. PostView.unbind('render');
  3. PostView.unbind();

Unbinding all is not recommended as there are many internal events.

@trigger/emit event_name, args...

Trigger a given event with an arbitrary number of arguments.

  1. PostView.bind 'custom', (arg1,arg2) ->
  2. PostView.trigger 'custom', arg1, arg2
  1. PostView.bind('custom', function(arg1, arg2) {});
  2. PostView.trigger('custom', arg1, arg2);

@before: method_name: (original_method,args...) ->

A simple implementation of AOP. A logger could be implemented as:

  1. View.extend log: (method_name) ->
  2. @before method_name, (next,args...) ->
  3. response = next.apply @, args
  4. console.log "#{@name}.#{method_name}", args, ' -> ', response
  5. response
  6. View.log 'set'
  7. View.log 'get'
  8. instance = View.create()
  9. instance.set key: 'value'
  1. var instance;
  2. var __slice = Array.prototype.slice;
  3. View.extend({
  4. log: function(method_name) {
  5. return this.before(method_name, function() {
  6. var args, next, response;
  7. next = arguments[0], args = 2

Env

Allows for the conditional execution of code depending on environment. The following environments are built in:

@env: name: ->

  1. View.env
  2. server: -> console.log 'only run on the server'
  3. browser: -> console.log 'only run on a browser'
  1. View.env({
  2. server: function() {
  3. return console.log('only run on the server');
  4. },
  5. browser: function() {
  6. return console.log('only run on a browser');
  7. }
  8. });

@env: set: name: ->

New environment names can be set using the set key:

  1. View.env set:
  2. ie: -> !!window.attachEvent and not window.opera
  1. View.env({
  2. set: {
  3. ie: function() {
  4. return !!window.attachEvent && !window.opera;
  5. }
  6. }
  7. });

@extend env: name: ->

The env directive can be used in a mixin.

  1. View.extend
  2. env:
  3. ie: ->
  4. @on render: -> console.log 'render called in IE'
  1. View.extend({
  2. env: {
  3. ie: function() {
  4. return this.on({
  5. render: function() {
  6. return console.log('render called in IE');
  7. }
  8. });
  9. }
  10. }
  11. });

@extend env: name: mixin

The env directive in a mixin, can itself contain a mixin that will be passed to extend.

  1. View.extend
  2. env:
  3. ie:
  4. on:
  5. render: -> console.log 'render called in IE'
  1. View.extend({
  2. env: {
  3. ie: {
  4. on: {
  5. render: function() {
  6. return console.log('render called in IE');
  7. }
  8. }
  9. }
  10. }
  11. });

Routes

@extend routes: path: view_name

This is automatically set by the ViewServer, but when using View standalone you must manually set this before you can use the Router mixin. Attributes matched in a route will be @set on the view when it is activated.

  1. View.extend
  2. routes:
  3. '/about/': 'AboutPageView'
  4. '/contact/': 'ContactPageView'
  5. '/blog/:post_id': 'PostView'
  1. View.extend({
  2. routes: {
  3. '/about/': 'AboutPageView',
  4. '/contact/': 'ContactPageView',
  5. '/blog/:post_id': 'PostView'
  6. }
  7. });

@url view_name: attributes

Generates a URL for a given view and attributes. The view must have a corresponding route.

  1. '/post/5' is View.url PostView: id: 5
  2. '/post/5' is PostView.url id: 5
  3. '/' is View.url IndexView: {}
  4. '/' is IndexView.url()
  1. '/post/5' === View.url({
  2. PostView: {
  3. id: 5
  4. }
  5. });
  6. '/post/5' === PostView.url({
  7. id: 5
  8. });
  9. '/' === View.url({
  10. IndexView: {}
  11. });
  12. '/' === IndexView.url();

Mixins

Router

The Router uses the excellent History.js to manage the history state in the browser. The recommended behavior is to serve browsers incapable of supporting the HTML5 history API a static HTML snapshot of the view. The ViewServer provides the legacy and html5 directives to @env to easily allow this.

When a view is activated by a route the deactivated event is triggered on the previous activated view, and the activated event is triggered on the newly active view. Client side the deactivated view is hidden and the activated view is shown. Server side, all views but the activated one will be removed from the DOM before it is serialized.

Each application that uses Routes must mixin the Router once. The Router mixin makes available @Router which will contain a div, which itself contains all of the elements of the routed views.

  1. View.extend
  2. routes:
  3. '/about/': 'AboutPageView'
  4. '/contact/': 'ContactPageView'
  5. '/blog/:post_id': 'PostView'
  6. View.create
  7. ApplicationView: [Router,
  8. render: ->
  9. @div @Router
  10. ]
  11. AboutPageView:
  12. render: 'about.eco'
  13. ContactPageView:
  14. render: 'contact.eco'
  15. PostView:
  16. render: 'post.eco'
  17. on:
  18. change:
  19. post_id: (post_id) ->
  20. Post.find post_id, (post) =>
  21. @model = post
  22. @render()
  1. var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  2. View.extend({
  3. routes: {
  4. '/about/': 'AboutPageView',
  5. '/contact/': 'ContactPageView',
  6. '/blog/:post_id': 'PostView'
  7. }
  8. });
  9. View.create({
  10. ApplicationView: [
  11. Router, {
  12. render: function() {
  13. return this.div(this.Router);
  14. }
  15. }
  16. ],
  17. AboutPageView: {
  18. render: 'about.eco'
  19. },
  20. ContactPageView: {
  21. render: 'contact.eco'
  22. },
  23. PostView: {
  24. render: 'post.eco',
  25. on: {
  26. change: {
  27. post_id: function(post_id) {
  28. return Post.find(post_id, __bind(function(post) {
  29. this.model = post;
  30. return this.render();
  31. }, this));
  32. }
  33. }
  34. }
  35. }
  36. });

Builder

The builder mixin adds all valid HTML5 tag names as methods to a view. Usage of Builder is covered in the tag method.

  1. {TableView} = View.create TableView: [Builder,
  2. render: ->
  3. @table cellpadding: 0, cellspacing: 0,
  4. @tbody(
  5. @tr(
  6. @td 'Cell One'
  7. @td 'Cell Two'
  8. )
  9. )
  10. ]
  1. var TableView;
  2. TableView = View.create({
  3. TableView: [
  4. Builder, {
  5. render: function() {
  6. return this.table({
  7. cellpadding: 0,
  8. cellspacing: 0
  9. }, this.tbody(this.tr(this.td('Cell One'), this.td('Cell Two'))));
  10. }
  11. }
  12. ]
  13. }).TableView;

Server

Extends an Express server to serve the assets of a View based application, or execute the View application server side, serving the resulting DOM as static HTML.

ViewServer

ViewServer view_name

Find a ViewServer by name:

  1. BlogServer = View 'BlogServer'
  1. var BlogServer;
  2. BlogServer = View('BlogServer');

ViewServer server_name: ->

Passing in name: callback pairs will call the callback with the ViewServer as it's context when the ViewServer is available:

  1. ViewServer BlogServer: ->
  2. @server.use express.logger()
  1. ViewServer({
  2. BlogServer: function() {
  3. return this.server.use(express.logger());
  4. }
  5. });

@create server_name: mixins

The ViewServer's mixin system and create method works identically to View's.

  1. ViewServer.create BlogServer: ->
  2. port: 3001
  3. public: __dirname + '/public'
  1. ViewServer.create({
  2. BlogServer: function() {
  3. return {
  4. port: 3001,
  5. public: __dirname + '/public'
  6. };
  7. }
  8. });

Server

@server

The Express instance which handles requests for the ViewServer.

  1. express = require 'express'
  2. BlogServer.server.use express.logger()
  3. BlogServer.server.get '/custom/', (request,response) ->
  1. var express;
  2. express = require('express');
  3. BlogServer.server.use(express.logger());
  4. BlogServer.server.get('/custom/', function(request, response) {});

@extend server: express_instance

If no server attribute is supplied, an Express server will automatically be created. Or you can supply a custom one:

  1. express = require 'express'
  2. server = express.createServer()
  3. {BlogServer} = ViewServer.create
  4. server: server
  1. var BlogServer, express, server;
  2. express = require('express');
  3. server = express.createServer();
  4. BlogServer = ViewServer.create({
  5. server: server
  6. }).BlogServer;

@extend port: number

The port number to listen on. When both port and public have been defined in the server @server.listen() will be called.

@extend public: path

Full path to the public directory. When both port and public have been defined in the server @server.listen() will be called.

@extend templates: path

Full path to the templates directory. All eco and Jade templates in this directory will automatically be compiled to public/javascripts/templates.js when running the cake watch command.

Assets

@extend javascripts: [javascripts...]

JavaScript tags to send to the browser on each request. If a directory is specified it will be recursively searched for .js files.

  1. BlogServer.extend javascripts: [
  2. "public/javascripts/lib/view.js"
  3. "public/javascripts/models/"
  4. "public/javascripts/views/"
  5. ]
  1. BlogServer.extend({
  2. javascripts: ["public/javascripts/lib/view.js", "public/javascripts/models/", "public/javascripts/views/"]
  3. });

@extend execute: [execute...]

JavaScript to execute server side on each request. If a directory is specified it will be recursively searched for .js files.

  1. BlogServer.extend execute: [
  2. "public/javascripts/lib/view.js"
  3. "public/javascripts/models/"
  4. "public/javascripts/views/"
  5. ]
  1. BlogServer.extend({
  2. execute: ["public/javascripts/lib/view.js", "public/javascripts/models/", "public/javascripts/views/"]
  3. });

@extend stylesheets: [stylesheets...]

Add stylesheets to. If a directory is specified it will be recursively searched for .css files.

  1. BlogServer.extend stylesheets: [
  2. "public/stylesheets/a.css"
  3. "public/stylesheets/b.css"
  4. #now scan the directory for all remaining stylesheets
  5. "public/stylesheets/"
  6. ]
  1. BlogServer.extend({
  2. stylesheets: ["public/stylesheets/a.css", "public/stylesheets/b.css", "public/stylesheets/"]
  3. });

@extend meta: [meta...]

Inject arbitrary meta information into the document head.

  1. BlogServer.extend meta: [
  2. '<link rel="alternate" type="application/rss+xml" title="Feed Name" href="url" />'
  3. ]
  1. BlogServer.extend({
  2. meta: ['']
  3. });

@extend bundle: assets

TODO

Env

Execute code, or process mixin directives conditionally per request. The following environments are pre-defined:

@env: request, name: ->

  1. View.env request,
  2. html5: ->
  3. console.log 'only run when an HTML5 capable user agent initiated the request'
  4. legacy: (request) ->
  5. console.log 'only run when a non HTML5 capable user agent initiated the request'
  1. View.env(request, {
  2. html5: function() {
  3. return console.log('only run when an HTML5 capable user agent initiated the request');
  4. },
  5. legacy: function(request) {
  6. return console.log('only run when a non HTML5 capable user agent initiated the request');
  7. }
  8. });

@extend: env: name: mixin

The following mixin directives can be used in an env directive:

In the following example the Typekit JavaScript would be available in a script tag in the header for both legacy and HTML5 capable browsers, but the rest of the application would be run server side for legacy browsers, and client side for HTML5 browsers.

  1. application_payload = [
  2. "public/javascripts/lib/jquery.js"
  3. "public/javascripts/lib/underscore.js"
  4. "public/javascripts/lib/backbone.js"
  5. "public/javascripts/lib/view.js"
  6. "public/javascripts/templates.js"
  7. "public/javascripts/models"
  8. "public/javascripts/views"
  9. ]
  10. {MyServer} = ViewServer.create MyServer:
  11. public: __dirname + '/public'
  12. javascripts: [
  13. "http://use.typekit.com/xxx.js"
  14. ]
  15. env:
  16. legacy:
  17. execute: application_payload
  18. html5:
  19. javascripts: application_payload
  1. var MyServer, application_payload;
  2. application_payload = ["public/javascripts/lib/jquery.js", "public/javascripts/lib/underscore.js", "public/javascripts/lib/backbone.js", "public/javascripts/lib/view.js", "public/javascripts/templates.js", "public/javascripts/models", "public/javascripts/views"];
  3. MyServer = ViewServer.create({
  4. MyServer: {
  5. public: __dirname + '/public',
  6. javascripts: ["http://use.typekit.com/xxx.js"],
  7. env: {
  8. legacy: {
  9. execute: application_payload
  10. },
  11. html5: {
  12. javascripts: application_payload
  13. }
  14. }
  15. }
  16. }).MyServer;

@env: set: name: (request) ->

Define a new environment. Request may not be present, so always check for request? first.

  1. ViewServer.env set:
  2. ios: (request) ->
  3. request? and request.headers['user-agent'].match /(iPhone|iPad)/
  1. ViewServer.env({
  2. set: {
  3. ios: function(request) {
  4. return (request != null) && request.headers['user-agent'].match(/(iPhone|iPad)/);
  5. }
  6. }
  7. });

Not all environments depend on the request object. The following example sets development and production environments based on wether or not the code is run in the VMWare Cloud Foundry offering:

  1. ViewServer.env set:
  2. production: ->
  3. process?.env?.VCAP_APPLICATION
  4. development: ->
  5. not process?.env?.VCAP_APPLICATION
  1. ViewServer.env({
  2. set: {
  3. production: function() {
  4. var _ref;
  5. return typeof process != "undefined" && process !== null ? (_ref = process.env) != null ? _ref.VCAP_APPLICATION : void 0 : void 0;
  6. },
  7. development: function() {
  8. var _ref;
  9. return !(typeof process != "undefined" && process !== null ? (_ref = process.env) != null ? _ref.VCAP_APPLICATION : void 0 : void 0);
  10. }
  11. }
  12. });

Events

The following events are triggered internally by ViewServer:

@bind event_name, callback

@on event_name, callback

Bind a callback to an event. bind and on are aliases.

  1. BlogServer.bind 'request', (request,response) ->
  2. console.log 'Incoming request'
  1. BlogServer.bind('request', function(request, response) {
  2. return console.log('Incoming request');
  3. });

An object containing event name, callback pairs can also be used:

  1. BlogServer.on
  2. request: (request,response) ->
  3. console.log 'Incoming request'
  1. BlogServer.on({
  2. request: function(request, response) {
  3. return console.log('Incoming request');
  4. }
  5. });

@extend on: events

The same behavior can be achieved using a mixin:

  1. BlogServer.extend
  2. on:
  3. request: (request,response) ->
  4. console.log 'Incoming request'
  1. BlogServer.extend({
  2. on: {
  3. request: function(request, response) {
  4. return console.log('Incoming request');
  5. }
  6. }
  7. });

@unbind/removeListener event_name = false, handler = ->

  1. BlogServer.unbind 'error', handler #unbinds a particular handler
  2. BlogServer.unbind 'error' #unbinds all error handlers
  3. BlogServer.unbind() #unbinds all
  1. BlogServer.unbind('error', handler);
  2. BlogServer.unbind('error');
  3. BlogServer.unbind();

@trigger/emit event_name, args...

Trigger a given event with an arbitrary number of arguments.

  1. BlogServer.bind 'custom', (arg1,arg2) ->
  2. BlogServer.trigger 'custom', arg1, arg2
  1. BlogServer.bind('custom', function(arg1, arg2) {});
  2. BlogServer.trigger('custom', arg1, arg2);

Routes

@extend routes: path: view_name

Will register get handlers in express that will serve or execute JavaScript depending on the contents of the execute and javascripts directives, and will pass through route definitions to View.routes automatically.

  1. BlogServer.extend
  2. routes:
  3. '/': 'IndexView'
  4. '/post/:id': 'PostView'
  1. BlogServer.extend({
  2. routes: {
  3. '/': 'IndexView',
  4. '/post/:id': 'PostView'
  5. }
  6. });

You can optionally pass a callback for low level control just like in Express. Specifying a port number will proxy all requests below that path to the given port number, specifying a fully qualified URL will proxy all requests below that path to the given URL.

  1. BlogServer.extend
  2. routes:
  3. '/local_api/': 3002
  4. '/remote_api/': "http://api.service.com/"
  5. '/page/:name': (request,response) ->
  6. response.send 'Page Contents'
  7. # reqests to:
  8. # /local_api/posts/2.json would proxy the request to: localhost:3002/posts/2.json
  9. # /remote_api/posts/2.json would proxy the request to: http://api.service.com/posts/2.json
  1. BlogServer.extend({
  2. routes: {
  3. '/local_api/': 3002,
  4. '/remote_api/': "http://api.service.com/",
  5. '/page/:name': function(request, response) {
  6. return response.send('Page Contents');
  7. }
  8. }
  9. });

Cache

cache: [views...]

TODO