diff --git a/.gitignore b/.gitignore index 8217b24..0a467ad 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ node_modules dump.rdb npm-debug.log *.tgz +*.swp +*.swo +*~ +!.gitignore diff --git a/HISTORY.md b/HISTORY.md index 28ced50..f887cb1 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,27 +1,4 @@ -0.1.3 / 2012-04-10 +0.1.0 / 2012-09-20 ================== - -* New API for SocketStream 0.3 beta2 -* Now includes client code (Hogan VM) for client -* Improved error reporting in console -* Reset repo to remove rubbish - - -0.1.2 / 2012-03-17 -================== - -* Templates are now appended to `ss.tmpl` instead of global variables (e.g. `HT`) -* Tip: You may put `window.HT = ss.tmpl` into `entry.js` to avoid changing your code -* Reduced amount of code output for apps with many templates - - -0.1.1 / 2012-03-17 -================== - -* Upgraded `hogan` to 2.0.0 - - -0.1.0 / 2012-01-14 -================== - -* Initial commit \ No newline at end of file +* Initial commit +* Forked from ss-hogan v0.1.3 https://github.com/socketstream/ss-hogan/commit/ab32279bfd57882bb7feeccc66faee3e39843749 diff --git a/README.md b/README.md index 6ea25e4..8fc419b 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,42 @@ -# Hogan Template Engine wrapper for SocketStream 0.3 +# Handlebars Template Engine wrapper for SocketStream 0.3 -http://twitter.github.com/hogan.js/ +http://handlebarsjs.com/ -Use pre-compiled Hogan (Mustache-compatible) client-side templates in your app to benefit from increased performance and a smaller client-side library download. +Use pre-compiled Handlebars client-side templates in your app. ### Installation - -The `ss-hogan` module is installed by default with new apps created by SocketStream 0.3. If you don't already have it installed, add `ss-hogan` to your application's `package.json` file and then add this line to app.js: +Add ss-handlebars to your application's package.json file and then add this line to app.js: ```javascript -ss.client.templateEngine.use(require('ss-hogan')); +ss.client.templateEngine.use(require('ss-handlebars')); ``` Restart the server. From now on all templates will be pre-compiled and accessibale via the `ss.tmpl` object. -Note: Hogan uses a small [client-side VM](https://raw.github.com/twitter/hogan.js/master/lib/template.js) which renders the pre-compiled templates. This file is included and automatically sent to the client. +Note: Handlebars uses a small [client-side runtime](http://handlebarsjs.com/precompilation.html) which renders the pre-compiled templates. This file is included and automatically sent to the client. ### Usage E.g. a template placed in - /client/templates/offers/latest.html + /client/templates/offers/latest.hds Can be rendered in your browser with ```javascript // assumes var ss = require('socketstream') -var html = ss.tmpl['offers-latest'].render({name: 'Special Offers'}) +var html = ss.tmpl['offers-latest']({name: 'Special Offers'}) ``` ### Options -When experimenting with Hogan, or converting an app from one template type to another, you may find it advantageous to use multiple template engines and confine use of Hogan to a sub-directory of `/client/templates`. +When experimenting with Handlebars, or converting an app from one template type to another, you may find it advantageous to use multiple template engines and confine use of Handlebars to a sub-directory of `/client/templates`. Directory names can be passed to the second argument as so: ```javascript -ss.client.templateEngine.use(require('ss-hogan'), '/hogan-templates'); -``` \ No newline at end of file +ss.client.templateEngine.use(require('ss-handlebars'), '/hds-templates'); +``` diff --git a/client.js b/client.js deleted file mode 100644 index 8491aad..0000000 --- a/client.js +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright 2011 Twitter, Inc. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -var Hogan = {}; - -(function (Hogan, useArrayBuffer) { - Hogan.Template = function (renderFunc, text, compiler, options) { - this.r = renderFunc || this.r; - this.c = compiler; - this.options = options; - this.text = text || ''; - this.buf = (useArrayBuffer) ? [] : ''; - } - - Hogan.Template.prototype = { - // render: replaced by generated code. - r: function (context, partials, indent) { return ''; }, - - // variable escaping - v: hoganEscape, - - // triple stache - t: coerceToString, - - render: function render(context, partials, indent) { - return this.ri([context], partials || {}, indent); - }, - - // render internal -- a hook for overrides that catches partials too - ri: function (context, partials, indent) { - return this.r(context, partials, indent); - }, - - // tries to find a partial in the curent scope and render it - rp: function(name, context, partials, indent) { - var partial = partials[name]; - - if (!partial) { - return ''; - } - - if (this.c && typeof partial == 'string') { - partial = this.c.compile(partial, this.options); - } - - return partial.ri(context, partials, indent); - }, - - // render a section - rs: function(context, partials, section) { - var tail = context[context.length - 1]; - - if (!isArray(tail)) { - section(context, partials, this); - return; - } - - for (var i = 0; i < tail.length; i++) { - context.push(tail[i]); - section(context, partials, this); - context.pop(); - } - }, - - // maybe start a section - s: function(val, ctx, partials, inverted, start, end, tags) { - var pass; - - if (isArray(val) && val.length === 0) { - return false; - } - - if (typeof val == 'function') { - val = this.ls(val, ctx, partials, inverted, start, end, tags); - } - - pass = (val === '') || !!val; - - if (!inverted && pass && ctx) { - ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]); - } - - return pass; - }, - - // find values with dotted names - d: function(key, ctx, partials, returnFound) { - var names = key.split('.'), - val = this.f(names[0], ctx, partials, returnFound), - cx = null; - - if (key === '.' && isArray(ctx[ctx.length - 2])) { - return ctx[ctx.length - 1]; - } - - for (var i = 1; i < names.length; i++) { - if (val && typeof val == 'object' && names[i] in val) { - cx = val; - val = val[names[i]]; - } else { - val = ''; - } - } - - if (returnFound && !val) { - return false; - } - - if (!returnFound && typeof val == 'function') { - ctx.push(cx); - val = this.lv(val, ctx, partials); - ctx.pop(); - } - - return val; - }, - - // find values with normal names - f: function(key, ctx, partials, returnFound) { - var val = false, - v = null, - found = false; - - for (var i = ctx.length - 1; i >= 0; i--) { - v = ctx[i]; - if (v && typeof v == 'object' && key in v) { - val = v[key]; - found = true; - break; - } - } - - if (!found) { - return (returnFound) ? false : ""; - } - - if (!returnFound && typeof val == 'function') { - val = this.lv(val, ctx, partials); - } - - return val; - }, - - // higher order templates - ho: function(val, cx, partials, text, tags) { - var compiler = this.c; - var t = val.call(cx, text, function(t) { - return compiler.compile(t, {delimiters: tags}).render(cx, partials); - }); - this.b(compiler.compile(t.toString(), {delimiters: tags}).render(cx, partials)); - return false; - }, - - // template result buffering - b: (useArrayBuffer) ? function(s) { this.buf.push(s); } : - function(s) { this.buf += s; }, - fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } : - function() { var r = this.buf; this.buf = ''; return r; }, - - // lambda replace section - ls: function(val, ctx, partials, inverted, start, end, tags) { - var cx = ctx[ctx.length - 1], - t = null; - - if (!inverted && this.c && val.length > 0) { - return this.ho(val, cx, partials, this.text.substring(start, end), tags); - } - - t = val.call(cx); - - if (typeof t == 'function') { - if (inverted) { - return true; - } else if (this.c) { - return this.ho(t, cx, partials, this.text.substring(start, end), tags); - } - } - - return t; - }, - - // lambda replace variable - lv: function(val, ctx, partials) { - var cx = ctx[ctx.length - 1]; - var result = val.call(cx); - if (typeof result == 'function') { - result = result.call(cx); - } - result = result.toString(); - - if (this.c && ~result.indexOf("{\u007B")) { - return this.c.compile(result).render(cx, partials); - } - - return result; - } - - }; - - var rAmp = /&/g, - rLt = //g, - rApos =/\'/g, - rQuot = /\"/g, - hChars =/[&<>\"\']/; - - - function coerceToString(val) { - return String((val === null || val === undefined) ? '' : val); - } - - function hoganEscape(str) { - str = coerceToString(str); - return hChars.test(str) ? - str - .replace(rAmp,'&') - .replace(rLt,'<') - .replace(rGt,'>') - .replace(rApos,''') - .replace(rQuot, '"') : - str; - } - - var isArray = Array.isArray || function(a) { - return Object.prototype.toString.call(a) === '[object Array]'; - }; - -})(typeof exports !== 'undefined' ? exports : Hogan); diff --git a/engine.js b/engine.js index 0766ceb..463ce4a 100644 --- a/engine.js +++ b/engine.js @@ -1,25 +1,25 @@ -// Hogan Template Engine wrapper for SocketStream 0.3 +// Handlebars Template Engine wrapper for SocketStream 0.3 var fs = require('fs'), path = require('path'), - hogan = require('hogan.js'); + Handlebars = require('handlebars'); exports.init = function(ss, config) { - // Send Hogan VM to the client - var clientCode = fs.readFileSync(path.join(__dirname, 'client.js'), 'utf8'); - ss.client.send('lib', 'hogan-template', clientCode); + // Send handlebars runtime to the client + var clientCode = fs.readFileSync(path.join(__dirname, 'handlebars.runtime.js'), 'utf8'); + ss.client.send('lib', 'handlebars-template', clientCode); return { - name: 'Hogan', + name: 'Handlebars', - // Opening code to use when a Hogan template is called for the first time + // Opening code to use when a Handlebars template is called for the first time prefix: function() { - return ''; }, @@ -30,15 +30,15 @@ exports.init = function(ss, config) { var compiledTemplate; try { - compiledTemplate = hogan.compile(template, {asString: true}); + compiledTemplate = Handlebars.precompile(template); } catch (e) { - var message = '! Error compiling the ' + path + ' template into Hogan'; + var message = '! Error compiling the ' + path + ' template into Handlebars'; console.log(String.prototype.hasOwnProperty('red') && message.red || message); throw new Error(e); compiledTemplate = '

