ViewJS
Introduction
Write applications using the latest HTML5 and JavaScript technologies, serving them as static HTML to legacy browsers.
Installation
- mkdir project_name
- cd project_name
- npm install view
- node_modules/view/bin/bootstrap
- node app.js
- mkdir(project_name);
- cd(project_name);
- npm(install(view));
- node_modules / view / bin / bootstrap;
- node(app.js);
Development
Run this command from your project:
- cake watch
- cake(watch);
This will compile the following stylus and coffeescript files:
- app/stylesheets -> public/stylesheets
- app/models -> public/javascripts/models
- app/collections -> public/javascripts/collections
- app/controllers -> public/javascripts/controllers
- app/views -> public/javascripts/views
- app/templates -> public/javascripts/templates.js
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:
- ContactView = View 'ContactView'
- var ContactView;
- 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:
- View
- ContactView: ->
- @initialize()
- View({
- ContactView: function() {
- return this.initialize();
- }
- });
@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.
- View.create ParentView:
- render: 'parent.eco'
- on:
- render: ->
- console.log 'render event triggered'
- View.create BuilderView: [Builder,
- render: ->
- @div()
- ]
- View.create({
- ParentView: {
- render: 'parent.eco',
- on: {
- render: function() {
- return console.log('render event triggered');
- }
- }
- }
- });
- View.create({
- BuilderView: [
- Builder, {
- render: function() {
- return this.div();
- }
- }
- ]
- });
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:
- {ChildView} = ParentView.create ChildView:
- render: 'child.eco'
- on:
- render: ->
- console.log 'render event triggered'
- #render now has two event handlers
- var ChildView;
- ChildView = ParentView.create({
- ChildView: {
- render: 'child.eco',
- on: {
- render: function() {
- return console.log('render event triggered');
- }
- }
- }
- }).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.
- anonymous_view = ChildView.create()
- var anonymous_view;
- 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.
- {PostView} = View.create PostView:
- render: 'post.eco' #processed with render directive
- key: 'value'
- PostView.key is 'value'
- var PostView;
- PostView = View.create({
- PostView: {
- render: 'post.eco',
- key: 'value'
- }
- }).PostView;
- 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:
- View.extend extend:on: (events) ->
- for event_name, callback of events
- @bind event_name, callback
- #now "on" can be passed to extend
- View.extend
- on:
- render: ->
- console.log 'Called when any view renders'
- View.extend({
- extend: {
- on: function(events) {
- var callback, event_name, _results;
- _results = [];
- for (event_name in events) {
- callback = events[event_name];
- _results.push(this.bind(event_name, callback));
- }
- return _results;
- }
- }
- });
- View.extend({
- on: {
- render: function() {
- return console.log('Called when any view renders');
- }
- }
- });
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.
- View.extend extend:routes: (routes,discard) ->
- #process routes once then
- discard()
- View.extend({
- extend: {
- routes: function(routes, discard) {
- return discard();
- }
- }
- });
@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.
- {PostCollectionView} = View.create PostCollectionView:
- render: 'post_preview.eco'
- collection: PostCollection
- initialize: (next) ->
- @collection.fetch success: next
- var PostCollectionView;
- PostCollectionView = View.create({
- PostCollectionView: {
- render: 'post_preview.eco',
- collection: PostCollection,
- initialize: function(next) {
- return this.collection.fetch({
- success: next
- });
- }
- }
- }).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.
- $('body').append PostCollectionView
- PostCollectionView.initialize ->
- console.log 'called when initialize is complete'
- $('body').append(PostCollectionView);
- PostCollectionView.initialize(function() {
- return console.log('called when initialize is complete');
- });
@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:
- {ApplicationView} = View.create ApplicationView: [Builder,
- views: ['PostCollectionView','SideBarView']
- render: ->
- @div(
- @div class: 'main', @PostCollectionView
- @div class: 'sidebar', @SideBarView
- )
- ]
- var ApplicationView;
- ApplicationView = View.create({
- ApplicationView: [
- Builder, {
- views: ['PostCollectionView', 'SideBarView'],
- render: function() {
- return this.div(this.div({
- "class": 'main',
- PostCollectionView: this.PostCollectionView
- }), this.div({
- "class": 'sidebar',
- SideBarView: this.SideBarView
- }));
- }
- }
- ]
- }).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.
- post = new Backbone.Model title: 'Post Title'
- {PostView} = View.create PostView:
- render: 'post.eco'
- model: post
- # post.eco
- # <h2><%= @title %></h2>
- var PostView, post;
- post = new Backbone.Model({
- title: 'Post Title'
- });
- PostView = View.create({
- PostView: {
- render: 'post.eco',
- model: post
- }
- }).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.
- {PostCollectionView} = View.create PostCollectionView:
- render: 'post_preview.eco'
- collection: PostCollection
- initialize: (next) ->
- @collection.fetch success: next
- var PostCollectionView;
- PostCollectionView = View.create({
- PostCollectionView: {
- render: 'post_preview.eco',
- collection: PostCollection,
- initialize: function(next) {
- return this.collection.fetch({
- success: next
- });
- }
- }
- }).PostCollectionView;
collection is designed for use with a Backbone.Collection, but any class conforming to the following API will work:
- method: bind(event_name,listener)
- event: all()
- event: add(model)
- event: remove(model)
- event: refresh()
@get key
- title = PostView.model.get 'title'
- var title;
- 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.
- View PostView: ->
- @on change: -> @render()
- @set title: 'Title One' #render() called
- @set {title: 'Title Two'}, {silent: true} #render() not called
- View({
- PostView: function() {
- this.on({
- change: function() {
- return this.render();
- }
- });
- this.set({
- title: 'Title One'
- });
- return this.set({
- title: 'Title Two'
- }, {
- silent: true
- });
- }
- });
@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.
- View.extend
- $: jQuery
- View.extend({
- $: jQuery
- });
@$ 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.
- PostView.$.hide() #hide while loading
- PostView.initialize ->
- @$.show()
- @$('li').addClass 'item'
- PostView.$.hide();
- PostView.initialize(function() {
- this.$.show();
- return this.$('li').addClass('item');
- });
@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.
- {TableView} = View.create TableView: [Builder,
- render: ->
- @table cellpadding: 0, cellspacing: 0,
- @tbody(
- @tr(
- @td 'Cell One'
- @td 'Cell Two'
- )
- )
- ]
- #without Builder
- {ListView} = View.create ListView:
- render: ->
- @tag('ul',@tag('li','Item One'))
- var ListView, TableView;
- TableView = View.create({
- TableView: [
- Builder, {
- render: function() {
- return this.table({
- cellpadding: 0,
- cellspacing: 0
- }, this.tbody(this.tr(this.td('Cell One'), this.td('Cell Two'))));
- }
- }
- ]
- }).TableView;
- ListView = View.create({
- ListView: {
- render: function() {
- return this.tag('ul', this.tag('li', 'Item One'));
- }
- }
- }).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:
- @ul @li @a('Link',href:'#').click ->
- this.ul(this.li(this.a('Link', {
- href: '#'
- }).click(function() {})));
@extend render: template
Specify a template to render:
- View.create PostView:
- model: post
- render: 'post.eco'
- View.create({
- PostView: {
- model: post,
- render: 'post.eco'
- }
- });
@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.
- View.create PostView: [Builder,
- model: post
- render: ->
- @div class: 'post',
- @a('My Link',href: '#').click -> false
- @h2 @model.get 'title'
- ]
- View.create({
- PostView: [
- Builder, {
- model: post,
- render: function() {
- return this.div({
- "class": 'post'
- }, this.a('My Link', {
- href: '#'
- }).click(function() {
- return false;
- }), this.h2(this.model.get('title')));
- }
- }
- ]
- });
@render()
render() is automatically called when initialize() has completed. Subsequent calls to render() will replace the contents of the view's element.
- View.create PostView:
- render: 'post.eco'
- on:
- change:
- id: (id) ->
- Post.find id, (post) =>
- @model = post
- @render()
- var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
- View.create({
- PostView: {
- render: 'post.eco',
- on: {
- change: {
- id: function(id) {
- return Post.find(id, __bind(function(post) {
- this.model = post;
- return this.render();
- }, this));
- }
- }
- }
- }
- });
@extend helpers: template_helpers
Adds helper methods to templates:
- View.extend helpers:
- url: (params) ->
- @url params
- View.extend({
- helpers: {
- url: function(params) {
- return this.url(params);
- }
- }
- });
@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.
- $('body').append PostView
- PostView.$.html() #empty
- PostView.initialize ->
- @$.html() #contents of post.eco
- $('body').append(PostView);
- PostView.$.html();
- PostView.initialize(function() {
- return this.$.html();
- });
@extend element: -> Element
Override the default element which is a div with a className containing the name attribute of the view.
- {PostCollectionView} = View.create
- PostCollectionView: [Builder,
- collection: PostCollection
- element: ->
- @ul()
- render: (post) ->
- @li post.get 'title'
- ]
- var PostCollectionView;
- PostCollectionView = View.create({
- PostCollectionView: [
- Builder, {
- collection: PostCollection,
- element: function() {
- return this.ul();
- },
- render: function(post) {
- return this.li(post.get('title'));
- }
- }
- ]
- }).PostCollectionView;
@extend delegate: events
- View.create PostView:
- delegate:
- 'click a.class': (event) ->
- View.create({
- PostView: {
- delegate: {
- 'click a.class': function(event) {}
- }
- }
- });
@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.
- View.extend
- templates:
- 'post.eco': (attributes) ->
- "html output"
- View.extend({
- templates: {
- 'post.eco': function(attributes) {
- return "html output";
- }
- }
- });
Events
The following events are triggered internally by View:
- initialize: -> Triggered when the view is initialized.
- change: -> Triggered when any attribute in the view has changed.
- change:key: (value) -> Triggered when a particular key changes.
- ready: -> Triggered the first time a view is rendered.
- render: -> Triggered when a view is rendered.
- activated: -> Triggered when a route activates the view.
- deactivated: -> Triggered when a view was active, and another view is activated by a route.
- error: (error) -> Triggered when an exception is thrown.
- warning: (warning) -> Triggered when a warning (such as a deprecation) occurs.
@bind event_name, callback
@on event_name, callback
Bind a callback to an event. bind and on are aliases.
- PostView.bind 'render', ->
- console.log 'Post rendered'
- PostView.bind('render', function() {
- return console.log('Post rendered');
- });
An object containing event name, callback pairs can also be used:
- PostView.on
- render: ->
- console.log 'Post rendered'
- initialize: ->
- console.log 'Post initialized'
- PostView.on({
- render: function() {
- return console.log('Post rendered');
- },
- initialize: function() {
- return console.log('Post initialized');
- }
- });
@extend on: events
The same behavior can be achieved using a mixin:
- PostView.extend
- on:
- render: ->
- console.log 'Post rendered'
- PostView.extend({
- on: {
- render: function() {
- return console.log('Post rendered');
- }
- }
- });
@unbind/removeListener event_name = false, handler = ->
- PostView.unbind 'render', handler #unbinds a particular handler
- PostView.unbind 'render' #unbinds all render handlers
- PostView.unbind() #unbinds all
- PostView.unbind('render', handler);
- PostView.unbind('render');
- 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.
- PostView.bind 'custom', (arg1,arg2) ->
- PostView.trigger 'custom', arg1, arg2
- PostView.bind('custom', function(arg1, arg2) {});
- PostView.trigger('custom', arg1, arg2);
@before: method_name: (original_method,args...) ->
A simple implementation of AOP. A logger could be implemented as:
- View.extend log: (method_name) ->
- @before method_name, (next,args...) ->
- response = next.apply @, args
- console.log "#{@name}.#{method_name}", args, ' -> ', response
- response
- View.log 'set'
- View.log 'get'
- instance = View.create()
- instance.set key: 'value'
- var instance;
- var __slice = Array.prototype.slice;
- View.extend({
- log: function(method_name) {
- return this.before(method_name, function() {
- var args, next, response;
- next = arguments[0], args = 2
Env
Allows for the conditional execution of code depending on environment. The following environments are built in:
- server: Executed when the application is being processed by NodeJS
- browser: Executed by a remote client / web browser. Note that this will become "client" in a future release, but is not presently so due to internal usage. Usage of "browser" will continue to work but will issue a deprecation warning in the future.
@env: name: ->
- View.env
- server: -> console.log 'only run on the server'
- browser: -> console.log 'only run on a browser'
- View.env({
- server: function() {
- return console.log('only run on the server');
- },
- browser: function() {
- return console.log('only run on a browser');
- }
- });
@env: set: name: ->
New environment names can be set using the set key:
- View.env set:
- ie: -> !!window.attachEvent and not window.opera
- View.env({
- set: {
- ie: function() {
- return !!window.attachEvent && !window.opera;
- }
- }
- });
@extend env: name: ->
The env directive can be used in a mixin.
- View.extend
- env:
- ie: ->
- @on render: -> console.log 'render called in IE'
- View.extend({
- env: {
- ie: function() {
- return this.on({
- render: function() {
- return console.log('render called in IE');
- }
- });
- }
- }
- });
@extend env: name: mixin
The env directive in a mixin, can itself contain a mixin that will be passed to extend.
- View.extend
- env:
- ie:
- on:
- render: -> console.log 'render called in IE'
- View.extend({
- env: {
- ie: {
- on: {
- render: function() {
- return console.log('render called in IE');
- }
- }
- }
- }
- });
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.
- View.extend
- routes:
- '/about/': 'AboutPageView'
- '/contact/': 'ContactPageView'
- '/blog/:post_id': 'PostView'
- View.extend({
- routes: {
- '/about/': 'AboutPageView',
- '/contact/': 'ContactPageView',
- '/blog/:post_id': 'PostView'
- }
- });
@url view_name: attributes
Generates a URL for a given view and attributes. The view must have a corresponding route.
- '/post/5' is View.url PostView: id: 5
- '/post/5' is PostView.url id: 5
- '/' is View.url IndexView: {}
- '/' is IndexView.url()
- '/post/5' === View.url({
- PostView: {
- id: 5
- }
- });
- '/post/5' === PostView.url({
- id: 5
- });
- '/' === View.url({
- IndexView: {}
- });
- '/' === 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.
- View.extend
- routes:
- '/about/': 'AboutPageView'
- '/contact/': 'ContactPageView'
- '/blog/:post_id': 'PostView'
- View.create
- ApplicationView: [Router,
- render: ->
- @div @Router
- ]
- AboutPageView:
- render: 'about.eco'
- ContactPageView:
- render: 'contact.eco'
- PostView:
- render: 'post.eco'
- on:
- change:
- post_id: (post_id) ->
- Post.find post_id, (post) =>
- @model = post
- @render()
- var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
- View.extend({
- routes: {
- '/about/': 'AboutPageView',
- '/contact/': 'ContactPageView',
- '/blog/:post_id': 'PostView'
- }
- });
- View.create({
- ApplicationView: [
- Router, {
- render: function() {
- return this.div(this.Router);
- }
- }
- ],
- AboutPageView: {
- render: 'about.eco'
- },
- ContactPageView: {
- render: 'contact.eco'
- },
- PostView: {
- render: 'post.eco',
- on: {
- change: {
- post_id: function(post_id) {
- return Post.find(post_id, __bind(function(post) {
- this.model = post;
- return this.render();
- }, this));
- }
- }
- }
- }
- });
Builder
The builder mixin adds all valid HTML5 tag names as methods to a view. Usage of Builder is covered in the tag method.
- {TableView} = View.create TableView: [Builder,
- render: ->
- @table cellpadding: 0, cellspacing: 0,
- @tbody(
- @tr(
- @td 'Cell One'
- @td 'Cell Two'
- )
- )
- ]
- var TableView;
- TableView = View.create({
- TableView: [
- Builder, {
- render: function() {
- return this.table({
- cellpadding: 0,
- cellspacing: 0
- }, this.tbody(this.tr(this.td('Cell One'), this.td('Cell Two'))));
- }
- }
- ]
- }).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:
- BlogServer = View 'BlogServer'
- var BlogServer;
- 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:
- ViewServer BlogServer: ->
- @server.use express.logger()
- ViewServer({
- BlogServer: function() {
- return this.server.use(express.logger());
- }
- });
@create server_name: mixins
The ViewServer's mixin system and create method works identically to View's.
- ViewServer.create BlogServer: ->
- port: 3001
- public: __dirname + '/public'
- ViewServer.create({
- BlogServer: function() {
- return {
- port: 3001,
- public: __dirname + '/public'
- };
- }
- });
Server
@server
The Express instance which handles requests for the ViewServer.
- express = require 'express'
- BlogServer.server.use express.logger()
- BlogServer.server.get '/custom/', (request,response) ->
- var express;
- express = require('express');
- BlogServer.server.use(express.logger());
- 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:
- express = require 'express'
- server = express.createServer()
- {BlogServer} = ViewServer.create
- server: server
- var BlogServer, express, server;
- express = require('express');
- server = express.createServer();
- BlogServer = ViewServer.create({
- server: server
- }).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.
- BlogServer.extend javascripts: [
- "public/javascripts/lib/view.js"
- "public/javascripts/models/"
- "public/javascripts/views/"
- ]
- BlogServer.extend({
- javascripts: ["public/javascripts/lib/view.js", "public/javascripts/models/", "public/javascripts/views/"]
- });
@extend execute: [execute...]
JavaScript to execute server side on each request. If a directory is specified it will be recursively searched for .js files.
- BlogServer.extend execute: [
- "public/javascripts/lib/view.js"
- "public/javascripts/models/"
- "public/javascripts/views/"
- ]
- BlogServer.extend({
- execute: ["public/javascripts/lib/view.js", "public/javascripts/models/", "public/javascripts/views/"]
- });
@extend stylesheets: [stylesheets...]
Add stylesheets to. If a directory is specified it will be recursively searched for .css files.
- BlogServer.extend stylesheets: [
- "public/stylesheets/a.css"
- "public/stylesheets/b.css"
- #now scan the directory for all remaining stylesheets
- "public/stylesheets/"
- ]
- BlogServer.extend({
- stylesheets: ["public/stylesheets/a.css", "public/stylesheets/b.css", "public/stylesheets/"]
- });
@extend meta: [meta...]
Inject arbitrary meta information into the document head.
- BlogServer.extend meta: [
- '<link rel="alternate" type="application/rss+xml" title="Feed Name" href="url" />'
- ]
- BlogServer.extend({
- meta: ['']
- });
@extend bundle: assets
TODO
Env
Execute code, or process mixin directives conditionally per request. The following environments are pre-defined:
- html5: If the user-agent supports the HTML5 history API.
- legacy: If the user-agent does not support the HTML5 history API.
@env: request, name: ->
- View.env request,
- html5: ->
- console.log 'only run when an HTML5 capable user agent initiated the request'
- legacy: (request) ->
- console.log 'only run when a non HTML5 capable user agent initiated the request'
- View.env(request, {
- html5: function() {
- return console.log('only run when an HTML5 capable user agent initiated the request');
- },
- legacy: function(request) {
- return console.log('only run when a non HTML5 capable user agent initiated the request');
- }
- });
@extend: env: name: mixin
The following mixin directives can be used in an env directive:
- execute
- javascripts
- stylesheets
- meta
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.
- 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"
- ]
- {MyServer} = ViewServer.create MyServer:
- public: __dirname + '/public'
- javascripts: [
- "http://use.typekit.com/xxx.js"
- ]
- env:
- legacy:
- execute: application_payload
- html5:
- javascripts: application_payload
- var MyServer, application_payload;
- 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"];
- MyServer = ViewServer.create({
- MyServer: {
- public: __dirname + '/public',
- javascripts: ["http://use.typekit.com/xxx.js"],
- env: {
- legacy: {
- execute: application_payload
- },
- html5: {
- javascripts: application_payload
- }
- }
- }
- }).MyServer;
@env: set: name: (request) ->
Define a new environment. Request may not be present, so always check for request? first.
- ViewServer.env set:
- ios: (request) ->
- request? and request.headers['user-agent'].match /(iPhone|iPad)/
- ViewServer.env({
- set: {
- ios: function(request) {
- return (request != null) && request.headers['user-agent'].match(/(iPhone|iPad)/);
- }
- }
- });
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:
- ViewServer.env set:
- production: ->
- process?.env?.VCAP_APPLICATION
- development: ->
- not process?.env?.VCAP_APPLICATION
- ViewServer.env({
- set: {
- production: function() {
- var _ref;
- return typeof process != "undefined" && process !== null ? (_ref = process.env) != null ? _ref.VCAP_APPLICATION : void 0 : void 0;
- },
- development: function() {
- var _ref;
- return !(typeof process != "undefined" && process !== null ? (_ref = process.env) != null ? _ref.VCAP_APPLICATION : void 0 : void 0);
- }
- }
- });
Events
The following events are triggered internally by ViewServer:
- error: (error) -> Triggered when an exception is thrown.
- warning: (warning) -> Triggered when a warning (such as a deprecation) occurs.
- request: (request,response) -> Triggered when an incoming request will be handled by a view.
@bind event_name, callback
@on event_name, callback
Bind a callback to an event. bind and on are aliases.
- BlogServer.bind 'request', (request,response) ->
- console.log 'Incoming request'
- BlogServer.bind('request', function(request, response) {
- return console.log('Incoming request');
- });
An object containing event name, callback pairs can also be used:
- BlogServer.on
- request: (request,response) ->
- console.log 'Incoming request'
- BlogServer.on({
- request: function(request, response) {
- return console.log('Incoming request');
- }
- });
@extend on: events
The same behavior can be achieved using a mixin:
- BlogServer.extend
- on:
- request: (request,response) ->
- console.log 'Incoming request'
- BlogServer.extend({
- on: {
- request: function(request, response) {
- return console.log('Incoming request');
- }
- }
- });
@unbind/removeListener event_name = false, handler = ->
- BlogServer.unbind 'error', handler #unbinds a particular handler
- BlogServer.unbind 'error' #unbinds all error handlers
- BlogServer.unbind() #unbinds all
- BlogServer.unbind('error', handler);
- BlogServer.unbind('error');
- BlogServer.unbind();
@trigger/emit event_name, args...
Trigger a given event with an arbitrary number of arguments.
- BlogServer.bind 'custom', (arg1,arg2) ->
- BlogServer.trigger 'custom', arg1, arg2
- BlogServer.bind('custom', function(arg1, arg2) {});
- 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.
- BlogServer.extend
- routes:
- '/': 'IndexView'
- '/post/:id': 'PostView'
- BlogServer.extend({
- routes: {
- '/': 'IndexView',
- '/post/:id': 'PostView'
- }
- });
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.
- BlogServer.extend
- routes:
- '/local_api/': 3002
- '/remote_api/': "http://api.service.com/"
- '/page/:name': (request,response) ->
- response.send 'Page Contents'
- # reqests to:
- # /local_api/posts/2.json would proxy the request to: localhost:3002/posts/2.json
- # /remote_api/posts/2.json would proxy the request to: http://api.service.com/posts/2.json
- BlogServer.extend({
- routes: {
- '/local_api/': 3002,
- '/remote_api/': "http://api.service.com/",
- '/page/:name': function(request, response) {
- return response.send('Page Contents');
- }
- }
- });
Cache
cache: [views...]
TODO