Coffee Views

Object oriented view system for Node.JS

Coffee Views was inspired by coffee-templates and coffeekup to add template inheritance to CoffeeScript templating.

Installation

npm i --save coffee-views

Example

# views/layout.coffee

{Html} = require 'coffee-views'

module.exports = class Layout extends Html

  # The #render() method is automatically called when rendering the view in express.
  render: (options)->
    @doctype 5
    @html {lang:'en'}, ->

      @head ->
        @title options.title
        @stylesheetBlock()

      @body ->
        @h1 options.title
        @contentBlock()

  stylesheetBlock: ->
    @link rel:'stylesheet', href:'/css/main.css'

  contentBlock: ->
# views/myview.coffee

Layout = require './layout'

module.exports = class MyView extends Layout
  contentBlock: ->
    @div ->
      @p 'This is my view'
View = require './views/myview'
view = new View()
console.log view.compile 'render', title: 'My Site'

... will produce ...

<!doctype html>
<html lang="en">
  <head>
    <title>My Site</title>
    <link rel="stylesheet" href="/css/main.css"/>
  </head>
  <body>
    <h1>My Site</h1>
    <div>
      <p>This is my view</p>
    </div>
  </body>
</html>

Example 2 - in Express.js

# app.coffee
# using the templates in the previous example

# ...

app = express()

app.configure ->
  app.engine 'coffee', require('coffee-views').engine
  app.set 'view engine', 'coffee'
  app.set 'views', path.join(__dirname, 'views')

# ...

app.get '/', (req, res)->
  res.render 'myview',
    title: 'My Site'

Inheriting methods

Just use CoffeeScript's native super() method to return a rendered parent method.

class MyExtendedView extends MyView
  contentBlock: ->
    super
    @div ->
      @p 'This is an extension to "MyView"'

view = new MyExtendedView() 
console.log view.compile 'render', title: 'My extended view'
###
<!doctype html>
<html>
  <head>
    <title>My extended view</title>
  </head>
  <body>
    <h1>My extended view</h1>
    <div>
      <p>This is my view</p>
    </div>
    <div>
      <p>This is an extension to "MyView"</p>
    </div>
  </body>
</html>
###

Plain ol' JavaScript

There's a possibility you may want to use plain JavaScript instead of CoffeeScript. In this case, just use Node's util module to extend your classes:

// layout.js
var Html = require('coffee-views').Html,
    util = require('util');

function Layout(){}
module.exports = Layout;
util.inherits(Layout, Html);

Layout.prototype.render = function(options){
  this.doctype(5);
  this.head(function(){
    this.title(options.title);
    this.stylesheetBlock();
  });
  this.body(function(){
    this.h1(options.title);
    this.contentBlock();
  });
};

Layout.prototype.stylesheetBlock = function(){};
Layout.prototype.contentBlock = function(){};
// myview.js
var Layout = require('./layout'),
    util = require('util');

function MyExtendedView(){}
module.exports = MyExtendedView;
util.inherits(MyExtendedView, Layout);

MyView.prototype.contentBlock = function(){
  this.div(function(){
    this.p('My content');
  });
};
// extendedview.js
var MyView = require('./myview'),
    util = require('util');

function ExtendedView(){}
module.exports = ExtendedView;
util.inherits(ExtendedView, MyView);

ExtendedView.prototype.contentBlock = function(){
  ExtendedView.super_.prototype.contentBlock.call(this);
  this.div(function(){
    this.p('Native extensions');
  });
};

Registering XML Tags

When creating your own XML tags you can register them like so:

# my-xml.coffee

{Xml} = require 'coffee-views'

class MyXml extends Xml

MyXml.registerTag 'mung'
MyXml.registerTag 'face'
MyXml.registerOpenTag 'this-is-always-open'
MyXml.registerClosedTag 'this-is-always-closed'
# your-xml.coffee

MyXml = require './my-xml'

class YourXml extends MyXml
  render: ->
    @mung ->
      @face 'yay, custom tags'
    @['this-is-always-open']()
    @['this-is-always-closed']()

# <mung><face>yay custom tags</face></mung>
# <this-is-always-open></this-is-always-open>
# <this-is-always-closed/>

Registered HTML Tags

Apart from the obvious HTML5 compliant tags, here are a few extras.

The JavaScript Tag

Using the #javascript() function will create a <script> tag with the passed function as a string.

class MyView extends Html
  javascriptBlock: ->
    @javascript ->
      alert 'Yay! CoffeeScript'

view = new MyView()
console.log view.compile 'javascriptBlock'
###
<script>
(function () {
  alert('Yay! CoffeeScript!');
}).call(this)
</script>
###

You can also pass server variables through to your client code:

class MyView extends Html
  javascriptBlock: (options)->

    # Assuming "options" is {username:'Craig David'}

    @javascript [options.username], (username)->
      alert "Your name is #{username}. Lucky you. *snigger*"

    @javascript [options], (options)->
      alert "Name: #{options.name}"

    @javascript [-> 'Craig David'], (getName)->
      alert "getting name... #{getName()}"

... will produce ...

<script>
(function(username){
  alert('Your name is ' + username + '. Lucky you. *snigger*');
}).call(this, 'Craig David')
</script>

<script>
(function(options){
  alert('Name: ' + options.name);
}).call(this, {username:'Craig David'});
</script>

<script>
(function(getName){
  alert('getting name... ' + getName());
}).call(this, function(){ return 'Craig David'; })
</script>

CSS

The #css() method renders as CCSS. Pass an object and it will create a <style> tag.

class MyView extends Html
  stylesheetBlock: ->
    @css
      form:
        input:
          padding: '5px'
          border: '1px solid'

view = new MyView()
console.log view.compile 'stylesheetBlock'
###
<style>form input {
  padding: 5px;
  border: 1px solid;
}
</style>
###

The Literal Tag

Using the #lit() method will just add any content to the output string:

class MyView extends Html
  contentBlock: ->
    @lit '<wierdtag/> Mung'

view = new MyView()
console.log view.compile 'contentBlock'
###
<wierdtag/> Mung
###

The Unliteral Tag

Using the #unlit() method will escape content if #safeOutput is set to true (which it is by default).

class MyView extends Html
  contentBlock: ->
    @unlit '<wierdtag/> Mung'

view = new MyView()
console.log view.compile 'contentBlock'
###
<wierdtag/> Mung
###

Escaping content on the fly

If you want to make sure something is escaped, go ahead and use the #escape() method:

escapedContent = @escape '<mung>' # => '<mung>'

Comments

HTML comments.

@comment 'Here\'a a comment'
@comment ->
  @p 'I\'ve been a commented out'

IE Specific comments

@ie ->
  @link rel:'stylesheet', href:'ie.css'
@ie 7, ->
  @link rel:'stylesheet', href:'ie7.css'
@ie 'lte', 8, ->
  @link rel:'stylesheet', href:'ie8.css'