Error

'; } - return 't[\'' + id + '\']=new ht(' + compiledTemplate + ');'; + return 't[\'' + id + '\']= ht(' + compiledTemplate + ');'; } } -} \ No newline at end of file +} diff --git a/handlebars.runtime.js b/handlebars.runtime.js new file mode 100644 index 0000000..0866641 --- /dev/null +++ b/handlebars.runtime.js @@ -0,0 +1,241 @@ +// lib/handlebars/base.js + +/*jshint eqnull:true*/ +this.Handlebars = {}; + +(function(Handlebars) { + +Handlebars.VERSION = "1.0.rc.1"; + +Handlebars.helpers = {}; +Handlebars.partials = {}; + +Handlebars.registerHelper = function(name, fn, inverse) { + if(inverse) { fn.not = inverse; } + this.helpers[name] = fn; +}; + +Handlebars.registerPartial = function(name, str) { + this.partials[name] = str; +}; + +Handlebars.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Error("Could not find property '" + arg + "'"); + } +}); + +var toString = Object.prototype.toString, functionType = "[object Function]"; + +Handlebars.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + + var ret = ""; + var type = toString.call(context); + + if(type === functionType) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if(type === "[object Array]") { + if(context.length > 0) { + return Handlebars.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + return fn(context); + } +}); + +Handlebars.K = function() {}; + +Handlebars.createFrame = Object.create || function(object) { + Handlebars.K.prototype = object; + var obj = new Handlebars.K(); + Handlebars.K.prototype = null; + return obj; +}; + +Handlebars.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var ret = "", data; + + if (options.data) { + data = Handlebars.createFrame(options.data); + } + + if(context && context.length > 0) { + for(var i=0, j=context.length; i": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var badChars = /[&<>"'`]/g; + var possible = /[&<>"'`]/; + + var escapeChar = function(chr) { + return escape[chr] || "&"; + }; + + Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, + + isEmpty: function(value) { + if (typeof value === "undefined") { + return true; + } else if (value === null) { + return true; + } else if (value === false) { + return true; + } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; + } + } + }; +})();; +// lib/handlebars/runtime.js +Handlebars.VM = { + template: function(templateSpec) { + // Just add water + var container = { + escapeExpression: Handlebars.Utils.escapeExpression, + invokePartial: Handlebars.VM.invokePartial, + programs: [], + program: function(i, fn, data) { + var programWrapper = this.programs[i]; + if(data) { + return Handlebars.VM.program(fn, data); + } else if(programWrapper) { + return programWrapper; + } else { + programWrapper = this.programs[i] = Handlebars.VM.program(fn); + return programWrapper; + } + }, + programWithDepth: Handlebars.VM.programWithDepth, + noop: Handlebars.VM.noop + }; + + return function(context, options) { + options = options || {}; + return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); + }; + }, + + programWithDepth: function(fn, data, $depth) { + var args = Array.prototype.slice.call(arguments, 2); + + return function(context, options) { + options = options || {}; + + return fn.apply(this, [context, options.data || data].concat(args)); + }; + }, + program: function(fn, data) { + return function(context, options) { + options = options || {}; + + return fn(context, options.data || data); + }; + }, + noop: function() { return ""; }, + invokePartial: function(partial, name, context, helpers, partials, data) { + var options = { helpers: helpers, partials: partials, data: data }; + + if(partial === undefined) { + throw new Handlebars.Exception("The partial " + name + " could not be found"); + } else if(partial instanceof Function) { + return partial(context, options); + } else if (!Handlebars.compile) { + throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); + } else { + partials[name] = Handlebars.compile(partial, {data: data !== undefined}); + return partials[name](context, options); + } + } +}; + +Handlebars.template = Handlebars.VM.template; +; diff --git a/package.json b/package.json index 292ec12..dcb6375 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,18 @@ { - "name": "ss-hogan", - "author": "Owen Barnes ", - "description": "Hogan template engine wrapper providing server-side compiled templates for SocketStream apps", - "version": "0.1.3", + "name": "ss-handlebars", + "author": "Yi Wang ", + "description": "Handlebars template engine wrapper providing server-side compiled templates for SocketStream apps", + "version": "0.1.0", "main": "./engine.js", "repository": { "type" : "git", - "url": "https://github.com/socketstream/ss-hogan.git" + "url": "https://github.com/yiwang/ss-handlebars.git" }, "engines": { "node": ">= 0.6.0" }, "dependencies": { - "hogan.js": "2.0.0" + "handlebars": "1.0.7" }, "devDependencies": {} }