UNPKG

todomvc

Version:

> Helping you select an MV\* framework

1,396 lines (1,349 loc) 70.6 kB
/*! * 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/scope", "can/view", "can/view/scanner", "can/compute", "can/view/render"], function( can ){ // # mustache.js // `can.Mustache`: The Mustache templating engine. // // See the [Transformation](#section-29) section within *Scanning Helpers* for a detailed explanation // of the runtime render code design. The majority of the Mustache engine implementation // occurs within the *Transformation* scanning helper. // ## Initialization // // Define the view extension. can.view.ext = ".mustache"; // ### Setup internal helper variables and functions. // // An alias for the context variable used for tracking a stack of contexts. // This is also used for passing to helper functions to maintain proper context. var SCOPE = 'scope', // An alias for the variable used for the hash object that can be passed // to helpers via `options.hash`. HASH = '___h4sh', // An alias for the most used context stacking call. CONTEXT_OBJ = '{scope:' + SCOPE + ',options:options}', // argument names used to start the function (used by scanner and steal) ARG_NAMES = SCOPE+",options", // matches arguments inside a {{ }} argumentsRegExp = /((([^\s]+?=)?('.*?'|".*?"))|.*?)\s/g, // matches a literal number, string, null or regexp literalNumberStringBooleanRegExp = /^(('.*?'|".*?"|[0-9]+\.?[0-9]*|true|false|null|undefined)|((.+?)=(('.*?'|".*?"|[0-9]+\.?[0-9]*|true|false)|(.+))))$/, // returns an object literal that we can use to look up a value in the current scope makeLookupLiteral = function(type){ return '{get:"'+type.replace(/"/g,'\\"')+'"}' }, // returns if the object is a lookup isLookup = function(obj){ return obj && typeof obj.get == "string" }, /* * Checks whether an object is like a can.Map. This takes into * fact that can.route is can.Map like. * @param {[can.Map]} observable * @return {Boolean} returns if the object is observable like. */ isObserveLike = function(obj) { return obj instanceof can.Map || (obj && !!obj._get); }, /* * Tries to determine if the object passed is an array. * @param {Array} obj The object to check. * @return {Boolean} returns if the object is an array. */ isArrayLike = function(obj) { return obj && obj.splice && typeof obj.length == 'number'; }, // used to make sure .fn and .inverse are always called with a Scope like object makeConvertToScopes = function(orignal, scope, options){ return function(updatedScope, updatedOptions){ if(updatedScope !== undefined && !(updatedScope instanceof can.view.Scope)){ updatedScope = scope.add(updatedScope) } if(updatedOptions !== undefined && !(updatedOptions instanceof OptionsScope)){ updatedOptions = options.add(updatedOptions) } return orignal(updatedScope, updatedOptions || options) } }; // ## Mustache /** * @hide * The Mustache templating engine. * @param {Object} options Configuration options */ Mustache = function(options, helpers) { // Support calling Mustache without the constructor. // This returns a function that renders the template. if ( this.constructor != Mustache ) { var mustache = new Mustache(options); return function(data,options) { return mustache.render(data,options); }; } // 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. can.extend(this, options); this.template = this.scanner.scan(this.text, this.name); }; /** * @add can.Mustache */ // Put Mustache on the `can` object. can.Mustache = window.Mustache = Mustache; /** * @prototype */ Mustache.prototype. /** * @function can.Mustache.prototype.render render * @parent can.Mustache.prototype * @signature `mustache.render( data [, helpers] )` * @param {Object} data Data to interpolate into the template. * @return {String} The template with interpolated data, in string form. * @hide * * @body * Renders an object with view helpers attached to the view. * * new Mustache({text: "<%= message %>"}).render({ * message: "foo" * }) */ render = function( data, options ) { if(!(data instanceof can.view.Scope)){ data = new can.view.Scope(data || {}); } if( ! (options instanceof OptionsScope) ){ options = new OptionsScope(options || {}) } options = options || {}; return this.template.fn.call(data, data, options); }; can.extend(Mustache.prototype, { // Share a singleton scanner for parsing templates. scanner: new can.view.Scanner({ // A hash of strings for the scanner to inject at certain points. text: { // This is the logic to inject at the beginning of a rendered template. // This includes initializing the `context` stack. start: "",//"var "+SCOPE+"= this instanceof can.view.Scope? this : new can.view.Scope(this);\n", scope: SCOPE, options: ",options: options", argNames: ARG_NAMES }, // An ordered token registry for the scanner. // This needs to be ordered by priority to prevent token parsing errors. // Each token follows the following structure: // // [ // // Which key in the token map to match. // "tokenMapName", // // // A simple token to match, like "{{". // "token", // // // Optional. A complex (regexp) token to match that // // overrides the simple token. // "[\\s\\t]*{{", // // // Optional. A function that executes advanced // // manipulation of the matched content. This is // // rarely used. // function(content){ // return content; // } // ] tokens: [ /** * @function can.Mustache.tags.escaped {{key}} * * @description Insert the value of the [can.Mustache.key key] into the * output of the template. * * @parent can.Mustache.tags 0 * * @signature `{{key}}` * * @param {can.Mustache.key} key A key that references one of the following: * * - A [can.Mustache.registerHelper registered helper]. * - A value within the current or parent * [can.Mustache.context context]. If the value is a function or [can.compute], the * function's return value is used. * * @return {String|Function|*} * * After the key's value is found (and set to any function's return value), * it is passed to [can.view.txt] as the result of a call to its `func` * argument. There, if the value is a: * * - `null` or `undefined` - an empty string is inserted into the rendered template result. * - `String` or `Number` - the value is inserted into the rendered template result. * - `Function` - A [can.view.hook hookup] attribute or element is inserted so this function * will be called back with the DOM element after it is created. * * @body * * ## Use * * `{{key}}` insert data into the template. It most commonly references * values within the current [can.Mustache.context context]. For example: * * Rendering: * * <h1>{{name}}</h1> * * With: * * {name: "Austin"} * * Results in: * * <h1>Austin</h1> * * If the key value is a String or Number, it is inserted into the template. * If it is `null` or `undefined`, nothing is added to the template. * * * ## Nested Properties * * Mustache supports nested paths, making it possible to * look up properties nested deep inside the current context. For example: * * Rendering: * * <h1>{{book.author}}</h1> * * With: * * { * book: { * author: "Ernest Hemingway" * } * } * * Results in: * * <h1>Ernest Hemingway</h1> * * ## Looking up values in parent contexts * * Sections and block helpers can create their own contexts. If a key's value * is not found in the current context, it will look up the key's value * in parent contexts. For example: * * Rendering: * * {{#chapters}} * <li>{{title}} - {{name}}</li> * {{chapters}} * * With: * * { * title: "The Book of Bitovi" * chapters: [{name: "Breakdown"}] * } * * Results in: * * <li>The Book of Bitovi - Breakdown</li> * * */ // Return unescaped ["returnLeft", "{{{", "{{[{&]"], // Full line comments ["commentFull", "{{!}}", "^[\\s\\t]*{{!.+?}}\\n"], /** * @function can.Mustache.tags.comment {{!key}} * * @parent can.Mustache.tags 7 * * @description A comment that doesn't get inserted into the rendered result. * * @signature `{{!key}}` * * The comment tag operates similarly to a `<!-- -->` tag in HTML. It exists in your template but never shows up. * * @param {can.Mustache.key} key Everything within this tag is completely ignored. * @return {String} * */ // Inline comments ["commentLeft", "{{!", "(\\n[\\s\\t]*{{!|{{!)"], /** * @function can.Mustache.tags.unescaped {{{key}}} * * @parent can.Mustache.tags 1 * * @description Insert the unescaped value of the [can.Mustache.key key] into the * output of the template. * * @signature `{{{key}}}` * * Behaves just like [can.Mustache.tags.escaped {{key}}] and [can.Mustache.helpers.helper {{helper}}] but does not * escape the result. * * @param {can.Mustache.key} key A key that references a value within the current or parent * context. If the value is a function or can.compute, the function's return value is used. * @return {String|Function|*} * * */ // /** * @function can.Mustache.tags.unescaped2 {{&key}} * * @parent can.Mustache.tags 2 * * @description Insert the unescaped value of the [can.Mustache.key key] into the * output of the template. * * @signature `{{&key}}` * * The `{{&key}}` tag is an alias for [can.Mustache.tags.unescaped {{{key}}}], behaving just * like [can.Mustache.tags.escaped {{key}}] and [can.Mustache.helpers.helper {{helper}}] but does not * escape the result. * * @param {can.Mustache.key} key A key that references a value within the current or parent * context. If the value is a function or can.compute, the function's return value is used. * @return {String|Function|*} * */ // Full line escapes // This is used for detecting lines with only whitespace and an escaped tag ["escapeFull", "{{}}", "(^[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}\\n|\\n[\\s\\t]*{{[#/^][^}]+?}}$)", function(content) { return { before: /^\n.+?\n$/.test(content) ? '\n' : '', content: content.match(/\{\{(.+?)\}\}/)[1] || '' }; }], // Return escaped ["escapeLeft", "{{"], // Close return unescaped ["returnRight", "}}}"], // Close tag ["right", "}}"] ], // ## Scanning Helpers // // This is an array of helpers that transform content that is within escaped tags like `{{token}}`. These helpers are solely for the scanning phase; they are unrelated to Mustache/Handlebars helpers which execute at render time. Each helper has a definition like the following: // // { // // The content pattern to match in order to execute. // // Only the first matching helper is executed. // name: /pattern to match/, // // // The function to transform the content with. // // @param {String} content The content to transform. // // @param {Object} cmd Scanner helper data. // // { // // insert: "insert command", // // tagName: "div", // // status: 0 // // } // fn: function(content, cmd) { // return 'for text injection' || // { raw: 'to bypass text injection' }; // } // } helpers: [ // ### Partials // // Partials begin with a greater than sign, like {{> box}}. // // Partials are rendered at runtime (as opposed to compile time), // so recursive partials are possible. Just avoid infinite loops. // // For example, this template and partial: // // base.mustache: // <h2>Names</h2> // {{#names}} // {{> user}} // {{/names}} // // user.mustache: // <strong>{{name}}</strong> { name: /^>[\s]*\w*/, fn:function(content, cmd){ // Get the template name and call back into the render method, // passing the name and the current context. var templateName = can.trim(content.replace(/^>\s?/, '')).replace(/["|']/g, ""); return "can.Mustache.renderPartial('"+templateName+"'," + ARG_NAMES + ")"; } }, // ### Data Hookup // // This will attach the data property of `this` to the element // its found on using the first argument as the data attribute // key. // // For example: // // <li id="nameli" {{ data 'name' }}></li> // // then later you can access it like: // // can.$('#nameli').data('name'); /** * @function can.Mustache.helpers.data {{data name}} * @parent can.Mustache.htags 7 * @signature `{{data name}}` * * Adds the current [can.Mustache.context context] to the * element's [can.data]. * * @param {String} name The name of the data attribute to use for the * context. * * @body * * ## Use * * It is common for you to want some data in the template to be available * on an element. `{{data name}}` allows you to save the * context so it can later be retrieved by [can.data] or * `$.fn.data`. For example, * * The template: * * <ul> * <li id="person" {{data 'person'}}>{{name}}</li> * </ul> * * Rendered with: * * document.body.appendChild( * can.view.mustache(template,{ person: { name: 'Austin' } }); * * Retrieve the person data back with: * * $("#person").data("person") * */ { name: /^\s*data\s/, fn: function(content, cmd){ var attr = content.match(/["|'](.*)["|']/)[1]; // return a function which calls `can.data` on the element // with the attribute name with the current context. return "can.proxy(function(__){" + // "var context = this[this.length-1];" + // "context = context." + STACKED + " ? context[context.length-2] : context; console.warn(this, context);" + "can.data(can.$(__),'" + attr + "', this.attr('.')); }, " + SCOPE + ")"; } }, { name:/\s*\(([\$\w]+)\)\s*->([^\n]*)/, fn: function(content){ var quickFunc = /\s*\(([\$\w]+)\)\s*->([^\n]*)/, parts = content.match(quickFunc); //find return "can.proxy(function(__){var " + parts[1] + "=can.$(__);with("+SCOPE+".attr('.')){" + parts[2] + "}}, this);"; } }, // ### Transformation (default) // // This transforms all content to its interpolated equivalent, // including calls to the corresponding helpers as applicable. // This outputs the render code for almost all cases. // // #### Definitions // // * `context` - This is the object that the current rendering context operates within. // Each nested template adds a new `context` to the context stack. // * `stack` - Mustache supports nested sections, // each of which add their own context to a stack of contexts. // Whenever a token gets interpolated, it will check for a match against the // last context in the stack, then iterate through the rest of the stack checking for matches. // The first match is the one that gets returned. // * `Mustache.txt` - This serializes a collection of logic, optionally contained within a section. // If this is a simple interpolation, only the interpolation lookup will be passed. // If this is a section, then an `options` object populated by the truthy (`options.fn`) and // falsey (`options.inverse`) encapsulated functions will also be passed. This section handling // exists to support the runtime context nesting that Mustache supports. // * `Mustache.get` - This resolves an interpolation reference given a stack of contexts. // * `options` - An object containing methods for executing the inner contents of sections or helpers. // `options.fn` - Contains the inner template logic for a truthy section. // `options.inverse` - Contains the inner template logic for a falsey section. // `options.hash` - Contains the merged hash object argument for custom helpers. // // #### Design // // This covers the design of the render code that the transformation helper generates. // // ##### Pseudocode // // A detailed explanation is provided in the following sections, but here is some brief pseudocode // that gives a high level overview of what the generated render code does (with a template similar to // `"{{#a}}{{b.c.d.e.name}}{{/a}}" == "Phil"`). // // *Initialize the render code.* // // view = [] // context = [] // stack = fn { context.concat([this]) } // // *Render the root section.* // // view.push( "string" ) // view.push( can.view.txt( // // *Render the nested section with `can.Mustache.txt`.* // // txt( // // *Add the current context to the stack.* // // stack(), // // *Flag this for truthy section mode.* // // "#", // // *Interpolate and check the `a` variable for truthyness using the stack with `can.Mustache.get`.* // // get( "a", stack() ), // // *Include the nested section's inner logic. // The stack argument is usually the parent section's copy of the stack, // but it can be an override context that was passed by a custom helper. // Sections can nest `0..n` times -- **NESTCEPTION**.* // // { fn: fn(stack) { // // *Render the nested section (everything between the `{{#a}}` and `{{/a}}` tokens).* // // view = [] // view.push( "string" ) // view.push( // // *Add the current context to the stack.* // // stack(), // // *Flag this as interpolation-only mode.* // // null, // // *Interpolate the `b.c.d.e.name` variable using the stack.* // // get( "b.c.d.e.name", stack() ), // ) // view.push( "string" ) // // *Return the result for the nested section.* // // return view.join() // }} // ) // )) // view.push( "string" ) // // *Return the result for the root section, which includes all nested sections.* // // return view.join() // // ##### Initialization // // Each rendered template is started with the following initialization code: // // var ___v1ew = []; // var ___c0nt3xt = []; // ___c0nt3xt.__sc0pe = true; // var __sc0pe = function(context, self) { // var s; // if (arguments.length == 1 && context) { // s = !context.__sc0pe ? [context] : context; // } else { // s = context && context.__sc0pe // ? context.concat([self]) // : __sc0pe(context).concat([self]); // } // return (s.__sc0pe = true) && s; // }; // // The `___v1ew` is the the array used to serialize the view. // The `___c0nt3xt` is a stacking array of contexts that slices and expands with each nested section. // The `__sc0pe` function is used to more easily update the context stack in certain situations. // Usually, the stack function simply adds a new context (`self`/`this`) to a context stack. // However, custom helpers will occasionally pass override contexts that need their own context stack. // // ##### Sections // // Each section, `{{#section}} content {{/section}}`, within a Mustache template generates a section // context in the resulting render code. The template itself is treated like a root section, with the // same execution logic as any others. Each section can have `0..n` nested sections within it. // // Here's an example of a template without any descendent sections. // Given the template: `"{{a.b.c.d.e.name}}" == "Phil"` // Would output the following render code: // // ___v1ew.push("\""); // ___v1ew.push(can.view.txt(1, '', 0, this, function() { // return can.Mustache.txt(__sc0pe(___c0nt3xt, this), null, // can.Mustache.get("a.b.c.d.e.name", // __sc0pe(___c0nt3xt, this)) // ); // })); // ___v1ew.push("\" == \"Phil\""); // // The simple strings will get appended to the view. Any interpolated references (like `{{a.b.c.d.e.name}}`) // will be pushed onto the view via `can.view.txt` in order to support live binding. // The function passed to `can.view.txt` will call `can.Mustache.txt`, which serializes the object data by doing // a context lookup with `can.Mustache.get`. // // `can.Mustache.txt`'s first argument is a copy of the context stack with the local context `this` added to it. // This stack will grow larger as sections nest. // // The second argument is for the section type. This will be `"#"` for truthy sections, `"^"` for falsey, // or `null` if it is an interpolation instead of a section. // // The third argument is the interpolated value retrieved with `can.Mustache.get`, which will perform the // context lookup and return the approriate string or object. // // Any additional arguments, if they exist, are used for passing arguments to custom helpers. // // For nested sections, the last argument is an `options` object that contains the nested section's logic. // // Here's an example of a template with a single nested section. // Given the template: `"{{#a}}{{b.c.d.e.name}}{{/a}}" == "Phil"` // Would output the following render code: // // ___v1ew.push("\""); // ___v1ew.push(can.view.txt(0, '', 0, this, function() { // return can.Mustache.txt(__sc0pe(___c0nt3xt, this), "#", // can.Mustache.get("a", __sc0pe(___c0nt3xt, this)), // [{ // _: function() { // return ___v1ew.join(""); // } // }, { // fn: function(___c0nt3xt) { // var ___v1ew = []; // ___v1ew.push(can.view.txt(1, '', 0, this, // function() { // return can.Mustache.txt( // __sc0pe(___c0nt3xt, this), // null, // can.Mustache.get("b.c.d.e.name", // __sc0pe(___c0nt3xt, this)) // ); // } // )); // return ___v1ew.join(""); // } // }] // ) // })); // ___v1ew.push("\" == \"Phil\""); // // This is specified as a truthy section via the `"#"` argument. The last argument includes an array of helper methods used with `options`. // These act similarly to custom helpers: `options.fn` will be called for truthy sections, `options.inverse` will be called for falsey sections. // The `options._` function only exists as a dummy function to make generating the section nesting easier (a section may have a `fn`, `inverse`, // or both, but there isn't any way to determine that at compilation time). // // Within the `fn` function is the section's render context, which in this case will render anything between the `{{#a}}` and `{{/a}}` tokens. // This function has `___c0nt3xt` as an argument because custom helpers can pass their own override contexts. For any case where custom helpers // aren't used, `___c0nt3xt` will be equivalent to the `__sc0pe(___c0nt3xt, this)` stack created by its parent section. The `inverse` function // works similarly, except that it is added when `{{^a}}` and `{{else}}` are used. `var ___v1ew = []` is specified in `fn` and `inverse` to // ensure that live binding in nested sections works properly. // // All of these nested sections will combine to return a compiled string that functions similar to EJS in its uses of `can.view.txt`. // // #### Implementation { name: /^.*$/, fn: function(content, cmd) { var mode = false, result = []; // Trim the content so we don't have any trailing whitespace. content = can.trim(content); // Determine what the active mode is. // // * `#` - Truthy section // * `^` - Falsey section // * `/` - Close the prior section // * `else` - Inverted section (only exists within a truthy/falsey section) if (content.length && (mode = content.match(/^([#^/]|else$)/))) { mode = mode[0]; switch (mode) { /** * @function can.Mustache.helpers.section {{#key}} * @parent can.Mustache.tags 3 * * @signature `{{#key}}BLOCK{{/key}}` * * Render blocks of text one or more times, depending * on the value of the key in the current context. * * @param {can.Mustache.key} key A key that references a value within the current or parent * [can.Mustache.context context]. If the value is a function or [can.compute], the * function's return value is used. * * * @return {String} * * Depending on the value's type, the following actions happen: * * - `Array` or [can.List] - the block is rendered for * each item in the array. The [can.Mustache.context context] is set to * the item within each block rendering. * - A `truthy` value - the block is rendered with the [can.Mustache.context context] * set to the value. * - A `falsey` value - the block is not rendered. * * The rendered result of the blocks, block or an empty string is returned. * * @body * * Sections contain text blocks and evaluate whether to render it or not. If * the object evaluates to an array it will iterate over it and render the block * for each item in the array. There are four different types of sections. * * ## Falseys or Empty Arrays * * If the value returns a `false`, `undefined`, `null`, `""` or `[]` we consider * that a *falsey* value. * * If the value is falsey, the section will **NOT** render the block. * * { * friends: false * } * * {{#friends}} * Never shown! * {{/friends}} * * * ## Arrays * * If the value is a non-empty array, sections will iterate over the * array of items, rendering the items in the block. * * For example, a list of friends will iterate * over each of those items within a section. * * { * friends: [ * { name: "Austin" }, * { name: "Justin" } * ] * } * * <ul> * {{#friends}} * <li>{{name}}</li> * {{/friends}} * </ul> * * would render: * * <ul> * <li>Austin</li> * <li>Justin</li> * </ul> * * Reminder: Sections will reset the current context to the value for which it is iterating. * See the [basics of contexts](#Basics) for more information. * * ## Truthys * * When the value is a non-falsey object but not a list, it is considered truthy and will be used * as the context for a single rendering of the block. * * { * friends: { name: "Jon" } * } * * {{#friends}} * Hi {{name}} * {{/friends}} * * would render: * * Hi Jon! */ // /** * @function can.Mustache.helpers.helper {{helper args hashes}} * @parent can.Mustache.htags 0 * * @description Calls a mustache helper function and inserts its return value into * the rendered template. * * @signature `{{helper [args...] [hashProperty=hashValue...]}}` * * Calls a mustache helper function or a function. For example: * * The template: * * <p>{{madLib "Lebron James" verb 4 foo="bar"}}</p> * * Rendered with: * * {verb: "swept"} * * Will call a `madLib` helper with the following arguements: * * can.Mustache.registerHelper('madLib', * function(subject, verb, number, options){ * // subject -> "Lebron James" * // verb -> "swept" * // number -> 4 * // options.hash.foo -> "bar" * }); * * @param {can.Mustache.key} helper A key that finds a [can.Mustache.helper helper function] * that is either [can.Mustache.registerHelper registered] or found within the * current or parent [can.Mustache.context context]. * * @param {...can.Mustache.key|String|Number} [args] Space seperated arguments * that get passed to the helper function as arguments. If the key's value is a: * * - [can.Map] - A getter/setter [can.compute] is passed. * - [can.compute] - The can.compute is passed. * - `function` - The function's return value is passed. * * @param {String} hashProperty * * A property name that gets added to a [can.Mustache.helperOptions helper options]'s * hash object. * * @param {...can.Mustache.key|String|Number} hashValue A value that gets * set as a property value of the [can.Mustache.helperOptions helper option argument]'s * hash object. * * @body * * ## Use * * The `{{helper}}` syntax is used to call out to Mustache [can.Mustache.helper helper functions] functions * that may contain more complex functionality. `helper` is a [can.Mustache.key key] that must match either: * * - a [can.Mustache.registerHelper registered helper function], or * - a function in the current or parent [can.Mustache.context contexts] * * The following example shows both cases. * * The Template: * * <p>{{greeting}} {{user}}</p> * * Rendered with data: * * { * user: function(){ return "Justin" } * } * * And a with a registered helper like: * * can.Mustache.registerHelper('greeting', function(){ * return "Hello" * }); * * Results in: * * <p>Hello Justin</p> * * ## Arguments * * Arguments can be passed from the template to helper function by * listing space seperated strings, numbers or other [can.Mustache.key keys] after the * `helper` name. For example: * * The template: * * <p>{{madLib "Lebron James" verb 4}}</p> * * Rendered with: * * {verb: "swept"} * * Will call a `madLib` helper with the following arguements: * * can.Mustache.registerHelper('madLib', * function(subject, verb, number, options){ * // subject -> "Lebron James" * // verb -> "swept" * // number -> 4 * }); * * If an argument `key` value is a [can.Map] property, the Observe's * property is converted to a getter/setter [can.compute]. For example: * * The template: * * <p>What! My name is: {{mr user.name}}</p> * * Rendered with: * * {user: new can.Map({name: "Slim Shady"})} * * Needs the helper to check if name is a function or not: * * can.Mustache.registerHelper('mr',function(name){ * return "Mr. "+ (typeof name === "function" ? * name(): * name) * }) * * This behavior enables two way binding helpers and is explained in more detail * on the [can.Mustache.helper helper functions] docs. * * ## Hash * * If enumerated arguments isn't an appropriate way to configure the behavior * of a helper, it's possible to pass a hash of key-value pairs to the * [can.Mustache.helperOptions helper option argument]'s * hash object. Properties and values are specified * as `hashProperty=hashValue`. For example: * * The template: * * <p>My {{excuse who=pet how="shreded"}}</p> * ` * And the helper: * * can.Mustache.registerHelper("excuse",function(options){ * return ["My", * options.hash.who || "dog". * options.hash.how || "ate", * "my", * options.hash.what || "homework"].join(" ") * }) * * Render with: * * {pet: "cat"} * * Results in: * * <p>My cat shareded my homework</p> * * ## Returning an element callback function * * If a helper returns a function, that function is called back after * the template has been rendered into DOM elements. This can * be used to create mustache tags that have rich behavior. Read about it * on the [can.Mustache.helper helper function] page. * */ // /** * @function can.Mustache.helpers.sectionHelper {{#helper args hashes}} * @parent can.Mustache.htags 1 * * Calls a mustache helper function with a block, and optional inverse * block. * * @signature `{{#helper [args...] [hashName=hashValue...]}}BLOCK{{/helper}}` * * Calls a mustache helper function or a function with a block to * render. * * The template: * * <p>{{countTo number}}{{num}}{{/countTo}}</p> * * Rendered with: * * {number: 5} * * Will call the `countTo` helper: * * can.Mustache.registerHelper('madLib', * function(number, options){ * var out = [] * for(var i =0; i < number; i++){ * out.push( options.fn({num: i+1}) ) * } * return out.join(" ") * }); * * Results in: * * <p>1 2 3 4 5</p> * * @param {can.Mustache.key} helper A key that finds a [can.Mustache.helper helper function] * that is either [can.Mustache.registerHelper registered] or found within the * current or parent [can.Mustache.context context]. * * @param {...can.Mustache.key|String|Number} [args] Space seperated arguments * that get passed to the helper function as arguments. If the key's value is a: * * - [can.Map] - A getter/setter [can.compute] is passed. * - [can.compute] - The can.compute is passed. * - `function` - The function's return value is passed. * * @param {String} hashProperty * * A property name that gets added to a [can.Mustache.helperOptions helper options]'s * hash object. * * @param {...can.Mustache.key|String|Number} hashValue A value that gets * set as a property value of the [can.Mustache.helperOptions helper option argument]'s * hash object. * * @param {mustache} BLOCK A mustache template that gets compiled and * passed to the helper function as the [can.Mustache.helperOptions options argument's] `fn` * property. * * * @signature `{{#helper [args...] [hashName=hashValue...]}}BLOCK{{else}}INVERSE{{/helper}}` * * Calls a mustache helper function or a function with a `fn` and `inverse` block to * render. * * The template: * * <p>The bed is * {{isJustRight firmness}} * pefect! * {{else}} * uncomfortable. * {{/justRight}}</p> * * Rendered with: * * {firmness: 45} * * Will call the `isJustRight` helper: * * can.Mustache.registerHelper('isJustRight', * function(number, options){ * if(number > 50){ * return options.fn(this) * } else { * return options.inverse(this) * } * return out.join(" ") * }); * * Results in: * * <p>The bed is uncomfortable.</p> * * @param {can.Mustache.key} helper A key that finds a [can.Mustache.helper helper function] * that is either [can.Mustache.registerHelper registered] or found within the * current or parent [can.Mustache.context context]. * * @param {...can.Mustache.key|String|Number} [args] Space seperated arguments * that get passed to the helper function as arguments. If the key's value is a: * * - [can.Map] - A getter/setter [can.compute] is passed. * - [can.compute] - The can.compute is passed. * - `function` - The function's return value is passed. * * @param {String} hashProperty * * A property name that gets added to a [can.Mustache.helperOptions helper options]'s * hash object. * * @param {...can.Mustache.key|String|Number} hashValue A value that gets * set as a property value of the [can.Mustache.helperOptions helper option argument]'s * hash object. * * @param {mustache} BLOCK A mustache template that gets compiled and * passed to the helper function as the [can.Mustache.helperOptions options argument's] `fn` * property. * * @param {mustache} INVERSE A mustache template that gets compiled and * passed to the helper function as the [can.Mustache.helperOptions options argument's] `inverse` * property. * * * @body * * ## Use * * Read the [use section of {{helper}}](can.Mustache.helpers.helper.html#section_Use) to better understand how: * * - [Helper functions are found](can.Mustache.helpers.helper.html#section_Arguments) * - [Arguments are passed to the helper](can.Mustache.helpers.helper.html#section_Arguments) * - [Hash values are passed to the helper](can.Mustache.helpers.helper.html#section_Hash) * * Read how [helpers that return functions](can.Mustache.helper.html#section_Returninganelementcallbackfunction) can * be used for rich behavior like 2-way binding. * */ // Open a new section. case '#': /** * @function can.Mustache.helpers.inverse {{^key}} * @parent can.Mustache.tags 5 * * @signature `{{^key}}BLOCK{{/key}}` * * Render blocks of text if the value of the key * is falsey. An inverted section syntax is similar to regular * sections except it begins with a caret rather than a * pound. If the value referenced is falsey, the section will render. * * @param {can.Mustache.key} key A key that references a value within the current or parent * [can.Mustache.context context]. If the value is a function or [can.compute], the * function's return value is used. * * @return {String} * * Depending on the value's type, the following actions happen: * * - A `truthy` value - the block is not rendered. * - A `falsey` value - the block is rendered. * * The rendered result of the block or an empty string is returned. * * @body * * ## Use * * Inverted sections match falsey values. An inverted section * syntax is similar to regular sections except it begins with a caret * rather than a pound. If the value referenced is falsey, the section * will render. For example: * * * The template: * * <ul> * {{#friends}} * </li>{{name}}</li> * {{/friends}} * {{^friends}} * <li>No friends.</li> * {{/friends}} * </ul> * * And data: * * { * friends: [] * } * * Results in: * * * <ul> * <li>No friends.</li> * </ul> */ case '^': if(cmd.specialAttribute) { result.push(cmd.insert + 'can.view.onlytxt(this,function(){ return '); } else { result.push(cmd.insert + 'can.view.txt(0,\'' + cmd.tagName + '\',' + cmd.status + ',this,function(){ return '); } break; // Close the prior section. /** * @function can.Mustache.helpers.close {{/key}} * @parent can.Mustache.tags 4 * * @signature `{{/key}}` * * Ends a [can.Mustache.helpers.section {{#key}}] or [can.Mustache.helpers.sectionHelper {{#helper}}] * block. * * @param {can.Mustache.key} [key] A key that matches the opening key or helper name. It's also * possible to simply write `{{/}}` to end a block. */ case '/': return { raw: 'return ___v1ew.join("");}}])}));' }; break; } // Trim the mode off of the content. content = content.substring(1); } // `else` helpers are special and should be skipped since they don't // have any logic aside from kicking off an `inverse` function. if (mode != 'else') { var args = [], i = 0, hashing = false, arg, split, m; // Start the content render block. result.push('can.Mustache.txt(\n'+CONTEXT_OBJ+',\n' + (mode ? '"'+mode+'"' : 'null') + ','); // Parse the helper arguments. // This needs uses this method instead of a split(/\s/) so that // strings with spaces can be correctly parsed. var args = [], hashes = []; (can.trim(content)+' ').replace(argumentsRegExp, function(whole, arg) { // Check for special helper arguments (string/number/boolean/hashes). if (i && (m = arg.match(literalNumberStringBooleanRegExp))) { // Found a native type like string/number/boolean. if (m[2]) { args.push(m[0]); } // Found a hash object. else { // Addd to the hash object. hashes.push(m[4]+":"+(m[6] ? m[6] : makeLookupLiteral(m[5]))) } } // Otherwise output a normal interpolation reference. else { args.push( makeLookupLiteral(arg) ); } i++; }); result.push(args.join(",")); if( hashes.length ) { result.push(",{"+HASH+":{"+hashes.join(",")+"}}") } } // Create an option object for sections of code. mode && mode != 'else' && result.push(',[\n\n'); switch (mode) { // Truthy section case '#': result.push('{fn:function(' + ARG_NAMES + '){var ___v1ew = [];'); break; // If/else section // Falsey section /** * @function can.Mustache.helpers.else {{else}} * @parent can.Mustache.htags 3 * * @signature `{{#helper}}BLOCK{{else}}INVERSE{{/helper}}` * * Creates an `inverse` block for a [can.Mustache.helper helper function]'s * [can.Mustache.helperOptions options argument]'s `inverse` property. * * @param {can.Mustache} INVERSE a mustache template coverted to a * function and set as the [can.Mustache.helper helper function]'s * [can.Mustache.helperOptions options argument]'s `inverse` property. * * @body * * ## Use * * For more information on how `{{else}}` is used checkout: * * - [can.Mustache.helpers.if {{if key}}] * - [can.Mustache.helpers.sectionHelper {{#helper}}] * */ case 'else': result.push('return ___v1ew.join("");}},\n{inverse:function(' + ARG_NAMES + '){\nvar ___v1ew = [];'); break; case '^': result.push('{inverse:function(' + ARG_NAMES + '){\nvar ___v1ew = [];'); break; // Not a section, no mode default: result.push(')'); break; } // Return a raw result if there was a section, otherwise return the default string. result = result.join(''); return mode ? { raw: result } : result; } } ] }) }); // Add in default scanner helpers first. // We could probably do this differently if we didn't 'break' on every match. var helpers = can.view.Scanner.prototype.helpers; for (var i = 0; i < helpers.length; i++) { Mustache.prototype.scanner.helpers.unshift(helpers[i]); }; /** * @function can.Mustache.txt * @hide * * Evaluates the resulting string based on the context/name. * * @param {Object|Array} context The context stack to be used with evaluation. * @param {String} mode The mode to evaluate the section with: # for truthy, ^ for falsey * @param {String|Object} name The string (or sometimes object) to pass to the given helper method. */ Mustache.txt = function(scopeAndOptions, mode, name) { var scope = scopeAndOptions.scope, options = scopeAndOptions.options, args = [], helperOptions = { fn: function() {}, inverse: function() {} }, hash, context = scope.attr("."), getHelper = true; // convert lookup values to actual values in name, arguments, and hash for(var i =3; i < arguments.length;i++){ var arg = arguments[i] if(mode && can.isArray( arg )){ // merge into options helperOptions = can.extend.apply(can, [helperOptions].concat(arg)) } else if(arg && arg[HASH]){ hash = arg[HASH]; // get values on hash for(var prop in hash){ if(isLookup(hash[prop]) ){ hash[prop] = Mustache.get(hash[prop].get, scopeAndOptions) } } } else if(arg && isLookup(arg)){ args.push( Mustache.get(arg.get, scopeAndOptions, false, true) ); } else { args.push(arg) } } if( isLookup(name) ){ var get = name.get; name = Mustache.get(name.get, scopeAndOptions, args.length , false); // Base whether or not we will get a helper on whether or not the original // name.get and Mustache.get resolve to the same thing. Saves us from running // into issues like {{text}} / {text: 'with'} getHelper = (get === name); } // overwrite fn and inverse to always convert to scopes helperOptions.fn = makeConvertToScopes(helperOptions.fn, scope, options); helperOptions.inverse = makeConvertToScopes(helperOptions.inverse, scope, options) // Check for a registered helper or a helper-like function. if (helper = ( getHelper && (typeof name === "string" && Mustache.getHelper(name,options) )|| (can.isFunction(name) && !name.isComputed && { fn: name }))) { // Add additional data to be used by helper functions can.extend(helperOptions,{ context: context, scope: scope, contexts: scope, hash: hash }) args.push(helperOptions) // Call the helper. return helper.fn.apply(context, args) || ''; } if( can.isFunction(name) ){ if ( name.isComputed ) { name = name(); } } // An array of arguments to check for truthyness when evaluating sections. var validArgs = args.length ? args : [name], // Whether the arguments meet the condition of the section. valid = true, result = [], i, helper, argIsObserve, arg; // Validate the arguments based on the section mode. if (mode) { for (i = 0; i < validArgs.length; i++) { arg = validArgs[i]; argIsObser