todomvc
Version:
> Helping you select an MV\* framework
249 lines (236 loc) • 6.77 kB
JavaScript
/*!
* CanJS - 2.0.3
* http://canjs.us/
* Copyright (c) 2013 Bitovi
* Tue, 26 Nov 2013 18:21:22 GMT
* Licensed MIT
* Includes: CanJS default build
* Download from: http://canjs.us/
*/
define(["can/util/library", "can/view", "can/util/string", "can/compute", "can/view/scanner", "can/view/render"], function( can ) {
// ## ejs.js
// `can.EJS`
// _Embedded JavaScript Templates._
// Helper methods.
var extend = can.extend,
EJS = function( options ) {
// Supports calling EJS without the constructor
// This returns a function that renders the template.
if ( this.constructor != EJS ) {
var ejs = new EJS(options);
return function( data, helpers ) {
return ejs.render(data, helpers);
};
}
// If we get a `function` directly, it probably is coming from
// a `steal`-packaged view.
if ( typeof options == "function" ) {
this.template = {
fn: options
};
return;
}
// Set options on self.
extend(this, options);
this.template = this.scanner.scan(this.text, this.name);
};
can.EJS = EJS;
/**
* @add can.EJS
* @prototype
*/
EJS.prototype.
/**
* @function can.EJS.prototype.render render
* @parent can.EJS.prototype
* @description Render a view object with data and helpers.
* @signature `ejs.render(data[, helpers])`
* @param {Object} [data] The data to populate the template with.
* @param {Object.<String, function>} [helpers] Helper methods referenced in the template.
* @return {String} The template with interpolated data.
*
* @body
* Renders an object with view helpers attached to the view.
*
* var rendered = new can.EJS({text: "<h1><%= message %>"</h1>}).render({
* message: "foo"
* },{helper: function(){ ... }})
*
* console.log(rendered); // "<h1>foo</h1>"
*/
render = function( object, extraHelpers ) {
object = object || {};
return this.template.fn.call(object, object, new EJS.Helpers(object, extraHelpers || {}));
};
extend(EJS.prototype, {
/**
* @hide
* Singleton scanner instance for parsing templates.
*/
scanner: new can.view.Scanner({
text: {
outStart: 'with(_VIEW) { with (_CONTEXT) {',
outEnd: "}}",
argNames: '_CONTEXT,_VIEW'
},
/**
* @hide
* An ordered token registry for the scanner.
* This needs to be ordered by priority to prevent token parsing errors.
* Each token is defined as: ["token-name", "string representation", "optional regexp override"]
*/
tokens: [
["templateLeft", "<%%"], // Template
["templateRight", "%>"], // Right Template
["returnLeft", "<%=="], // Return Unescaped
["escapeLeft", "<%="], // Return Escaped
["commentLeft", "<%#"], // Comment
["left", "<%"], // Run --- this is hack for now
["right", "%>"], // Right -> All have same FOR Mustache ...
["returnRight", "%>"]
],
helpers: [
/**
* Check if its a func like `()->`.
* @param {String} content
*/
{
name:/\s*\(([\$\w]+)\)\s*->([^\n]*)/,
fn: function(content){
var quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/,
parts = content.match(quickFunc);
return "can.proxy(function(__){var " + parts[1] + "=can.$(__);" + parts[2] + "}, this);";
}
}],
/**
* @hide
* Transforms the EJS template to add support for shared blocks.
* Essentially, this breaks up EJS tags into multiple EJS tags
* if they contained unmatched brackets.
*
* For example, this doesn't work:
* <% if (1) { %><% if (1) { %> hi <% } } %>
* ...without isolated EJS blocks:
* <% if (1) { %><% if (1) { %> hi <% } %><% } %>
* The result of transforming:
* <% if (1) { %><% %><% if (1) { %><% %> hi <% } %><% } %>
*/
transform: function(source) {
return source.replace(/<%([\s\S]+?)%>/gm, function(whole, part) {
var brackets = [],
foundBracketPair,
i;
// Look for brackets (for removing self-contained blocks)
part.replace(/[{}]/gm, function(bracket, offset) {
brackets.push([ bracket, offset ]);
});
// Remove bracket pairs from the list of replacements
do {
foundBracketPair = false;
for (i = brackets.length - 2; i >= 0; i--) {
if (brackets[i][0] == '{' && brackets[i+1][0] == '}') {
brackets.splice(i, 2);
foundBracketPair = true;
break;
}
}
} while (foundBracketPair);
// Unmatched brackets found, inject EJS tags
if (brackets.length >= 2) {
var result = ['<%'],
bracket,
last = 0;
for (i = 0; bracket = brackets[i]; i++) {
result.push(part.substring(last, last = bracket[1]));
if ((bracket[0] == '{' && i < brackets.length - 1) || (bracket[0] == '}' && i > 0)) {
result.push(bracket[0] == '{' ? '{ %><% ' : ' %><% }');
}
else {
result.push(bracket[0]);
}
++last;
}
result.push(part.substring(last), '%>');
return result.join('');
}
// Otherwise return the original
else {
return '<%' + part + '%>';
}
});
}
})
});
EJS.Helpers = function( data, extras ) {
this._data = data;
this._extras = extras;
extend(this, extras);
};
/**
* @page can.EJS.Helpers Helpers
* @parent can.EJS
*
* @body
* By adding functions to can.EJS.Helpers.prototype, those functions will be available in the
* views.
*
* The following helper converts a given string to upper case:
*
* can.EJS.Helpers.prototype.toUpper = function(params)
* {
* return params.toUpperCase();
* }
*
* Use it like this in any EJS template:
*
* <%= toUpper('javascriptmvc') %>
*
* To access the current DOM element return a function that takes the element as a parameter:
*
* can.EJS.Helpers.prototype.upperHtml = function(params)
* {
* return function(el) {
* $(el).html(params.toUpperCase());
* }
* }
*
* In your EJS view you can then call the helper on an element tag:
*
* <div <%= upperHtml('javascriptmvc') %>></div>
*/
EJS.Helpers.prototype = {
// TODO Deprecated!!
list : function(list, cb){
can.each(list, function(item, i){
cb(item, i, list)
})
},
each: function(list, cb){
// Normal arrays don't get live updated
if (can.isArray(list)) {
this.list(list, cb);
}
else {
can.view.lists(list, cb);
}
}
};
// Options for `steal`'s build.
can.view.register({
suffix: "ejs",
// returns a `function` that renders the view.
script: function( id, src ) {
return "can.EJS(function(_CONTEXT,_VIEW) { " + new EJS({
text: src,
name: id
}).template.out + " })";
},
renderer: function( id, text ) {
return EJS({
text: text,
name: id
});
}
});
return can;
});