# ## JstPages [module]
# A Sinatra plugin that adds support for JST (JavaScript Server Templates).
# See [JstPages example][jstx] for a full example application.
#
# ### Basic usage
# Register the `Sinatra::JstPages` plugin in your application, and use
# `serve_jst`. This example serves all JST files found in `/views/**/*.jst.*`
# (where `/views` is your views directory as defined in Sinatra's
# `settings.views`) as `http://localhost:4567/jst.js`.
#
# require 'sinatra/jstpages'
#
# class App < Sinatra::Base
# register Sinatra::JstPages
# serve_jst '/jst.js'
# end
#
# You will need to link to the JST route in your layout. Make a `
#
# So, if you have a JST view placed in `views/editor/edit.jst.tpl`:
#
# # views/editor/edit.jst.tpl
#
Edit <%= name %>
#
#
# Now in your browser you may invoke `JST['templatename']`:
#
# // Renders the editor/edit template
# JST['editor/edit']();
#
# // Renders the editor/edit template with template parameters
# JST['editor/edit']({name: 'Item Name'});
#
# #### Using Sinatra-AssetPack?
#
# __TIP:__ If you're using the [sinatra-assetpack][sap] gem, just add `/jst.js`
# to a package. (If you're not using Sinatra AssetPack yet, you probably
# should.)
#
# [sap]: http://ricostacruz.com/sinatra-assetpack
#
# ### Supported templates
#
# __[Jade][jade]__ (`.jst.jade`) -- Jade templates. This requires
# [jade.js][jade]. For older browsers, you will also need [json2.js][json2],
# and an implementation of [String.prototype.trim][trim].
#
# # views/editor/edit.jst.jade
# h1= "Edit "+name
# form
# button Save
#
# __[Underscore templates][under_tpl]__ (`.jst.tpl`) -- Simple templates by
# underscore. This requires [underscore.js][under], which Backbone also
# requires.
#
# # views/editor/edit.jst.tpl
# Edit <%= name %>
#
#
# __[Haml.js][haml]__ (`.jst.haml`) -- A JavaScript implementation of Haml.
# Requires [haml.js][haml].
#
# # views/editor/edit.jst.haml
# %h1= "Edit "+name
# %form
# %button Save
#
# __[Eco][eco]__ (`.jst.eco`) -- Embedded CoffeeScript templates. Requires
# [eco.js][eco] and [coffee-script.js][cs].
#
# # views/editor/edit.jst.eco
# Edit <%= name %>
#
#
# You can add support for more templates by subclassing the `Engine` class.
#
# [jade]: http://github.com/visionmedia/jade
# [json2]: https://github.com/douglascrockford/JSON-js
# [trim]: http://snippets.dzone.com/posts/show/701
# [under_tpl]: http://documentcloud.github.com/underscore/#template
# [under]: http://documentcloud.github.com/underscore
# [haml]: https://github.com/creationix/haml-js
# [eco]: https://github.com/sstephenson/eco
# [cs]: http://coffeescript.org
# [jstx]: https://github.com/rstacruz/sinatra-backbone/tree/master/examples/jstpages
#
module Sinatra
module JstPages
def self.registered(app)
app.extend ClassMethods
app.helpers Helpers
end
# Returns a hash to determine which engine is mapped onto a given extension.
def self.mappings
@mappings ||= Hash.new
end
def self.register(ext, engine)
mappings[ext] = engine
end
module Helpers
# Returns a list of JST files.
def jst_files(options = {})
# Tuples of [ name, Engine instance ]
root = options[:root] || settings.views
tuples = Dir.chdir(root) {
Dir["**/*.jst*"].map { |fn|
name = fn.match(%r{^(.*)\.jst})[1]
ext = fn.match(%r{\.([^\.]*)$})[1]
engine = JstPages.mappings[ext]
[ name, engine.new(File.join(root, fn)) ] if engine
}.compact
}
Hash[*tuples.flatten]
end
end
module ClassMethods
# ### serve_jst(path, [options]) [class method]
# Serves JST files in given `path`.
#
def serve_jst(path, options={})
get path do
content_type :js
jsts = jst_files(options).map { |(name, engine)|
%{
JST[#{name.inspect}] = function() {
if (!c[#{name.inspect}]) c[#{name.inspect}] = (#{engine.function});
return c[#{name.inspect}].apply(this, arguments);
};
}.strip.gsub(/^ {12}/, '')
}
%{
(function(){
var c = {};
if (!window.JST) window.JST = {};
#{jsts.join("\n ")}
})();
}.strip.gsub(/^ {12}/, '')
end
end
end
end
end
# ## JstPages::Engine [class]
# A template engine.
#
# #### Adding support for new template engines
# You will need to subclass `Engine`, override at least the `function` method,
# then use `JstPages.register`.
#
# This example will register `.jst.my` files to a new engine that uses
# `My.compile`.
#
# module Sinatra::JstPages
# class MyEngine < Engine
# def function() "My.compile(#{contents.inspect})"; end
# end
#
# register 'my', MyEngine
# end
#
module Sinatra::JstPages
class Engine
# ### file [attribute]
# The string path of the template file.
attr_reader :file
def initialize(file)
@file = file
end
# ### contents [method]
# Returns the contents of the template file as a string.
def contents
File.read(@file)
end
# ### function [method]
# The JavaScript function to invoke on the precompile'd object.
#
# What this returns should, in JavaScript, return a function that can be
# called with an object hash of the params to be passed onto the template.
def function
"_.template(#{contents.inspect})"
end
end
class HamlEngine < Engine
def function() "Haml(#{contents.inspect})"; end
end
class JadeEngine < Engine
def function() "require('jade').compile(#{contents.inspect})"; end
end
class EcoEngine < Engine
def function
"function() { var a = arguments.slice(); a.unshift(#{contents.inspect}); return eco.compile.apply(eco, a); }"
end
end
register 'tpl', Engine
register 'jst', Engine
register 'jade', JadeEngine
register 'haml', HamlEngine
register 'eco', EcoEngine
end