intermine/blad

Name: blad

Owner: InterMine

Description: A forms based Node.js CMS

Created: 2013-11-05 16:08:57.0

Updated: 2013-11-05 19:10:15.0

Pushed: 2013-11-05 19:10:14.0

Homepage:

Size: 1581

Language: CoffeeScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

blaš

A forms based node.js CMS ala SilverStripe, but smaller.

Codeship Status for radekstepan/blad

The idea was to create a RESTful CMS API that would be edited using a client side app. On the backend, we use flatiron and on the frontend chaplin that itself wraps Backbone.js.

image

Getting started

The way to use this CMS is to require it as a package. See blaš example site for a guide on how to do so. If you know what you are doing, require and start the service like so:

    = require 'fs'
ice = require 'blad'

ig = JSON.parse fs.readFileSync './config.json'
ice.start config, __dirname
Creating custom document types

Create a new folder with the type name in ./src/site. Each type consists of three files:

Admin form

Represented by a form.eco file.

Each document form automatically has the url, is public? and type fields. Any extra fields are defined by creating a form field that has a unique name attribute.

For example, the Markdown document type has a <textarea> defined like so:

 class="nine columns">
<textarea name="markup"><%= @markup %></textarea>
v>

Notice that to display the already saved version of that field, we use eco markup that populates a variable by the name of the field.

Files

File upload fields are a special case that need to have two fields defined. One for the actual type="file" and one for a place where the field will be loaded client side:

ut type="hidden" name="image" value="<%= @image %>" />
ut type="file" data-custom="file" data-target="image" />

The attribute data-target, then, specifies which field to populate with base64 encoded version of the file client side.

Dates

By the same token, we use Kronic to work with nicely formatted dates. To make use of this library, define the date fields like so:

ut type="hidden" name="published" value="<%= @published or (new Date()).toJSON() %>" />
ut type="text" data-custom="date" data-target="published" value="<%= if @published then Kronic.format(new Date(@published)) else 'Today' %>" />
Public presenter

Represented by a presenter.coffee file.

Each document has a custom class that determines how it is rendered. It has to only have a render function defined that takes a callback with contect that is passed to a template. As an example of Markdown rendering that returns the HTML result under the html key:

ad } = require 'blad'
ed   = require 'marked'

s exports.MarkdownDocument extends blad.Type

# Presentation for the document.
render: (done) -> done 'html': marked @markup

Extending the blad.Type class gives us the following helpers:

Public template

Represented by a template.eco file.

This file is populated with a context coming from the presenter. In the above Markdown example, we have passed only the html key - value forward.

If a /src/site/layout.eco file is found, it will be used as a wrapping template around individual templates. The key page populated with the individual template is passed to it.

Additions file

One can save a file in src/types/additions.coffee that exports an object. Then, this file can be included from within a Presenter. This way, you can re-use common functionality across types.

Caching

Sometimes new data may be fetched from within the Presenter and one would like to cache these for say a day. The following shows a workflow from within the Presneter's render() function.

  1. We check if data under a specific key is old. The second parameter represents a time in milliseconds after which to consider a key value pair to be old. One could also pass a third paramter passing in which unit the previous parameter is.
  2. If all data is fresh we get a data saved under a key. We could also pass a context/document as the second parameter. This is useful if we want to retrieve cache for a sibling, child document etc.
  3. If data is old, we save the new data returning the result in a callback.
# Check if data in store is old.
if @store.isOld 'data', 300
    # Update with new info and render back.
    @store.save 'data', 'new information', =>
        done
            'data': @store.get('data')
            'was':  'old'
        , false
else
    # Nope, all fresh.
    done
        'data': @store.get('data')
        'was':  'fresh'
    , false

It may be useful to know that cache is per document specific, so one can use the same cache key in different document types.

Mocha test suite

To run the tests execute the following.

m install
m test

A test collection in MongoDB will be created and cleared before each spec run. Make sure the server app is switched off in order to run the tests.

db (collection) ->
collection.remove {}, (error, removed) ->
    collection.find({}).toArray (error, results) ->
        results.length.should.equal 0
        done()

If you want to test the UI through an example app, execute the following:

RT=5200 npm start

Then visit http://127.0.0.1:5200/admin.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.