From 90f656ea6272531bf92f9b11be251be475e6d953 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Wed, 11 Dec 2013 20:42:14 -0800 Subject: [PATCH] Move Spacebars runtime support into own file --- packages/spacebars/package.js | 1 + packages/spacebars/spacebars-runtime.js | 223 ++++++++++++++++++++++++ packages/spacebars/spacebars.js | 222 ----------------------- packages/templating/package.js | 2 +- packages/templating/templating_tests.js | 2 +- 5 files changed, 226 insertions(+), 224 deletions(-) create mode 100644 packages/spacebars/spacebars-runtime.js diff --git a/packages/spacebars/package.js b/packages/spacebars/package.js index 354eaec752..0ff9a0c490 100644 --- a/packages/spacebars/package.js +++ b/packages/spacebars/package.js @@ -18,6 +18,7 @@ Package.on_use(function (api) { api.use('jsparse'); api.use('ui'); api.use('minifiers', ['server']); + api.add_files(['spacebars-runtime.js']); api.add_files(['tojs.js', 'spacebars.js']); }); diff --git a/packages/spacebars/spacebars-runtime.js b/packages/spacebars/spacebars-runtime.js new file mode 100644 index 0000000000..6ad8233f87 --- /dev/null +++ b/packages/spacebars/spacebars-runtime.js @@ -0,0 +1,223 @@ +Spacebars = {}; + +// Returns true if `a` and `b` are `===`, unless they are of a mutable type. +// (Because then, they may be equal references to an object that was mutated, +// and we'll never know. We save only a reference to the old object; we don't +// do any deep-copying or diffing.) +var safeEquals = function (a, b) { + if (a !== b) + return false; + else + return ((!a) || (typeof a === 'number') || (typeof a === 'boolean') || + (typeof a === 'string')); +}; + +Spacebars.include = function (kindOrFunc, args) { + args = args || {}; + if (typeof kindOrFunc === 'function') { + // function block helper + var func = kindOrFunc; + + var hash = {}; + // Call arguments if they are functions. This may cause + // reactive dependencies! + for (var k in args) { + if (k !== 'data') { + var v = args[k]; + hash[k] = (typeof v === 'function' ? v() : v); + } + } + + var result; + if ('data' in args) { + var data = args.data; + data = (typeof data === 'function' ? data() : data); + result = func(data, { hash: hash }); + } else { + result = func({ hash: hash }); + } + // In `{{#foo}}...{{/foo}}`, if `foo` is a function that + // returns a component, attach __content and __elseContent + // to it. + if (UI.isComponent(result) && + (('__content' in args) || ('__elseContent' in args))) { + var extra = {}; + if ('__content' in args) + extra.__content = args.__content; + if ('__elseContent' in args) + extra.__elseContent = args.__elseContent; + result = result.extend(extra); + } + return result; + } else { + // Component + var kind = kindOrFunc; + if (! UI.isComponent(kind)) + throw new Error("Expected template, found: " + kind); + + // Note that there are no reactive dependencies established here. + if (args) { + var emboxedArgs = {}; + for (var k in args) { + if (k === '__content' || k === '__elseContent') + emboxedArgs[k] = args[k]; + else + emboxedArgs[k] = UI.emboxValue(args[k], safeEquals); + } + + return kind.extend(emboxedArgs); + } else { + return kind; + } + } +}; + + +// Executes `{{foo bar baz}}` when called on `(foo, bar, baz)`. +// If `bar` and `baz` are functions, they are called before +// `foo` is called on them. +// +// This is the shared part of Spacebars.mustache and +// Spacebars.attrMustache, which differ in how they post-process the +// result. +Spacebars.mustacheImpl = function (value/*, args*/) { + var args = arguments; + // if we have any arguments (pos or kw), add an options argument + // if there isn't one. + if (args.length > 1) { + var kw = args[args.length - 1]; + if (! (kw instanceof Spacebars.kw)) { + kw = Spacebars.kw(); + // clone arguments into an actual array, then push + // the empty kw object. + args = Array.prototype.slice.call(arguments); + args.push(kw); + } else { + // For each keyword arg, call it if it's a function + var newHash = {}; + for (var k in kw.hash) { + var v = kw.hash[k]; + newHash[k] = (typeof v === 'function' ? v() : v); + } + args[args.length - 1] = Spacebars.kw(newHash); + } + } + + return Spacebars.call.apply(null, args); +}; + +Spacebars.mustache = function (value/*, args*/) { + var result = Spacebars.mustacheImpl.apply(null, arguments); + + if (result instanceof Handlebars.SafeString) + return HTML.Raw(result.toString()); + else + // map `null` and `undefined` to "", stringify anything else + // (e.g. strings, booleans, numbers including 0). + return String(result == null ? '' : result); +}; + +Spacebars.attrMustache = function (value/*, args*/) { + var result = Spacebars.mustacheImpl.apply(null, arguments); + + if (result == null || result === '') { + return null; + } else if (typeof result === 'object') { + return result; + } else if (typeof result === 'string' && HTML.isValidAttributeName(result)) { + var obj = {}; + obj[result] = ''; + return obj; + } else { + throw new Error("Expected valid attribute name, '', null, or object"); + } +}; + +// Idempotently wrap in `HTML.Raw`. +// +// Called on the return value from `Spacebars.mustache` in case the +// template uses triple-stache (`{{{foo bar baz}}}`). +Spacebars.makeRaw = function (value) { + if (value instanceof HTML.Raw) + return value; + else + return HTML.Raw(value); +}; + +// If `value` is a function, called it on the `args`, after +// evaluating the args themselves (by calling them if they are +// functions). Otherwise, simply return `value` (and assert that +// there are no args). +Spacebars.call = function (value/*, args*/) { + if (typeof value === 'function') { + // evaluate arguments if they are functions (by calling them) + var newArgs = []; + for (var i = 1; i < arguments.length; i++) { + var arg = arguments[i]; + newArgs[i-1] = (typeof arg === 'function' ? arg() : arg); + } + + return value.apply(null, newArgs); + } else { + if (arguments.length > 1) + throw new Error("Can't call non-function: " + value); + + return value; + } +}; + +// Call this as `Spacebars.kw({ ... })`. The return value +// is `instanceof Spacebars.kw`. +Spacebars.kw = function (hash) { + if (! (this instanceof Spacebars.kw)) + return new Spacebars.kw(hash); + + this.hash = hash || {}; +}; + +// `Spacebars.dot(foo, "bar", "baz")` performs a special kind +// of `foo.bar.baz` that allows safe indexing of `null` and +// indexing of functions (which calls the function). If the +// result is a function, it is always a bound function (e.g. +// a wrapped version of `baz` that always uses `foo.bar` as +// `this`). +// +// In `Spacebars.dot(foo, "bar")`, `foo` is assumed to be either +// a non-function value or a "fully-bound" function wrapping a value, +// where fully-bound means it takes no arguments and ignores `this`. +// +// `Spacebars.dot(foo, "bar")` performs the following steps: +// +// * If `foo` is falsy, return `foo`. +// +// * If `foo` is a function, call it (set `foo` to `foo()`). +// +// * If `foo` is falsy now, return `foo`. +// +// * Return `foo.bar`, binding it to `foo` if it's a function. +Spacebars.dot = function (value, id1/*, id2, ...*/) { + if (arguments.length > 2) { + // Note: doing this recursively is probably less efficient than + // doing it in an iterative loop. + var argsForRecurse = []; + argsForRecurse.push(Spacebars.dot(value, id1)); + argsForRecurse.push.apply(argsForRecurse, + Array.prototype.slice.call(arguments, 2)); + return Spacebars.dot.apply(null, argsForRecurse); + } + + if (typeof value === 'function') + value = value(); + + if (! value) + return value; // falsy, don't index, pass through + + var result = value[id1]; + if (typeof result !== 'function') + return result; + // `value[id1]` (or `value()[id1]`) is a function. + // Bind it so that when called, `value` will be placed in `this`. + return function (/*arguments*/) { + return result.apply(value, arguments); + }; +}; diff --git a/packages/spacebars/spacebars.js b/packages/spacebars/spacebars.js index 10d15a801a..2710f7aab8 100644 --- a/packages/spacebars/spacebars.js +++ b/packages/spacebars/spacebars.js @@ -1,6 +1,4 @@ -Spacebars = {}; - var makeStacheTagStartRegex = function (r) { return new RegExp(r.source + /(?![{>!#/])/.source, r.ignoreCase ? 'i' : ''); @@ -286,52 +284,6 @@ var makeObjectLiteral = function (obj) { }; -// `Spacebars.dot(foo, "bar", "baz")` performs a special kind -// of `foo.bar.baz` that allows safe indexing of `null` and -// indexing of functions (which calls the function). If the -// result is a function, it is always a bound function (e.g. -// a wrapped version of `baz` that always uses `foo.bar` as -// `this`). -// -// In `Spacebars.dot(foo, "bar")`, `foo` is assumed to be either -// a non-function value or a "fully-bound" function wrapping a value, -// where fully-bound means it takes no arguments and ignores `this`. -// -// `Spacebars.dot(foo, "bar")` performs the following steps: -// -// * If `foo` is falsy, return `foo`. -// -// * If `foo` is a function, call it (set `foo` to `foo()`). -// -// * If `foo` is falsy now, return `foo`. -// -// * Return `foo.bar`, binding it to `foo` if it's a function. -Spacebars.dot = function (value, id1/*, id2, ...*/) { - if (arguments.length > 2) { - // Note: doing this recursively is probably less efficient than - // doing it in an iterative loop. - var argsForRecurse = []; - argsForRecurse.push(Spacebars.dot(value, id1)); - argsForRecurse.push.apply(argsForRecurse, - Array.prototype.slice.call(arguments, 2)); - return Spacebars.dot.apply(null, argsForRecurse); - } - - if (typeof value === 'function') - value = value(); - - if (! value) - return value; // falsy, don't index, pass through - - var result = value[id1]; - if (typeof result !== 'function') - return result; - // `value[id1]` (or `value()[id1]`) is a function. - // Bind it so that when called, `value` will be placed in `this`. - return function (/*arguments*/) { - return result.apply(value, arguments); - }; -}; ////////////////////////////////////////////////// @@ -691,78 +643,6 @@ var codeGenInclusionArgs = function (tag) { return []; }; -// Returns true if `a` and `b` are `===`, unless they are of a mutable type. -// (Because then, they may be equal references to an object that was mutated, -// and we'll never know. We save only a reference to the old object; we don't -// do any deep-copying or diffing.) -var safeEquals = function (a, b) { - if (a !== b) - return false; - else - return ((!a) || (typeof a === 'number') || (typeof a === 'boolean') || - (typeof a === 'string')); -}; - -Spacebars.include = function (kindOrFunc, args) { - args = args || {}; - if (typeof kindOrFunc === 'function') { - // function block helper - var func = kindOrFunc; - - var hash = {}; - // Call arguments if they are functions. This may cause - // reactive dependencies! - for (var k in args) { - if (k !== 'data') { - var v = args[k]; - hash[k] = (typeof v === 'function' ? v() : v); - } - } - - var result; - if ('data' in args) { - var data = args.data; - data = (typeof data === 'function' ? data() : data); - result = func(data, { hash: hash }); - } else { - result = func({ hash: hash }); - } - // In `{{#foo}}...{{/foo}}`, if `foo` is a function that - // returns a component, attach __content and __elseContent - // to it. - if (UI.isComponent(result) && - (('__content' in args) || ('__elseContent' in args))) { - var extra = {}; - if ('__content' in args) - extra.__content = args.__content; - if ('__elseContent' in args) - extra.__elseContent = args.__elseContent; - result = result.extend(extra); - } - return result; - } else { - // Component - var kind = kindOrFunc; - if (! UI.isComponent(kind)) - throw new Error("Expected template, found: " + kind); - - // Note that there are no reactive dependencies established here. - if (args) { - var emboxedArgs = {}; - for (var k in args) { - if (k === '__content' || k === '__elseContent') - emboxedArgs[k] = args[k]; - else - emboxedArgs[k] = UI.emboxValue(args[k], safeEquals); - } - - return kind.extend(emboxedArgs); - } else { - return kind; - } - } -}; - // Input: Attribute dictionary, or null. Attribute values may have `Special` // nodes representing template tags. In addition, the synthetic attribute // `$specials` may be present and contain an array of `Special` nodes @@ -828,99 +708,6 @@ Spacebars._handleSpecialAttributes = function (oldAttrs) { return newAttrs; }; -// Executes `{{foo bar baz}}` when called on `(foo, bar, baz)`. -// If `bar` and `baz` are functions, they are called before -// `foo` is called on them. -// -// This is the shared part of Spacebars.mustache and -// Spacebars.attrMustache, which differ in how they post-process the -// result. -Spacebars.mustacheImpl = function (value/*, args*/) { - var args = arguments; - // if we have any arguments (pos or kw), add an options argument - // if there isn't one. - if (args.length > 1) { - var kw = args[args.length - 1]; - if (! (kw instanceof Spacebars.kw)) { - kw = Spacebars.kw(); - // clone arguments into an actual array, then push - // the empty kw object. - args = Array.prototype.slice.call(arguments); - args.push(kw); - } else { - // For each keyword arg, call it if it's a function - var newHash = {}; - for (var k in kw.hash) { - var v = kw.hash[k]; - newHash[k] = (typeof v === 'function' ? v() : v); - } - args[args.length - 1] = Spacebars.kw(newHash); - } - } - - return Spacebars.call.apply(null, args); -}; - -Spacebars.mustache = function (value/*, args*/) { - var result = Spacebars.mustacheImpl.apply(null, arguments); - - if (result instanceof Handlebars.SafeString) - return HTML.Raw(result.toString()); - else - // map `null` and `undefined` to "", stringify anything else - // (e.g. strings, booleans, numbers including 0). - return String(result == null ? '' : result); -}; - -Spacebars.attrMustache = function (value/*, args*/) { - var result = Spacebars.mustacheImpl.apply(null, arguments); - - if (result == null || result === '') { - return null; - } else if (typeof result === 'object') { - return result; - } else if (typeof result === 'string' && HTML.isValidAttributeName(result)) { - var obj = {}; - obj[result] = ''; - return obj; - } else { - throw new Error("Expected valid attribute name, '', null, or object"); - } -}; - -// Idempotently wrap in `HTML.Raw`. -// -// Called on the return value from `Spacebars.mustache` in case the -// template uses triple-stache (`{{{foo bar baz}}}`). -Spacebars.makeRaw = function (value) { - if (value instanceof HTML.Raw) - return value; - else - return HTML.Raw(value); -}; - -// If `value` is a function, called it on the `args`, after -// evaluating the args themselves (by calling them if they are -// functions). Otherwise, simply return `value` (and assert that -// there are no args). -Spacebars.call = function (value/*, args*/) { - if (typeof value === 'function') { - // evaluate arguments if they are functions (by calling them) - var newArgs = []; - for (var i = 1; i < arguments.length; i++) { - var arg = arguments[i]; - newArgs[i-1] = (typeof arg === 'function' ? arg() : arg); - } - - return value.apply(null, newArgs); - } else { - if (arguments.length > 1) - throw new Error("Can't call non-function: " + value); - - return value; - } -}; - var codeGenMustache = function (tag, mustacheType) { var nameCode = codeGenPath(tag.path); var argCode = codeGenArgs(tag.args); @@ -1052,12 +839,3 @@ var codeGenArgs = function (tagArgs) { return args; }; - -// Call this as `Spacebars.kw({ ... })`. The return value -// is `instanceof Spacebars.kw`. -Spacebars.kw = function (hash) { - if (! (this instanceof Spacebars.kw)) - return new Spacebars.kw(hash); - - this.hash = hash || {}; -}; \ No newline at end of file diff --git a/packages/templating/package.js b/packages/templating/package.js index d26089a3da..acdace199e 100644 --- a/packages/templating/package.js +++ b/packages/templating/package.js @@ -37,7 +37,7 @@ Package.on_test(function (api) { api.use('htmljs'); api.use('templating'); api.use('underscore'); - api.use(['test-helpers', 'domutils', 'session', 'deps', + api.use(['test-helpers', 'session', 'deps', 'minimongo'], 'client'); api.add_files([ diff --git a/packages/templating/templating_tests.js b/packages/templating/templating_tests.js index b74574ec73..7530d4a6c9 100644 --- a/packages/templating/templating_tests.js +++ b/packages/templating/templating_tests.js @@ -397,7 +397,7 @@ Tinytest.add("templating - template arg", function (test) { var div = renderToDiv(Template.test_template_arg_a.withData({food: "pie"})); var cleanupDiv = addToBody(div); test.equal($(div).text(), "Greetings 1-bold Line"); - clickElement(DomUtils.find(div, 'i')); + clickElement(div.querySelector('i')); test.equal($(div).text(), "Hello 3-element World (the secret is strawberry pie)"); cleanupDiv();