pax_global_header00006660000000000000000000000064120073476730014523gustar00rootroot0000000000000052 comment=734d3bd4d135106fa6df320b5a6e484089627509 BorisMoore-jsrender-734d3bd/000077500000000000000000000000001200734767300160315ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/MIT-LICENSE.txt000066400000000000000000000021101200734767300202750ustar00rootroot00000000000000Copyright (c) 2012 Boris Moore https://github.com/BorisMoore/jsviews Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. BorisMoore-jsrender-734d3bd/README.md000066400000000000000000000027121200734767300173120ustar00rootroot00000000000000## JsRender: Next-generation jQuery Templates _Optimized for high-performance pure string-based rendering, without DOM or jQuery dependency._
To view demo pages (on gh-branch) navigate to [http://borismoore.github.com/jsrender/demos/index.html](http://borismoore.github.com/jsrender/demos/index.html "JsRender Samples").
JsRender is used for rendering of templates to strings, ready for insertion in the DOM.
It can also be used together with [JsViews](https://github.com/BorisMoore/jsviews), which then provides interactive data-driven views. See [JsViews step-by-step samples](http://borismoore.github.com/jsviews/demos/index.html).
See [Approaching Beta: What's changing in JsRender and JsViews](http://www.borismoore.com/2012/03/approaching-beta-whats-changing-in_06.html) for documentation of changes.
See also [JsRender Fundamentals](http://johnpapa.net/new-course-on-jsrender-templating-fundamentals-with-javascript) training course from John Papa on [Pluralsight](http://pluralsight.net)
**Warning:** JsRender is not yet officially beta, though the APIs and code are now stable. JsViews, on the other hand, is still evolving (with a number of powerful features arriving), and its Beta is currently planned for late September or early October. Since this could lead to small API changes in JsRender (to accomodate JsViews integration) JsRender will not be declared officially Beta until around the same time. Thank you for you patience!
BorisMoore-jsrender-734d3bd/demos/000077500000000000000000000000001200734767300171405ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/demos.html000066400000000000000000000043401200734767300211360ustar00rootroot00000000000000 JsRender: Demos
<< Index for JsRender and JsViews

JsRender: Demos

JsRender - step-by-step samples

JsRender - variants and details

JsRender - scenario examples

BorisMoore-jsrender-734d3bd/demos/index.html000066400000000000000000000043011200734767300211330ustar00rootroot00000000000000 JsRender: Demos

JsRender: Next-generation jQuery Templates

JsRender:
JsRender templates are optimized for high-performance pure string-based rendering, without DOM or jQuery dependency
JsRender: Demos
Source code:
https://github.com/BorisMoore/jsrender
Tests:
JsRender unit tests - with jQuery
JsRender unit tests - without jQuery
Perf comparison
See also on JsViews site:
JsViews are interactive data-driven views, built on top of JsRender templates
JsViews: Demos
JsViews and JsRender Overview:
Demo sequence from jQuery Conference October 2011
Other links:
Slide deck: jQuery Conference October 2011
MSDN 'Client Insight' articles on JsRender part one and part two
Training course: JsRender Fundamentals from John Papa on Pluralsight (3 hours of video)
BorisMoore-jsrender-734d3bd/demos/resources/000077500000000000000000000000001200734767300211525ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/resources/demos.css000066400000000000000000000017541200734767300230020ustar00rootroot00000000000000body { padding: 10px; font-family: Verdana; font-size: small } h4 { font-size: inherit`; font-variant: small-caps; } .height { width: 100%; margin-bottom:10px; float: left; clear: both; } .bottom { height:400px; width: 100%; margin-bottom:10px; float: left; clear: both; } body > button { float: left; clear: right; margin: 3px } .subhead { margin: 3px 0 5px 0; font-weight:bolder; color:#116; font-family:Arial; font-size:10pt } a { color: #55b; } pre { font-size:10pt; font-weight:bold; } .inset { padding-left: 18px; } .box { border: 1px solid #777; padding: 5px; margin: 5px 0 30px; } .box div { margin: 3px 0 10px 0; } .box .label { margin: 0; padding: 10px 0 0 0; font-style:italic; color: #55b; } .box.label { font-style:italic; color: #55b; } .desc { font-style:italic; margin: 0 0 15px; color:#116; } pre { border-left: 3px solid #aaa; padding:10px; margin-bottom: 30px; } .indexitems { list-style-type:none; padding-left:10px; margin: 0 0 20px;} h3 { margin-bottom: 10px; font-size: 11pt;} BorisMoore-jsrender-734d3bd/demos/resources/movielist.css000066400000000000000000000006661200734767300237070ustar00rootroot00000000000000table { border-collapse: collapse; } table tr { color: blue; height: 25px; } thead tr { color: #009; border-bottom: solid #77c 2px; background-color: #E8E8F7; } thead th { padding:5px; border: 1px solid #77c; } #movieList tr td:first-child { width: 130px; } table { border: 2px solid blue; width: 520px; margin: 4px 0 24px 4px; padding: 2px; background-color: #f8f8f8; } table td { padding: 3px; margin: 3px; border: solid #77c 1px; } BorisMoore-jsrender-734d3bd/demos/scenarios/000077500000000000000000000000001200734767300211265ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/scenarios/01_default-values-scenario.html000066400000000000000000000060561200734767300270450ustar00rootroot00000000000000 JsRender Demos

Example Scenario: providing default values for data.

The simplest (and best) way: Javascript expression '||':
{{>languages||'Languages unavailable'}}
Title{{>path}}
Creating a special custom tag:
{{get languages defaultValue="No languages!"/}}

$.views.tags({
    get: function( value ) {
        return value || this.props.defaultValue;
    }
});
Title{{get path default="..."}}
Creating a multi-purpose utility tag:
{{yesNo languages yes="Alternate languages available:" no="No alternate languages"/}}

$.views.tags({
    yesNo: function( value ) {
        return value ? this.props.yes : this.props.no;
    }
});
Title{{yesNo path yes="..." no="..."}}
BorisMoore-jsrender-734d3bd/demos/scenarios/02_separators-scenario.html000066400000000000000000000114321200734767300263020ustar00rootroot00000000000000 JsRender Demos

Example Scenario: Inserting "and" and "," separators between words

Example 1: Expressions in tags, and template parameters:
    {{for languages ~count=languages.length}}
        ...
        {{if #index === ~count-2}} and {{else #index < ~count-2}}, {{/if}}
        ...
    {{/for}}
TitleLanguages
Example 2: Custom helper functions:
    {{for languages}}
        ...
        {{if ~nextToLast()}} and {{else ~notLast()}}, {{/if}}
        ...
    {{/for}}
TitleLanguages

Using "allowCode"

Note: The allowCode feature is powerful, but leads to poor separation of concerns, and poor maintainability.
It is therefore only available as an opt-in feature on a per template basis.

The following two examples illustrate its use, but are not the recommended approach. The built-in expression support,
custom tags, helper functions etc. provide a better solution for almost all scenarios, as in the two examples above.
Example 3: allowCode, for program flow:
$.templates( "movieTmpl", {
    markup: "#movieTemplate",
    allowCode: true
});

{{*
    if ( myexpression ) {
}}
    ...
{{*
    }
}}
TitleLanguages
Example 4: allowCode, for returning content:
$.templates( "movieTmpl", {
    markup: "#movieTemplate",
    allowCode: true
});

{{*
    if ( myexpression ) {
        ret += ...;
        ...
    }
}}
TitleLanguages
BorisMoore-jsrender-734d3bd/demos/scenarios/03_iterating-through-fields-scenario.html000066400000000000000000000057071200734767300310400ustar00rootroot00000000000000 JsRender Demos

Example Scenario: Creating custom helpers to iterate through fields

Using a custom {{fields}} tag:
{{fields details}}
    <b>{{>~key}}</b>: {{>#data}}
{{/fields}}
TitleDetails
Using a custom ~getFields() helper function:
{{for ~getFields(details)}}
    <b>{{>key}}</b>: {{>value}}
{{/for}}
TitleDetails
BorisMoore-jsrender-734d3bd/demos/scenarios/04_assigning-variables-scenario.html000066400000000000000000000054011200734767300300500ustar00rootroot00000000000000 JsRender Demos

Example Scenario: Custom tag and helper for assigning/getting local variables.

Note: This scenario implies understanding the processing sequence of template rendering,
and is somewhat in contradiction with the 'logicless' and declarative philosophy.
However it illustrates the power of the custom tags and helper function extensibility,
and is useful in certain advanced scenarios.
Declare setvar custom tag and getvar custom helper function
var vars = {};

$.views.tags({
    setvar: function(key, value) {
        ...
        vars[key] = value;
        ...
    }
});

$.views.helpers({
    getvar: function(key) {
        return vars[key];
    }
})
Use {{setvar}} to assign values or rendered content to variable
{{setvar "summary" languages/}}
{{setvar "summary"}}
    <b>Subtitles only:</b> {{>subtitles}}
{{/setvar}}
Use {{:~getvar}} to take values stored in the variable, and render them elsewhere in the template
{{:~getvar('summary')}}
titlelanguagessummary
BorisMoore-jsrender-734d3bd/demos/scenarios/05_arrays-plus-headers-and-footers.html000066400000000000000000000041441200734767300304350ustar00rootroot00000000000000 JsRender Demos

To pass an array to a "layout template" - which includes headers or footers,
as well as the rendered items - wrap the array in an array...

Top-level layout:
$( "#movieList" ).html(
    // Wrap movies array in an array
    $("#movieTemplate").render( [movies] )
);

Template:

   header
   {{for #data}}
      item
   {{/for}}
   footer
Nested layout:
{{for [languages]}}
    header
    {{for #data}}
        item
    {{/for}}
    footer
{{/for}}
BorisMoore-jsrender-734d3bd/demos/step-by-step/000077500000000000000000000000001200734767300214745ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/step-by-step/01_inserting-data.html000066400000000000000000000016441200734767300256000ustar00rootroot00000000000000 JsRender Demos

Render template against local data

BorisMoore-jsrender-734d3bd/demos/step-by-step/02_compiling-named-templates-from-strings.html000066400000000000000000000030201200734767300323450ustar00rootroot00000000000000 JsRender Demos

Compiling named templates from strings



BorisMoore-jsrender-734d3bd/demos/step-by-step/03_converters-and-encoding.html000066400000000000000000000052521200734767300274060ustar00rootroot00000000000000 JsRender Demos

Using {{: }} or {{> }} to render data values with optional conversion or HTML encoding


Note: A common use for converters is to protect against injection attacks from untrusted data.
It is generally best to use {{> }} when rendering data within element content, if the data is not intended to provide markup for insertion in the DOM.
In the context of HTML attributes, other encoders can easily be registered, such as an attribute encoder, {{attr: }}.
Title (loc:French)No ConvertHTML Encode
BorisMoore-jsrender-734d3bd/demos/step-by-step/04_if-else-tag.html000066400000000000000000000024771200734767300247740ustar00rootroot00000000000000 JsRender Demos

Using {{if}} and {{else}} to render conditional sections.

titlelanguages
BorisMoore-jsrender-734d3bd/demos/step-by-step/05_for-tag.html000066400000000000000000000023661200734767300242340ustar00rootroot00000000000000 JsRender Demos

Using {{for}} to render hierarchical data - inline nested template.

TitleLanguages
BorisMoore-jsrender-734d3bd/demos/step-by-step/06_template-composition.html000066400000000000000000000035251200734767300270500ustar00rootroot00000000000000 JsRender Demos

Template composition. Using external templates for block tags, such as {{for}} and {{if}}.

SynopsisFixed TemplateTemplate specified in dataConditional Template
BorisMoore-jsrender-734d3bd/demos/step-by-step/07_paths.html000066400000000000000000000033431200734767300240120ustar00rootroot00000000000000 JsRender Demos

Accessing paths

BorisMoore-jsrender-734d3bd/demos/step-by-step/08_custom-tags.html000066400000000000000000000042571200734767300251470ustar00rootroot00000000000000 JsRender Demos

Custom tags

<td>{{sort languages reverse=true}}...{{/sort}}</td>

$.views.tags({

    // Tag to reverse-sort an array

    sort: function( array ) {
        var ret = "";
        if ( this.props.reverse ) {
            // Render in reverse order
            for ( var i = array.length; i; i-- ) {
                ret += this.renderContent( array[ i - 1 ] );
            }
        } else {
            // Render in original order
            ret += this.renderContent( array );
        }
        return ret;
    }

});
TitleLanguagesReverse order
BorisMoore-jsrender-734d3bd/demos/step-by-step/09_helper-functions.html000066400000000000000000000027741200734767300261710ustar00rootroot00000000000000 JsRender Demos

Helper functions

{{>~format(name, "upper")}}

$.views.helpers({

    format: function( val, format ) {
        ...
        return val.toUpperCase();
        ...
    },

    ...
});
TitleLanguages
BorisMoore-jsrender-734d3bd/demos/step-by-step/10_comparison-tests.html000066400000000000000000000026661200734767300262060ustar00rootroot00000000000000 JsRender Demos

Comparison tests

{{if !(languages && languages.length)}}...{{/if}}

{{if languages==null}}...{{/if}}
Title{{if !(languages && languages.length)}}{{if a==null}}
BorisMoore-jsrender-734d3bd/demos/step-by-step/11_accessing-parent-data.html000066400000000000000000000056111200734767300270230ustar00rootroot00000000000000 JsRender Demos

Example Scenario: Accessing parent data.

Stepping up through the views (tree of nested rendered templates)
var model = {
    specialMessage: function(...) { ... },
    theater: "Rialto",
    movies: [ ... ]
}

{{for movies}}
    <tr>
        <td>'{{>title}}': showing at the '{{>#parent.parent.data.theater}}'</td>
TitleLanguages (+specialMessage)
Setting contextual template parameters, accessible in all nested contexts as ~nameOfParameter:
{{for movies ~theater=theater}}
    <tr>
        <td>'{{>title}}': showing at the '{{>~theater}}'</td>
TitleLanguages (+specialMessage)
BorisMoore-jsrender-734d3bd/demos/step-by-step/12_passing-in-context.html000066400000000000000000000057121200734767300264230ustar00rootroot00000000000000 JsRender Demos

Template context: Passing in additional variables to a render() call

Passing in contextual variables or helper functions, using the options parameter of ...render( data, options );
$( selector ).render( data, {
    reverseSort: reverse,
    buttonCaption: reverse ? "Sort increasing" : "Sort decreasing",
    format: myFormatFunction
})
Use ~name to context variables or helpers by name - whether passed in with options,
registered globally as helpers, or registered as helpers for a specific template.
<th><button>{{>~buttonCaption}}</button></th>
...
<td>{{>~format(title)}}</td>
...
<td>{{sort languages reverse=~reverseSort}}...{{/sort}}</td>

BorisMoore-jsrender-734d3bd/demos/step-by-step/13_associating-helpers-with-templates.html000066400000000000000000000043221200734767300315770ustar00rootroot00000000000000 JsRender Demos

Associating specific contextual helpers with templates

Including helpers in a template definition.
$.templates({
    appTmpl: {
        markup:"#appTmpl",
        helpers: {
            supplierUtils: ...
        }
    }
});
Passing different helpers to a subtemplate based on the context where it is used.
{{for suppliers tmpl="personTmpl" ~utils=~supplierUtils/}}
Accessing helper from nested template:
<b>ID:</b> <em>{{:~utils.format(id)}}</em>
​ BorisMoore-jsrender-734d3bd/demos/step-by-step/20_without-jquery.html000066400000000000000000000036761200734767300257170ustar00rootroot00000000000000 JsRender Demos

JsRender without jQuery

    jsviews.templates({
        movieTemplate: document.getElementById( "movieTemplate" ).innerHTML,
        ...
    });

    document.getElementById( "movieList" ).innerHTML = jsviews.render.movieTemplate( movies );
BorisMoore-jsrender-734d3bd/demos/variants/000077500000000000000000000000001200734767300207675ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/variants/accessing-templates/000077500000000000000000000000001200734767300247225ustar00rootroot0000000000000001_compiling-template-objects-from-strings.html000066400000000000000000000033521200734767300357050ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/variants/accessing-templates
<< JsRender Demos: Variants

Compiling template objects from strings

Variant of Compiling named templates sample



02_registering-named-template-from-script-declaration.html000066400000000000000000000036641200734767300400060ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/variants/accessing-templates
<< JsRender Demos: Variants

Register script block template declarations, as named templates

Variant of Compiling named templates sample



03_getting-template-objects-from-script-declaration.html000066400000000000000000000037171200734767300374720ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/variants/accessing-templates
<< JsRender Demos: Variants

Getting template objects from script block template declarations

Variant of Compiling named templates sample



04_template-composition-subtemplates.html000066400000000000000000000042541200734767300347230ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/variants/accessing-templates
<< JsRender Demos: Variants

Template composition. Registering and accessing subtemplates.

Variant of template composition sample

SynopsisFixed TemplateTemplate specified in dataConditional Template
05_template-composition-templateobjects.html000066400000000000000000000043651200734767300354040ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/demos/variants/accessing-templates
<< JsRender Demos: Variants

Template composition. Passing in template objects as the contextual template parameters.

Variant of template composition sample

SynopsisFixed TemplateTemplate specified in dataConditional Template
BorisMoore-jsrender-734d3bd/demos/variants/variants.html000066400000000000000000000020701200734767300235030ustar00rootroot00000000000000 JsRender: Demos
<< JsRender Demos

JsRender: Variants and Details

Accessing templates

BorisMoore-jsrender-734d3bd/jsrender.js000066400000000000000000001101121200734767300201770ustar00rootroot00000000000000/*! JsRender v1.0pre: http://github.com/BorisMoore/jsrender */ /* * Optimized version of jQuery Templates, for rendering to string. * Does not require jQuery, or HTML DOM * Integrates with JsViews (http://github.com/BorisMoore/jsviews) * Copyright 2012, Boris Moore * Released under the MIT License. */ // informal pre beta commit counter: 21 (function(global, jQuery, undefined) { // global is the this object, which is window when running in the usual browser environment. if (jQuery && jQuery.views || global.jsviews) return; // JsRender is already loaded //========================== Top-level vars ========================== var versionNumber = "v1.0pre", $, rTag, rTmplString, $extend, // compiledTmplsCache = {}, delimOpenChar0 = "{", delimOpenChar1 = "{", delimCloseChar0 = "}", delimCloseChar1 = "}", deferChar = "!", $viewsSub = {}, FALSE = false, TRUE = true, rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g, // nil object helper view viewProperty pathTokens leafToken string rParams = /(\()(?=|\s*\()|(?:([([])\s*)?(?:([#~]?[\w$.]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)/g, // lftPrn lftPrn2 path operator err eq path2 prn comma lftPrn2 apos quot rtPrn prn2 space // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space rNewLine = /\r?\n/g, rUnescapeQuotes = /\\(['"])/g, rEscapeQuotes = /\\?(['"])/g, rBuildHash = /\x08(~)?([^\x08]+)\x08/g, autoTmplName = 0, escapeMapForHtml = { "&": "&", "<": "<", ">": ">" }, tmplAttr = "data-jsv-tmpl", fnDeclStr = "var j=j||" + (jQuery ? "jQuery." : "js") + "views,", htmlSpecialChar = /[\x00"&'<>]/g, slice = Array.prototype.slice, $render = {}, // jsviews object ($.views if jQuery is loaded) $views = { jsviews: versionNumber, sub: $viewsSub, // subscription, e.g. JsViews integration debugMode: TRUE, render: $render, templates: $templates, tags: $viewsTags, helpers: $viewsHelpers, converters: $viewsConverters, delimiters: $viewsDelimiters, View: View, _convert: convert, _err: function(e) { return $views.debugMode ? ("Error: " + (e.message || e)) + ". " : ''; }, _tmplFn: tmplFn, _tag: renderTag, error: error, Error: JsViewsError }; function JsViewsError(message) { // Error exception type for JsViews/JsRender this.name = "JsRender Error", this.message = message || "JsRender error" } (JsViewsError.prototype = new Error()).constructor = JsViewsError; //========================== Top-level functions ========================== //=================== // jsviews.delimiters //=================== function $viewsDelimiters(openChars, closeChars, defer) { // Set the tag opening and closing delimiters. Default is "{{" and "}}" // openChar, closeChars: opening and closing strings, each with two characters if (!$views.rTag || arguments.length) { delimOpenChar0 = openChars ? "\\" + openChars.charAt(0) : delimOpenChar0; // Escape the characters - since they could be regex special characters delimOpenChar1 = openChars ? "\\" + openChars.charAt(1) : delimOpenChar1; delimCloseChar0 = closeChars ? "\\" + closeChars.charAt(0) : delimCloseChar0; delimCloseChar1 = closeChars ? "\\" + closeChars.charAt(0) : delimCloseChar1; defer = defer ? "\\" + defer : deferChar; // Build regex with new delimiters $views.rTag = rTag // make rTag available to JsViews (or other components) for parsing binding expressions // tag (followed by / space or }) or cvtr+colon or html or code = "(\\w*" + defer + ")?(?:(?:(\\w+(?=[\\/\\s" + delimCloseChar0 + "]))|(?:(\\w+)?(:)|(>)|(\\*)))" // params + "\\s*((?:[^" + delimCloseChar0 + "]|" + delimCloseChar0 + "(?!" + delimCloseChar1 + "))*?)"; // slash or closeBlock }} rTag = new RegExp(delimOpenChar0 + delimOpenChar1 + rTag + "(\\/)?|(?:\\/(\\w+)))" + delimCloseChar0 + delimCloseChar1, "g"); // Default rTag: tag converter colon html code params slash closeBlock // /{{(?:(?:(\w+(?=[\/\s}]))|(?:(\w+)?(:)|(>)|(\*)))\s*((?:[^}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}} rTmplString = new RegExp("<.*>|([^\\\\]|^)[{}]|" + delimOpenChar0 + delimOpenChar1 + ".*" + delimCloseChar0 + delimCloseChar1); // rTmplString looks for html tags or { or } char not preceeded by \\, or JsRender tags {{xxx}}. Each of these strings are considered NOT to be jQuery selectors } return [delimOpenChar0, delimOpenChar1, delimCloseChar0, delimCloseChar1, deferChar]; } //================= // View._hlp //================= function getHelper(helper) { // Helper method called as view._hlp() from compiled template, for helper functions or template parameters ~foo var view = this, tmplHelpers = view.tmpl.helpers || {}; helper = ( view.dynCtx && view.dynCtx[helper] !== undefined ? view.dynCtx : view.ctx[helper] !== undefined ? view.ctx : tmplHelpers[helper] !== undefined ? tmplHelpers : $viewsHelpers[helper] !== undefined ? $viewsHelpers : {} )[helper]; return typeof helper !== "function" ? helper : function() { return helper.apply(view, arguments); }; } //================= // jsviews._convert //================= function convert(converter, view, self, text) { // self is template object or link object var linkContext = !self.markup && self || undefined, tmplConverter = view.tmpl.converters; tmplConverter = tmplConverter && tmplConverter[converter] || $viewsConverters[converter]; return tmplConverter ? tmplConverter.call(view, text, linkContext) : (error("Unknown converter: {{"+ converter + ":"), text); } //================= // jsviews._tag //================= function renderTag(tag, parentView, self, content, tagInstance) { // Called from within compiled template function, to render a nested tag // Returns the rendered tag var ret, linkCtx = !self.markup && self, // self is either a template object (if rendering a tag) or a linkCtx object (if linking using a link tag) parentTmpl = linkCtx ? linkCtx.view.tmpl : self, tmplTags = parentTmpl.tags, nestedTemplates = parentTmpl.templates, props = tagInstance.props = tagInstance.props || {}, tmpl = props.tmpl, args = arguments.length > 5 ? slice.call(arguments, 5) : [], tagObject = tmplTags && tmplTags[tag] || $viewsTags[tag]; if (!tagObject) { error("Unknown tag: {{"+ tag + "}}"); return ""; } // Set the tmpl property to the content of the block tag, unless set as an override property on the tag content = content && parentTmpl.tmpls[content - 1]; tmpl = tmpl || content || tagObject.template || undefined; tagInstance.view = parentView; tmpl = tagInstance.tmpl = "" + tmpl === tmpl // if a string ? nestedTemplates && nestedTemplates[tmpl] || $templates[tmpl] || $templates(tmpl) : tmpl; tagInstance.attr = // Setting attr on tagInstance so renderContent knows whether to include template annotations. self.attr = // Setting attr on self.fn to ensure outputting to the correct target attribute. self.attr || tagObject.attr; tagInstance.tagName = tag; tagInstance.renderContent = renderContent; if (linkCtx) { linkCtx.tagCtx = { args: args, props: props, path: tagInstance.path, tag: tagObject }; } // If render function is declared, call it. If the return result is undefined, return "", or, if a template (or content) is provide, return the rendered template (using the first parameter as data); if (tagObject.render) { ret = tagObject.render.apply(tagInstance, args); } return ret || (ret == undefined ? (tmpl ? tagInstance.renderContent(args[0], undefined, parentView) : "") : ret.toString()); // (If ret is the value 0 or false, will render to string) } //================= // View constructor //================= function View(context, path, parentView, data, template, key, onRender, isArray) { // Constructor for view object in view hierarchy. (Augmented by JsViews if JsViews is loaded) var views, self = { data: data, tmpl: template, views: isArray ? [] : {}, parent: parentView, ctx: context, // If the data is an array, this is an 'Array View' with a views array for each child 'Instance View' // If the data is not an array, this is an 'Instance View' with a views 'map' object for any child nested views // _useKey is non zero if is not an 'Array View' (owning a data array). Uuse this as next key for adding to child views map path: path, _useKey: isArray ? 0 : 1, _onRender: onRender, _hlp: getHelper, renderLink: function(index) { var linkTmpl = this.tmpl.tmpls[index]; return linkTmpl.render(data, context, this); } }; if (parentView) { views = parentView.views; if (parentView._useKey) { // Parent is an 'Instance View'. Add this view to its views object // self.key = is the key in the parent view map views[self.key = "_" + parentView._useKey++] = self; // self.index = is index of the parent self.index = parentView.index; } else { // Parent is an 'Array View'. Add this view to its views array views.splice( // self.key = self.index - the index in the parent view array self.key = self.index = key !== undefined ? key : views.length, 0, self); } } return self; } //================= // Registration //================= function addToStore(self, store, name, item, process) { // Add item to named store such as templates, helpers, converters... var key, onStore; if (name && typeof name === "object" && !name.nodeType) { // If name is a map, iterate over map and call store for key for (key in name) { store(key, name[key]); } return self; } if (item === undefined) { item = name; name = undefined; } if (onStore = $viewsSub.onBeforeStoreItem) { // e.g. provide an external compiler or preprocess the item. process = onStore(store, name, item, process) || process; } if (!name) { item = process ? process(item) : item } else if ("" + name === name) { // name must be a string if (item === null) { // If item is null, delete this entry delete store[name]; } else { store[name] = process ? (item = process(item, name)) : item; } } if (onStore = $viewsSub.onStoreItem) { // e.g. JsViews integration onStore(store, name, item, process); } return item; } function compileTag(item, name) { item = typeof item === "function" ? { render: item } : item; item.name = name; item.is = "tag"; return item; } function $templates(name, tmpl) { // Register templates // Setter: Use $.templates( name, tmpl ) or $.templates({ name: tmpl, ... }) to add additional templates to the registered templates collection. // Getter: Use var tmpl = $.templates( name ) or $.templates[name] or $.templates.name to return the object for the registered template. // Remove: Use $.templates( name, null ) to remove a registered template from $.templates. return addToStore(this, $templates, name, tmpl, compile); } function $viewsTags(name, tag) { // Register template tags // Setter: Use $.view.tags( name, tag ) or $.view.tags({ name: tag, ... }) to add additional tags to the registered tags collection. // Getter: Use var tag = $.views.tags( name ) or $.views.tags[name] or $.views.tags.name to return the object for the registered tag. // Remove: Use $.view.tags( name, null ) to remove a registered tag from $.view.tags. // When registering for {{foo a b c==d e=f}}, tag should corresponnd to a function with the signature: // function(a,b). The 'this' pointer will be a hash with properties c and e. return addToStore(this, $viewsTags, name, tag, compileTag); } function $viewsHelpers(name, helperFn) { // Register helper functions for use in templates (or in data-link expressions if JsViews is loaded) // Setter: Use $.view.helpers( name, helperFn ) or $.view.helpers({ name: helperFn, ... }) to add additional helpers to the registered helpers collection. // Getter: Use var helperFn = $.views.helpers( name ) or $.views.helpers[name] or $.views.helpers.name to return the function. // Remove: Use $.view.helpers( name, null ) to remove a registered helper function from $.view.helpers. // Within a template, access the helper using the syntax: {{... ~myHelper(...) ...}}. return addToStore(this, $viewsHelpers, name, helperFn); } function $viewsConverters(name, converterFn) { // Register converter functions for use in templates (or in data-link expressions if JsViews is loaded) // Setter: Use $.view.converters( name, converterFn ) or $.view.converters({ name: converterFn, ... }) to add additional converters to the registered converters collection. // Getter: Use var converterFn = $.views.converters( name ) or $.views.converters[name] or $.views.converters.name to return the converter function. // Remove: Use $.view.converters( name, null ) to remove a registered converter from $.view.converters. // Within a template, access the converter using the syntax: {{myConverter:...}}. return addToStore(this, $viewsConverters, name, converterFn); } //================= // renderContent //================= function renderContent(data, context, parentView, key, isLayout, path, onRender) { // Render template against data as a tree of subviews (nested template), or as a string (top-level template). var i, l, dataItem, newView, itemResult, parentContext, tmpl, props, swapContent, mergedCtx, dynCtx, hasContext, self = this, result = ""; if (key === TRUE) { swapContent = TRUE; key = 0; } if (self.tagName) { // This is a call from renderTag tmpl = self.tmpl; if (context || self.ctx) { // We need to create an augmented context for the view(s) we are about to render mergedCtx = {}; if (self.ctx) { // self.ctx is an object with the contextual template parameters on the tag, such as ~foo: {{tag ~foo=expression...}} $extend(mergedCtx, self.ctx); } if (context) { // This is a context object passed programmatically from the tag function $extend(mergedCtx, context); } } context = mergedCtx; props = self.props; if ( props && props.link === FALSE ) { // link=false setting on block tag // We will override inherited value of link by the explicit setting link=false taken from props // The child views of an unlinked view are also unlinked. So setting child back to true will not have any effect. context = context || {}; context.link = FALSE; } parentView = parentView || self.view; path = path || self.path; key = key || self.key; // onRender = self.attr === "html" && parentView && parentView._onRender; onRender = parentView && parentView._onRender; } else { tmpl = self.jquery && (self[0] || error('Unknown template: "' + self.selector + '"')) // This is a call from $(selector).render || self; onRender = onRender || parentView && parentView._onRender; } if (tmpl) { if (parentView) { parentContext = parentView.ctx; dynCtx = parentView.dynCtx; if (data === parentView) { // Inherit the data from the parent view. // This may be the contents of an {{if}} block // Set isLayout = true so we don't iterate the if block if the data is an array. data = parentView.data; isLayout = TRUE; } } else { parentContext = $viewsHelpers; } // Set additional context on views created here, (as modified context inherited from the parent, and to be inherited by child views) // Note: If no jQuery, $extend does not support chained copies - so limit extend() to two parameters // TODO could make this a reusable helper for merging context. hasContext = (context && context !== parentContext); if (dynCtx || hasContext) { parentContext = $extend({}, parentContext); if (hasContext) { $extend(parentContext, context); } if (dynCtx) { $extend(parentContext, dynCtx); } } context = parentContext; if (!tmpl.fn) { tmpl = $templates[tmpl] || $templates(tmpl); } if (tmpl) { onRender = context.link !== FALSE && onRender; // If link===false, do not call onRender, so no data-linking annotations if ($.isArray(data) && !isLayout) { // Create a view for the array, whose child views correspond to each data item. // (Note: if key and parentView are passed in along with parent view, treat as // insert -e.g. from view.addViews - so parentView is already the view item for array) newView = swapContent ? parentView : (key !== undefined && parentView) || View(context, path, parentView, data, tmpl, key, onRender, TRUE); for (i = 0, l = data.length; i < l; i++) { // Create a view for each data item. dataItem = data[i]; itemResult = tmpl.fn(dataItem, View(context, path, newView, dataItem, tmpl, (key || 0) + i, onRender), $views); result += onRender ? onRender(itemResult, tmpl, props) : itemResult; } } else { // Create a view for singleton data object. newView = swapContent ? parentView : View(context, path, parentView, data, tmpl, key, onRender); newView._onRender = onRender; result += tmpl.fn(data, newView, $views, returnVal); } return onRender ? onRender(result, tmpl, props, newView.key, path) : result; } } error("No template found"); return ""; } function returnVal(value) { return value; } //=========================== // Build and compile template //=========================== // Generate a reusable function that will serve to render a template against data // (Compile AST then build template function) function error(message) { if ($views.debugMode) { throw new $views.Error(message); } } function syntaxError(message) { error("Syntax error\n" + message); } function tmplFn(markup, tmpl, bind) { // Compile markup to AST (abtract syntax tree) then build the template function code from the AST nodes // Used for compiling templates, and also by JsViews to build functions for data link expressions var newNode, //result, allowCode = tmpl && tmpl.allowCode, astTop = [], loc = 0, stack = [], content = astTop, current = [, , , astTop]; //==== nested functions ==== function pushPreceedingContent(shift) { shift -= loc; if (shift) { content.push(markup.substr(loc, shift).replace(rNewLine, "\\n")); } } function blockTagCheck(tagName) { tagName && syntaxError('Unmatched or missing tag: "{{/' + tagName + '}}" in template:\n' + markup); } function parseTag(all, defer, tagName, converter, colon, html, code, params, slash, closeBlock, index) { // tag converter colon html code params slash closeBlock // /{{(?:(?:(\w+(?=[\/!\s\}!]))|(?:(\w+)?(:)|(?:(>)|(\*)))((?:[^\}]|}(?!}))*?)(\/)?|(?:\/(\w+)))}}/g; // Build abstract syntax tree (AST): [ tagName, converter, params, content, hash, contentMarkup, link ] if (html) { colon = ":"; converter = "html"; } var current0, hash = "", passedCtx = "", // Block tag if not self-closing and not {{:}} or {{>}} (special case) and not a data-link expression (has bind parameter) block = !slash && !colon && !bind; //==== nested helper function ==== tagName = tagName || colon; pushPreceedingContent(index); loc = index + all.length; // location marker - parsed up to here if (code) { if (allowCode) { content.push(["*", params.replace(rUnescapeQuotes, "$1")]); } } else if (tagName) { if (tagName === "else") { current[5] = markup.substring(current[5], index); // contentMarkup for block tag current = stack.pop(); content = current[3]; block = TRUE; } else if (defer) { stack.push(current); current = ["!", , , [], ,index]; content.push(current); content = current[3]; } params = (params ? parseParams(params, bind, defer) .replace(rBuildHash, function(all, isCtx, keyValue) { if (isCtx) { passedCtx += keyValue + ","; } else { hash += keyValue + ","; } return ""; }) : ""); hash = hash.slice(0, -1); params = params.slice(0, -1); newNode = [ tagName, converter || "", params, block && [], "{" + (hash ? ("props:{" + hash + "},") : "") + "data: data" + (passedCtx ? ",ctx:{" + passedCtx.slice(0, -1) + "}" : "") + "}" ]; content.push(newNode); if (block) { stack.push(current); current = newNode; current[5] = loc; // Store current location of open tag, to be able to add contentMarkup when we reach closing tag } else if (defer) { current[5] = markup.substring(current[5], loc); // contentMarkup for block tag current = stack.pop(); } } else if (closeBlock) { current0 = current[0]; blockTagCheck(closeBlock !== current0 && !(closeBlock === "if" && current0 === "else") && current0); current[5] = markup.substring(current[5], index); // contentMarkup for block tag if (current0 === "!") { // defer current[5] = markup.substring(current[5], loc); // contentMarkup for block tag current = stack.pop(); } current = stack.pop(); } blockTagCheck(!current && closeBlock); content = current[3]; } //==== /end of nested functions ==== // result = compiledTmplsCache[markup]; // Only cache if template is not named and markup length < ... Consider standard optimization for data-link="a.b.c" // if (!result) { // result = markup; markup = markup.replace(rEscapeQuotes, "\\$1"); blockTagCheck(stack[0] && stack[0][3].pop()[0]); // Build the AST (abstract syntax tree) under astTop markup.replace(rTag, parseTag); pushPreceedingContent(markup.length); // result = compiledTmplsCache[result] = buildCode(astTop, tmpl); // } // return result; return buildCode(astTop, tmpl); } function buildCode(ast, tmpl) { // Build the template function code from the AST nodes, and set as property on the passed in template object // Used for compiling templates, and also by JsViews to build functions for data link expressions var node, i, l, code, hasTag, hasEncoder, getsValue, hasConverter, hasViewPath, tag, converter, params, hash, nestedTmpl, allowCode, content, attr, quot, tmplOptions = tmpl ? { allowCode: allowCode = tmpl.allowCode, debug: tmpl.debug } : {}, nested = tmpl && tmpl.tmpls; // Use the AST (ast) to build the template function l = ast.length; code = (l ? "" : '"";'); for (i = 0; i < l; i++) { // AST nodes: [ tagName, converter, params, content, hash, contentMarkup, link ] node = ast[i]; if ("" + node === node) { // type string code += '"' + node + '"+'; } else { tag = node[0]; if (tag === "*") { code = code.slice(0, i ? -1 : -3) + ";" + node[1] + (i + 1 < l ? "ret+=" : ""); } else { converter = node[1]; params = node[2]; content = node[3]; hash = node[4]; markup = node[5]; if (tag.slice(-1) === "!") { // Create template object for nested template nestedTmpl = TmplObject(markup, tmplOptions, tmpl, nested.length); // Compile to AST and then to compiled function buildCode(content, nestedTmpl); if (attr = /\s+[\w-]*\s*\=\s*\\['"]$/.exec(ast[i-1])) { error("'{{!' in attribute:\n..." + ast[i-1] + "{{!...\nUse data-link"); } code += 'view.renderLink(' + nested.length + ')+'; nestedTmpl.bound = TRUE; nestedTmpl.fn.attr = attr || "leaf"; nested.push(nestedTmpl); } else { if (content) { // Create template object for nested template nestedTmpl = TmplObject(markup, tmplOptions, tmpl, nested.length); // Compile to AST and then to compiled function buildCode(content, nestedTmpl); nested.push(nestedTmpl); } hasViewPath = hasViewPath || hash.indexOf("view") > -1; code += (tag === ":" ? (converter === "html" ? (hasEncoder = TRUE, "h(" + params) : converter ? (hasConverter = TRUE, 'c("' + converter + '",view,this,' + params) : (getsValue = TRUE, "((v=" + params + ')!=u?v:""') ) : (hasTag = TRUE, 't("' + tag + '",view,this,' + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template + "," + hash + (params ? "," : "") + params)) + ")+"; } } } } code = fnDeclStr + (getsValue ? "v," : "") + (hasTag ? "t=j._tag," : "") + (hasConverter ? "c=j._convert," : "") + (hasEncoder ? "h=j.converters.html," : "") + "ret; try{\n\n" + (tmplOptions.debug ? "debugger;" : "") + (allowCode ? 'ret=' : 'return ') + code.slice(0, -1) + ";\n\n" + (allowCode ? "return ret;" : "") + "}catch(e){return j._err(e);}"; try { code = new Function("data, view, j, b, u", code); } catch (e) { syntaxError("Compiled template code:\n\n" + code, e); } // Include only the var references that are needed in the code if (tmpl) { tmpl.fn = code; } return code; } function parseParams(params, bind, defer) { var named, fnCall = {}, parenDepth = 0, quoted = FALSE, // boolean for string content in double quotes aposed = FALSE; // or in single quotes function parseTokens(all, lftPrn0, lftPrn, path, operator, err, eq, path2, prn, comma, lftPrn2, apos, quot, rtPrn, prn2, space) { // rParams = /(?:([([])\s*)?(?:([#~]?[\w$.]+)?\s*((\+\+|--)|\+|-|&&|\|\||===|!==|==|!=|<=|>=|[<>%*!:?\/]|(=))\s*|([#~]?[\w$.^]+)([([])?)|(,\s*)|(\(?)\\?(?:(')|("))|(?:\s*([)\]])([([]?))|(\s+)/g, // lftPrn path operator err eq path2 prn comma lftPrn3 apos quot rtPrn prn2 space // (left paren? followed by (path? followed by operator) or (path followed by paren?)) or comma or apos or quot or right paren or space operator = operator || ""; lftPrn = lftPrn || lftPrn0 || lftPrn2; path = path || path2; prn = prn || prn2 || ""; operator = operator || ""; var bindParam = (bind || defer) && prn !== "("; function parsePath(all, object, helper, view, viewProperty, pathTokens, leafToken) { // rPath = /^(?:null|true|false|\d[\d.]*|([\w$]+|~([\w$]+)|#(view|([\w$]+))?)([\w$.]*?)(?:[.[]([\w$]+)\]?)?|(['"]).*\8)$/g, // object helper view viewProperty pathTokens leafToken string if (object) { var leaf, ret = (helper ? 'view._hlp("' + helper + '")' : view ? "view" : "data") + (leafToken ? (viewProperty ? "." + viewProperty : helper ? "" : (view ? "" : "." + object) ) + (pathTokens || "") : (leafToken = helper ? "" : view ? viewProperty || "" : object, "")); leaf = (leafToken ? "." + leafToken : ""); if (!bindParam) { ret = ret + leaf; } ret = ret.slice(0, 9) === "view.data" ? ret.slice(5) // convert #view.data... to data... : ret; if (bindParam) { ret = "b(" + ret + ',"' + leafToken + '")' + leaf; } return ret; } return all; } if (err) { syntaxError(params); } else { return (aposed // within single-quoted string ? (aposed = !apos, (aposed ? all : '"')) : quoted // within double-quoted string ? (quoted = !quot, (quoted ? all : '"')) : ( (lftPrn ? (parenDepth++, lftPrn) : "") + (space ? (parenDepth ? "" : named ? (named = FALSE, "\b") : "," ) : eq // named param ? (parenDepth && syntaxError(params), named = TRUE, '\b' + path + ':') : path // path ? (path.replace(rPath, parsePath) + (prn ? (fnCall[++parenDepth] = TRUE, prn) : operator) ) : operator ? operator : rtPrn // function ? ((fnCall[parenDepth--] = FALSE, rtPrn) + (prn ? (fnCall[++parenDepth] = TRUE, prn) : "") ) : comma ? (fnCall[parenDepth] || syntaxError(params), ",") // We don't allow top-level literal arrays or objects : lftPrn0 ? "" : (aposed = apos, quoted = quot, '"') )) ); } } params = (params + " ").replace(rParams, parseTokens); return params; } function compileNested(items, process, options) { var key, nestedItem; if (items) { for (key in items) { // compile nested template declarations nestedItem = items[key]; if (!nestedItem.is) { // Not yet compiled items[key] = process(nestedItem, key, options); } } } } function compile(tmpl, name, parent, options) { // tmpl is either a template object, a selector for a template script block, the name of a compiled template, or a template object // options is the set of template properties, c var tmplOrMarkup, elem; //==== nested functions ==== function tmplOrMarkupFromStr(value) { // If value is of type string - treat as selector, or name of compiled template // Return the template object, if already compiled, or the markup string if (("" + value === value) || value.nodeType > 0) { try { elem = value.nodeType > 0 ? value : !rTmplString.test(value) // If value is a string and does not contain HTML or tag content, then test as selector && jQuery && jQuery(value)[0]; // If selector is valid and returns at least one element, get first element // If invalid, jQuery will throw. We will stay with the original string. } catch (e) { } if (elem) { // Generally this is a script element. // However we allow it to be any element, so you can for example take the content of a div, // use it as a template, and replace it by the same content rendered against data. // e.g. for linking the content of a div to a container, and using the initial content as template: // $.link("#content", model, {tmpl: "#content"}); // Create a name for compiled template if none provided value = $templates[elem.getAttribute(tmplAttr)]; if (!value) { // Not already compiled and cached, so compile and cache the name name = name || "_" + autoTmplName++; elem.setAttribute(tmplAttr, name); value = compile(elem.innerHTML, name, parent, options); // Use tmpl as options $templates[name] = value; } } return value; } // If value is not a string, return undefined } //==== Compile the template ==== tmpl = tmpl || ""; tmplOrMarkup = tmplOrMarkupFromStr(tmpl); // If tmpl is a template object, use it for options options = options || (tmpl.markup ? tmpl : {}); options.name = name; options.is = "tmpl"; // If tmpl is not a markup string or a selector string, then it must be a template object // In that case, get it from the markup property of the object if (!tmplOrMarkup && tmpl.markup && (tmplOrMarkup = tmplOrMarkupFromStr(tmpl.markup))) { if (tmplOrMarkup.fn && (tmplOrMarkup.debug !== tmpl.debug || tmplOrMarkup.allowCode !== tmpl.allowCode)) { // if the string references a compiled template object, but the debug or allowCode props are different, need to recompile tmplOrMarkup = tmplOrMarkup.markup; } } if (tmplOrMarkup !== undefined) { if (name && !parent) { $render[name] = function() { return tmpl.render.apply(tmpl, arguments); }; } if (tmplOrMarkup.fn || tmpl.fn) { // tmpl is already compiled, so use it, or if different name is provided, clone it if (tmplOrMarkup.fn) { if (name && name !== tmplOrMarkup.name) { tmpl = $extend($extend({}, tmplOrMarkup), options); } else { tmpl = tmplOrMarkup; } } } else { // tmplOrMarkup is a markup string, not a compiled template // Create template object tmpl = TmplObject(tmplOrMarkup, options, parent, 0); // Compile to AST and then to compiled function tmplFn(tmplOrMarkup, tmpl); } compileNested(options.templates, compile, tmpl); compileNested(options.tags, compileTag); return tmpl; } } //==== /end of function compile ==== function TmplObject(markup, options, parent, key) { // Template object constructor // nested helper function function extendStore(storeName) { if (parent[storeName]) { // Include parent items except if overridden by item of same name in options tmpl[storeName] = $extend($extend({}, parent[storeName]), options[storeName]); } } options = options || {}; var tmpl = { markup: markup, tmpls: [], links: [], render: renderContent }; if (parent) { if (parent.templates) { tmpl.templates = $extend($extend({}, parent.templates), options.templates); } tmpl.parent = parent; tmpl.name = parent.name + "[" + key + "]"; tmpl.key = key; } $extend(tmpl, options); if (parent) { extendStore("templates"); extendStore("tags"); extendStore("helpers"); extendStore("converters"); } return tmpl; } //========================== Initialize ========================== if (jQuery) { //////////////////////////////////////////////////////////////////////////////////////////////// // jQuery is loaded, so make $ the jQuery object $ = jQuery; $.templates = $templates; $.render = $render; $.views = $views; $.fn.render = renderContent; } else { //////////////////////////////////////////////////////////////////////////////////////////////// // jQuery is not loaded. $ = global.jsviews = $views; $.extend = function(target, source) { var name; target = target || {}; for (name in source) { target[name] = source[name]; } return target; }; $.isArray = Array && Array.isArray || function(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; }; } $extend = $.extend; function replacerForHtml(ch) { // Original code from Mike Samuel return escapeMapForHtml[ch] // Intentional assignment that caches the result of encoding ch. || (escapeMapForHtml[ch] = "&#" + ch.charCodeAt(0) + ";"); } //========================== Register tags ========================== $viewsTags({ "if": function() { var ifTag = this, view = ifTag.view; view.onElse = function(tagInstance, args) { var i = 0, l = args.length; while (l && !args[i++]) { // Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey if (i === l) { return ""; } } view.onElse = undefined; // If condition satisfied, so won't run 'else'. tagInstance.path = ""; return tagInstance.renderContent(view); // Test is satisfied, so render content, while remaining in current data context // By passing the view, we inherit data context from the parent view, and the content is treated as a layout template // (so if the data is an array, it will not iterate over the data }; return view.onElse(this, arguments); }, "else": function() { var view = this.view; return view.onElse ? view.onElse(this, arguments) : ""; }, "for": function() { var i, self = this, result = "", args = arguments, l = args.length; if (l === 0) { // If no parameters, render once, with #data undefined l = 1; } for (i = 0; i < l; i++) { result += self.renderContent(args[i]); } return result; }, "*": function(value) { return value; } }); //========================== Register global helpers ========================== // $viewsHelpers({ // Global helper functions // // TODO add any useful built-in helper functions // }); //========================== Register converters ========================== $viewsConverters({ html: function(text) { // HTML encoding helper: Replace < > & and ' and " by corresponding entities. // inspired by Mike Samuel return text != undefined ? String(text).replace(htmlSpecialChar, replacerForHtml) : ""; } }); //========================== Define default delimiters ========================== $viewsDelimiters(); })(this, this.jQuery); BorisMoore-jsrender-734d3bd/test/000077500000000000000000000000001200734767300170105ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/test/perf-compare.html000066400000000000000000000152701200734767300222630ustar00rootroot00000000000000 Benchmark JsRender

Perf comparison

Rendered content:


Times in microseconds:
BorisMoore-jsrender-734d3bd/test/qunit/000077500000000000000000000000001200734767300201505ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/test/qunit/qunit.css000066400000000000000000000106501200734767300220240ustar00rootroot00000000000000/** * QUnit v1.3.0pre - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit * * Copyright (c) 2011 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) * or GPL (GPL-LICENSE.txt) licenses. */ /** Font Family and Sizes */ #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; } #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } #qunit-tests { font-size: smaller; } /** Resets */ #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { margin: 0; padding: 0; } /** Header */ #qunit-header { padding: 0.5em 0 0.5em 1em; color: #8699a4; background-color: #0d3349; font-size: 1.5em; line-height: 1em; font-weight: normal; border-radius: 15px 15px 0 0; -moz-border-radius: 15px 15px 0 0; -webkit-border-top-right-radius: 15px; -webkit-border-top-left-radius: 15px; } #qunit-header a { text-decoration: none; color: #c2ccd1; } #qunit-header a:hover, #qunit-header a:focus { color: #fff; } #qunit-banner { height: 5px; } #qunit-testrunner-toolbar { padding: 0.5em 0 0.5em 2em; color: #5E740B; background-color: #eee; } #qunit-userAgent { padding: 0.5em 0 0.5em 2.5em; background-color: #2b81af; color: #fff; text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } /** Tests: Pass/Fail */ #qunit-tests { list-style-position: inside; } #qunit-tests li { padding: 0.4em 0.5em 0.4em 2.5em; border-bottom: 1px solid #fff; list-style-position: inside; } #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { display: none; } #qunit-tests li strong { cursor: pointer; } #qunit-tests li a { padding: 0.5em; color: #c2ccd1; text-decoration: none; } #qunit-tests li a:hover, #qunit-tests li a:focus { color: #000; } #qunit-tests ol { margin-top: 0.5em; padding: 0.5em; background-color: #fff; border-radius: 15px; -moz-border-radius: 15px; -webkit-border-radius: 15px; box-shadow: inset 0px 2px 13px #999; -moz-box-shadow: inset 0px 2px 13px #999; -webkit-box-shadow: inset 0px 2px 13px #999; } #qunit-tests table { border-collapse: collapse; margin-top: .2em; } #qunit-tests th { text-align: right; vertical-align: top; padding: 0 .5em 0 0; } #qunit-tests td { vertical-align: top; } #qunit-tests pre { margin: 0; white-space: pre-wrap; word-wrap: break-word; } #qunit-tests del { background-color: #e0f2be; color: #374e0c; text-decoration: none; } #qunit-tests ins { background-color: #ffcaca; color: #500; text-decoration: none; } /*** Test Counts */ #qunit-tests b.counts { color: black; } #qunit-tests b.passed { color: #5E740B; } #qunit-tests b.failed { color: #710909; } #qunit-tests li li { margin: 0.5em; padding: 0.4em 0.5em 0.4em 0.5em; background-color: #fff; border-bottom: none; list-style-position: inside; } /*** Passing Styles */ #qunit-tests li li.pass { color: #5E740B; background-color: #fff; border-left: 26px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass .test-name { color: #366097; } #qunit-tests .pass .test-actual, #qunit-tests .pass .test-expected { color: #999999; } #qunit-banner.qunit-pass { background-color: #C6E746; } /*** Failing Styles */ #qunit-tests li li.fail { color: #710909; background-color: #fff; border-left: 26px solid #EE5757; white-space: pre; } #qunit-tests > li:last-child { border-radius: 0 0 15px 15px; -moz-border-radius: 0 0 15px 15px; -webkit-border-bottom-right-radius: 15px; -webkit-border-bottom-left-radius: 15px; } #qunit-tests .fail { color: #000000; background-color: #EE5757; } #qunit-tests .fail .test-name, #qunit-tests .fail .module-name { color: #000000; } #qunit-tests .fail .test-actual { color: #EE5757; } #qunit-tests .fail .test-expected { color: green; } #qunit-banner.qunit-fail { background-color: #EE5757; } /** Result */ #qunit-testresult { padding: 0.5em 0.5em 0.5em 2.5em; color: #2b81af; background-color: #D2E0E6; border-bottom: 1px solid white; } /** Fixture */ #qunit-fixture { position: absolute; top: -10000px; left: -10000px; } BorisMoore-jsrender-734d3bd/test/qunit/qunit.js000066400000000000000000001201661200734767300216540ustar00rootroot00000000000000/** * QUnit v1.3.0pre - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit * * Copyright (c) 2011 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) * or GPL (GPL-LICENSE.txt) licenses. */ (function(window) { var defined = { setTimeout: typeof window.setTimeout !== "undefined", sessionStorage: (function() { try { return !!sessionStorage.getItem; } catch(e) { return false; } })() }; var testId = 0, toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty; var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { this.name = name; this.testName = testName; this.expected = expected; this.testEnvironmentArg = testEnvironmentArg; this.async = async; this.callback = callback; this.assertions = []; }; Test.prototype = { init: function() { var tests = id("qunit-tests"); if (tests) { var b = document.createElement("strong"); b.innerHTML = "Running " + this.name; var li = document.createElement("li"); li.appendChild( b ); li.className = "running"; li.id = this.id = "test-output" + testId++; tests.appendChild( li ); } }, setup: function() { if (this.module != config.previousModule) { if ( config.previousModule ) { runLoggingCallbacks('moduleDone', QUnit, { name: config.previousModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all } ); } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0 }; runLoggingCallbacks( 'moduleStart', QUnit, { name: this.module } ); } config.current = this; this.testEnvironment = extend({ setup: function() {}, teardown: function() {} }, this.moduleTestEnvironment); if (this.testEnvironmentArg) { extend(this.testEnvironment, this.testEnvironmentArg); } runLoggingCallbacks( 'testStart', QUnit, { name: this.testName, module: this.module }); // allow utility functions to access the current test environment // TODO why?? QUnit.current_testEnvironment = this.testEnvironment; try { if ( !config.pollution ) { saveGlobal(); } this.testEnvironment.setup.call(this.testEnvironment); } catch(e) { QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); } }, run: function() { config.current = this; if ( this.async ) { QUnit.stop(); } if ( config.notrycatch ) { this.callback.call(this.testEnvironment); return; } try { this.callback.call(this.testEnvironment); } catch(e) { fail("Test " + this.testName + " died, exception and test follows", e, this.callback); QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); // else next test will carry the responsibility saveGlobal(); // Restart the tests if they're blocking if ( config.blocking ) { QUnit.start(); } } }, teardown: function() { config.current = this; try { this.testEnvironment.teardown.call(this.testEnvironment); checkPollution(); } catch(e) { QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); } }, finish: function() { config.current = this; if ( this.expected != null && this.expected != this.assertions.length ) { QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); } var good = 0, bad = 0, tests = id("qunit-tests"); config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; if ( tests ) { var ol = document.createElement("ol"); for ( var i = 0; i < this.assertions.length; i++ ) { var assertion = this.assertions[i]; var li = document.createElement("li"); li.className = assertion.result ? "pass" : "fail"; li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); ol.appendChild( li ); if ( assertion.result ) { good++; } else { bad++; config.stats.bad++; config.moduleStats.bad++; } } // store result when possible if ( QUnit.config.reorder && defined.sessionStorage ) { if (bad) { sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); } else { sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); } } if (bad == 0) { ol.style.display = "none"; } var b = document.createElement("strong"); b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; var a = document.createElement("a"); a.innerHTML = "Rerun"; a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); addEvent(b, "click", function() { var next = b.nextSibling.nextSibling, display = next.style.display; next.style.display = display === "none" ? "block" : "none"; }); addEvent(b, "dblclick", function(e) { var target = e && e.target ? e.target : window.event.srcElement; if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); } }); var li = id(this.id); li.className = bad ? "fail" : "pass"; li.removeChild( li.firstChild ); li.appendChild( b ); li.appendChild( a ); li.appendChild( ol ); } else { for ( var i = 0; i < this.assertions.length; i++ ) { if ( !this.assertions[i].result ) { bad++; config.stats.bad++; config.moduleStats.bad++; } } } try { QUnit.reset(); } catch(e) { fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); } runLoggingCallbacks( 'testDone', QUnit, { name: this.testName, module: this.module, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length } ); }, queue: function() { var test = this; synchronize(function() { test.init(); }); function run() { // each of these can by async synchronize(function() { test.setup(); }); synchronize(function() { test.run(); }); synchronize(function() { test.teardown(); }); synchronize(function() { test.finish(); }); } // defer when previous test run passed, if storage is available var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); if (bad) { run(); } else { synchronize(run, true); }; } }; var QUnit = { // call on start of module test to prepend name to all tests module: function(name, testEnvironment) { config.currentModule = name; config.currentModuleTestEnviroment = testEnvironment; }, asyncTest: function(testName, expected, callback) { if ( arguments.length === 2 ) { callback = expected; expected = null; } QUnit.test(testName, expected, callback, true); }, test: function(testName, expected, callback, async) { var name = '' + escapeInnerText(testName) + '', testEnvironmentArg; if ( arguments.length === 2 ) { callback = expected; expected = null; } // is 2nd argument a testEnvironment? if ( expected && typeof expected === 'object') { testEnvironmentArg = expected; expected = null; } if ( config.currentModule ) { name = '' + config.currentModule + ": " + name; } if ( !validTest(config.currentModule + ": " + testName) ) { return; } var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); test.module = config.currentModule; test.moduleTestEnvironment = config.currentModuleTestEnviroment; test.queue(); }, /** * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. */ expect: function(asserts) { config.current.expected = asserts; }, /** * Asserts true. * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ ok: function(a, msg) { a = !!a; var details = { result: a, message: msg }; msg = escapeInnerText(msg); runLoggingCallbacks( 'log', QUnit, details ); config.current.assertions.push({ result: a, message: msg }); }, /** * Checks that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. * * Prefered to ok( actual == expected, message ) * * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); * * @param Object actual * @param Object expected * @param String message (optional) */ equal: function(actual, expected, message) { QUnit.push(expected == actual, actual, expected, message); }, notEqual: function(actual, expected, message) { QUnit.push(expected != actual, actual, expected, message); }, deepEqual: function(actual, expected, message) { QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); }, notDeepEqual: function(actual, expected, message) { QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); }, strictEqual: function(actual, expected, message) { QUnit.push(expected === actual, actual, expected, message); }, notStrictEqual: function(actual, expected, message) { QUnit.push(expected !== actual, actual, expected, message); }, raises: function(block, expected, message) { var actual, ok = false; if (typeof expected === 'string') { message = expected; expected = null; } try { block(); } catch (e) { actual = e; } if (actual) { // we don't want to validate thrown error if (!expected) { ok = true; // expected is a regexp } else if (QUnit.objectType(expected) === "regexp") { ok = expected.test(actual); // expected is a constructor } else if (actual instanceof expected) { ok = true; // expected is a validation function which returns true is validation passed } else if (expected.call({}, actual) === true) { ok = true; } } QUnit.ok(ok, message); }, start: function(count) { config.semaphore -= count || 1; if (config.semaphore > 0) { // don't start until equal number of stop-calls return; } if (config.semaphore < 0) { // ignore if start is called more often then stop config.semaphore = 0; } // A slight delay, to avoid any current callbacks if ( defined.setTimeout ) { window.setTimeout(function() { if (config.semaphore > 0) { return; } if ( config.timeout ) { clearTimeout(config.timeout); } config.blocking = false; process(true); }, 13); } else { config.blocking = false; process(true); } }, stop: function(count) { config.semaphore += count || 1; config.blocking = true; if ( config.testTimeout && defined.setTimeout ) { clearTimeout(config.timeout); config.timeout = window.setTimeout(function() { QUnit.ok( false, "Test timed out" ); config.semaphore = 1; QUnit.start(); }, config.testTimeout); } } }; //We want access to the constructor's prototype (function() { function F(){}; F.prototype = QUnit; QUnit = new F(); //Make F QUnit's constructor so that we can add to the prototype later QUnit.constructor = F; })(); // Backwards compatibility, deprecated QUnit.equals = QUnit.equal; QUnit.same = QUnit.deepEqual; // Maintain internal state var config = { // The queue of tests to run queue: [], // block until document ready blocking: true, // when enabled, show only failing tests // gets persisted through sessionStorage and can be changed in UI via checkbox hidepassed: false, // by default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, // by default, modify document.title when suite is done altertitle: true, urlConfig: ['noglobals', 'notrycatch'], //logging callback queues begin: [], done: [], log: [], testStart: [], testDone: [], moduleStart: [], moduleDone: [] }; // Load paramaters (function() { var location = window.location || { search: "", protocol: "file:" }, params = location.search.slice( 1 ).split( "&" ), length = params.length, urlParams = {}, current; if ( params[ 0 ] ) { for ( var i = 0; i < length; i++ ) { current = params[ i ].split( "=" ); current[ 0 ] = decodeURIComponent( current[ 0 ] ); // allow just a key to turn on a flag, e.g., test.html?noglobals current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; urlParams[ current[ 0 ] ] = current[ 1 ]; } } QUnit.urlParams = urlParams; config.filter = urlParams.filter; // Figure out if we're running the tests from a server or not QUnit.isLocal = !!(location.protocol === 'file:'); })(); // Expose the API as global variables, unless an 'exports' // object exists, in that case we assume we're in CommonJS if ( typeof exports === "undefined" || typeof require === "undefined" ) { extend(window, QUnit); window.QUnit = QUnit; } else { extend(exports, QUnit); exports.QUnit = QUnit; } // define these after exposing globals to keep them in these QUnit namespace only extend(QUnit, { config: config, // Initialize the configuration options init: function() { extend(config, { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, started: +new Date, updateRate: 1000, blocking: false, autostart: true, autorun: false, filter: "", queue: [], semaphore: 0 }); var tests = id( "qunit-tests" ), banner = id( "qunit-banner" ), result = id( "qunit-testresult" ); if ( tests ) { tests.innerHTML = ""; } if ( banner ) { banner.className = ""; } if ( result ) { result.parentNode.removeChild( result ); } if ( tests ) { result = document.createElement( "p" ); result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests ); result.innerHTML = 'Running...
 '; } }, /** * Resets the test setup. Useful for tests that modify the DOM. * * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. */ reset: function() { if ( window.jQuery ) { jQuery( "#qunit-fixture" ).html( config.fixture ); } else { var main = id( 'qunit-fixture' ); if ( main ) { main.innerHTML = config.fixture; } } }, /** * Trigger an event on an element. * * @example triggerEvent( document.body, "click" ); * * @param DOMElement elem * @param String type */ triggerEvent: function( elem, type, event ) { if ( document.createEvent ) { event = document.createEvent("MouseEvents"); event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); elem.dispatchEvent( event ); } else if ( elem.fireEvent ) { elem.fireEvent("on"+type); } }, // Safe object type checking is: function( type, obj ) { return QUnit.objectType( obj ) == type; }, objectType: function( obj ) { if (typeof obj === "undefined") { return "undefined"; // consider: typeof null === object } if (obj === null) { return "null"; } var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; switch (type) { case 'Number': if (isNaN(obj)) { return "nan"; } else { return "number"; } case 'String': case 'Boolean': case 'Array': case 'Date': case 'RegExp': case 'Function': return type.toLowerCase(); } if (typeof obj === "object") { return "object"; } return undefined; }, push: function(result, actual, expected, message) { var details = { result: result, message: message, actual: actual, expected: expected }; message = escapeInnerText(message) || (result ? "okay" : "failed"); message = '' + message + ""; expected = escapeInnerText(QUnit.jsDump.parse(expected)); actual = escapeInnerText(QUnit.jsDump.parse(actual)); var output = message + ''; if (actual != expected) { output += ''; output += ''; } if (!result) { var source = sourceFromStacktrace(); if (source) { details.source = source; output += ''; } } output += "
Expected:
' + expected + '
Result:
' + actual + '
Diff:
' + QUnit.diff(expected, actual) +'
Source:
' + escapeInnerText(source) + '
"; runLoggingCallbacks( 'log', QUnit, details ); config.current.assertions.push({ result: !!result, message: output }); }, url: function( params ) { params = extend( extend( {}, QUnit.urlParams ), params ); var querystring = "?", key; for ( key in params ) { if ( !hasOwn.call( params, key ) ) { continue; } querystring += encodeURIComponent( key ) + "=" + encodeURIComponent( params[ key ] ) + "&"; } return window.location.pathname + querystring.slice( 0, -1 ); }, extend: extend, id: id, addEvent: addEvent }); //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later //Doing this allows us to tell if the following methods have been overwritten on the actual //QUnit object, which is a deprecated way of using the callbacks. extend(QUnit.constructor.prototype, { // Logging callbacks; all receive a single argument with the listed properties // run test/logs.html for any related changes begin: registerLoggingCallback('begin'), // done: { failed, passed, total, runtime } done: registerLoggingCallback('done'), // log: { result, actual, expected, message } log: registerLoggingCallback('log'), // testStart: { name } testStart: registerLoggingCallback('testStart'), // testDone: { name, failed, passed, total } testDone: registerLoggingCallback('testDone'), // moduleStart: { name } moduleStart: registerLoggingCallback('moduleStart'), // moduleDone: { name, failed, passed, total } moduleDone: registerLoggingCallback('moduleDone') }); if ( typeof document === "undefined" || document.readyState === "complete" ) { config.autorun = true; } QUnit.load = function() { runLoggingCallbacks( 'begin', QUnit, {} ); // Initialize the config, saving the execution queue var oldconfig = extend({}, config); QUnit.init(); extend(config, oldconfig); config.blocking = false; var urlConfigHtml = '', len = config.urlConfig.length; for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { config[val] = QUnit.urlParams[val]; urlConfigHtml += ''; } var userAgent = id("qunit-userAgent"); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } var banner = id("qunit-header"); if ( banner ) { banner.innerHTML = ' ' + banner.innerHTML + ' ' + urlConfigHtml; addEvent( banner, "change", function( event ) { var params = {}; params[ event.target.name ] = event.target.checked ? true : undefined; window.location = QUnit.url( params ); }); } var toolbar = id("qunit-testrunner-toolbar"); if ( toolbar ) { var filter = document.createElement("input"); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; addEvent( filter, "click", function() { var ol = document.getElementById("qunit-tests"); if ( filter.checked ) { ol.className = ol.className + " hidepass"; } else { var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; ol.className = tmp.replace(/ hidepass /, " "); } if ( defined.sessionStorage ) { if (filter.checked) { sessionStorage.setItem("qunit-filter-passed-tests", "true"); } else { sessionStorage.removeItem("qunit-filter-passed-tests"); } } }); if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { filter.checked = true; var ol = document.getElementById("qunit-tests"); ol.className = ol.className + " hidepass"; } toolbar.appendChild( filter ); var label = document.createElement("label"); label.setAttribute("for", "qunit-filter-pass"); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); } var main = id('qunit-fixture'); if ( main ) { config.fixture = main.innerHTML; } if (config.autostart) { QUnit.start(); } }; addEvent(window, "load", QUnit.load); // addEvent(window, "error") gives us a useless event object window.onerror = function( message, file, line ) { if ( QUnit.config.current ) { ok( false, message + ", " + file + ":" + line ); } else { test( "global failure", function() { ok( false, message + ", " + file + ":" + line ); }); } }; function done() { config.autorun = true; // Log the last module results if ( config.currentModule ) { runLoggingCallbacks( 'moduleDone', QUnit, { name: config.currentModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all } ); } var banner = id("qunit-banner"), tests = id("qunit-tests"), runtime = +new Date - config.started, passed = config.stats.all - config.stats.bad, html = [ 'Tests completed in ', runtime, ' milliseconds.
', '', passed, ' tests of ', config.stats.all, ' passed, ', config.stats.bad, ' failed.' ].join(''); if ( banner ) { banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); } if ( tests ) { id( "qunit-testresult" ).innerHTML = html; } if ( config.altertitle && typeof document !== "undefined" && document.title ) { // show ✖ for good, ✔ for bad suite result in title // use escape sequences in case file gets loaded with non-utf-8-charset document.title = [ (config.stats.bad ? "\u2716" : "\u2714"), document.title.replace(/^[\u2714\u2716] /i, "") ].join(" "); } runLoggingCallbacks( 'done', QUnit, { failed: config.stats.bad, passed: passed, total: config.stats.all, runtime: runtime } ); } function validTest( name ) { var filter = config.filter, run = false; if ( !filter ) { return true; } var not = filter.charAt( 0 ) === "!"; if ( not ) { filter = filter.slice( 1 ); } if ( name.indexOf( filter ) !== -1 ) { return !not; } if ( not ) { run = true; } return run; } // so far supports only Firefox, Chrome and Opera (buggy) // could be extended in the future to use something like https://github.com/csnover/TraceKit function sourceFromStacktrace() { try { throw new Error(); } catch ( e ) { if (e.stacktrace) { // Opera return e.stacktrace.split("\n")[6]; } else if (e.stack) { // Firefox, Chrome return e.stack.split("\n")[4]; } else if (e.sourceURL) { // Safari, PhantomJS // TODO sourceURL points at the 'throw new Error' line above, useless //return e.sourceURL + ":" + e.line; } } } function escapeInnerText(s) { if (!s) { return ""; } s = s + ""; return s.replace(/[\&<>]/g, function(s) { switch(s) { case "&": return "&"; case "<": return "<"; case ">": return ">"; default: return s; } }); } function synchronize( callback, last ) { config.queue.push( callback ); if ( config.autorun && !config.blocking ) { process(last); } } function process( last ) { var start = new Date().getTime(); config.depth = config.depth ? config.depth + 1 : 1; while ( config.queue.length && !config.blocking ) { if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { config.queue.shift()(); } else { window.setTimeout( function(){ process( last ); }, 13 ); break; } } config.depth--; if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { done(); } } function saveGlobal() { config.pollution = []; if ( config.noglobals ) { for ( var key in window ) { if ( !hasOwn.call( window, key ) ) { continue; } config.pollution.push( key ); } } } function checkPollution( name ) { var old = config.pollution; saveGlobal(); var newGlobals = diff( config.pollution, old ); if ( newGlobals.length > 0 ) { ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); } var deletedGlobals = diff( old, config.pollution ); if ( deletedGlobals.length > 0 ) { ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { var result = a.slice(); for ( var i = 0; i < result.length; i++ ) { for ( var j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { result.splice(i, 1); i--; break; } } } return result; } function fail(message, exception, callback) { if ( typeof console !== "undefined" && console.error && console.warn ) { console.error(message); console.error(exception); console.error(exception.stack); console.warn(callback.toString()); } else if ( window.opera && opera.postError ) { opera.postError(message, exception, callback.toString); } } function extend(a, b) { for ( var prop in b ) { if ( b[prop] === undefined ) { delete a[prop]; // Avoid "Member not found" error in IE8 caused by setting window.constructor } else if ( prop !== "constructor" || a !== window ) { a[prop] = b[prop]; } } return a; } function addEvent(elem, type, fn) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, fn ); } else { fn(); } } function id(name) { return !!(typeof document !== "undefined" && document && document.getElementById) && document.getElementById( name ); } function registerLoggingCallback(key){ return function(callback){ config[key].push( callback ); }; } // Supports deprecated method of completely overwriting logging callbacks function runLoggingCallbacks(key, scope, args) { //debugger; var callbacks; if ( QUnit.hasOwnProperty(key) ) { QUnit[key].call(scope, args); } else { callbacks = config[key]; for( var i = 0; i < callbacks.length; i++ ) { callbacks[i].call( scope, args ); } } } // Test for equality any JavaScript type. // Author: Philippe Rathé QUnit.equiv = function () { var innerEquiv; // the real equiv function var callers = []; // stack to decide between skip/abort functions var parents = []; // stack to avoiding loops from circular referencing // Call the o related callback with the given arguments. function bindCallbacks(o, callbacks, args) { var prop = QUnit.objectType(o); if (prop) { if (QUnit.objectType(callbacks[prop]) === "function") { return callbacks[prop].apply(callbacks, args); } else { return callbacks[prop]; // or undefined } } } var getProto = Object.getPrototypeOf || function (obj) { return obj.__proto__; }; var callbacks = function () { // for string, boolean, number and null function useStrictEquality(b, a) { if (b instanceof a.constructor || a instanceof b.constructor) { // to catch short annotaion VS 'new' annotation of a // declaration // e.g. var i = 1; // var j = new Number(1); return a == b; } else { return a === b; } } return { "string" : useStrictEquality, "boolean" : useStrictEquality, "number" : useStrictEquality, "null" : useStrictEquality, "undefined" : useStrictEquality, "nan" : function(b) { return isNaN(b); }, "date" : function(b, a) { return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); }, "regexp" : function(b, a) { return QUnit.objectType(b) === "regexp" && a.source === b.source && // the regex itself a.global === b.global && // and its modifers // (gmi) ... a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; }, // - skip when the property is a method of an instance (OOP) // - abort otherwise, // initial === would have catch identical references anyway "function" : function() { var caller = callers[callers.length - 1]; return caller !== Object && typeof caller !== "undefined"; }, "array" : function(b, a) { var i, j, loop; var len; // b could be an object literal here if (!(QUnit.objectType(b) === "array")) { return false; } len = a.length; if (len !== b.length) { // safe and faster return false; } // track reference to avoid circular references parents.push(a); for (i = 0; i < len; i++) { loop = false; for (j = 0; j < parents.length; j++) { if (parents[j] === a[i]) { loop = true;// dont rewalk array } } if (!loop && !innerEquiv(a[i], b[i])) { parents.pop(); return false; } } parents.pop(); return true; }, "object" : function(b, a) { var i, j, loop; var eq = true; // unless we can proove it var aProperties = [], bProperties = []; // collection of // strings // comparing constructors is more strict than using // instanceof if (a.constructor !== b.constructor) { // Allow objects with no prototype to be equivalent to // objects with Object as their constructor. if (!((getProto(a) === null && getProto(b) === Object.prototype) || (getProto(b) === null && getProto(a) === Object.prototype))) { return false; } } // stack constructor before traversing properties callers.push(a.constructor); // track reference to avoid circular references parents.push(a); for (i in a) { // be strict: don't ensures hasOwnProperty // and go deep loop = false; for (j = 0; j < parents.length; j++) { if (parents[j] === a[i]) loop = true; // don't go down the same path // twice } aProperties.push(i); // collect a's properties if (!loop && !innerEquiv(a[i], b[i])) { eq = false; break; } } callers.pop(); // unstack, we are done parents.pop(); for (i in b) { bProperties.push(i); // collect b's properties } // Ensures identical properties name return eq && innerEquiv(aProperties.sort(), bProperties .sort()); } }; }(); innerEquiv = function() { // can take multiple arguments var args = Array.prototype.slice.apply(arguments); if (args.length < 2) { return true; // end transition } return (function(a, b) { if (a === b) { return true; // catch the most you can } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) { return false; // don't lose time with error prone cases } else { return bindCallbacks(a, callbacks, [ b, a ]); } // apply transition with (1..n) arguments })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1)); }; return innerEquiv; }(); /** * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | * http://flesler.blogspot.com Licensed under BSD * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 * * @projectDescription Advanced and extensible data dumping for Javascript. * @version 1.0.0 * @author Ariel Flesler * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} */ QUnit.jsDump = (function() { function quote( str ) { return '"' + str.toString().replace(/"/g, '\\"') + '"'; }; function literal( o ) { return o + ''; }; function join( pre, arr, post ) { var s = jsDump.separator(), base = jsDump.indent(), inner = jsDump.indent(1); if ( arr.join ) arr = arr.join( ',' + s + inner ); if ( !arr ) return pre + post; return [ pre, inner + arr, base + post ].join(s); }; function array( arr, stack ) { var i = arr.length, ret = Array(i); this.up(); while ( i-- ) ret[i] = this.parse( arr[i] , undefined , stack); this.down(); return join( '[', ret, ']' ); }; var reName = /^function (\w+)/; var jsDump = { parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance stack = stack || [ ]; var parser = this.parsers[ type || this.typeOf(obj) ]; type = typeof parser; var inStack = inArray(obj, stack); if (inStack != -1) { return 'recursion('+(inStack - stack.length)+')'; } //else if (type == 'function') { stack.push(obj); var res = parser.call( this, obj, stack ); stack.pop(); return res; } // else return (type == 'string') ? parser : this.parsers.error; }, typeOf:function( obj ) { var type; if ( obj === null ) { type = "null"; } else if (typeof obj === "undefined") { type = "undefined"; } else if (QUnit.is("RegExp", obj)) { type = "regexp"; } else if (QUnit.is("Date", obj)) { type = "date"; } else if (QUnit.is("Function", obj)) { type = "function"; } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { type = "window"; } else if (obj.nodeType === 9) { type = "document"; } else if (obj.nodeType) { type = "node"; } else if ( // native arrays toString.call( obj ) === "[object Array]" || // NodeList objects ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) ) { type = "array"; } else { type = typeof obj; } return type; }, separator:function() { return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; }, indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing if ( !this.multiline ) return ''; var chr = this.indentChar; if ( this.HTML ) chr = chr.replace(/\t/g,' ').replace(/ /g,' '); return Array( this._depth_ + (extra||0) ).join(chr); }, up:function( a ) { this._depth_ += a || 1; }, down:function( a ) { this._depth_ -= a || 1; }, setParser:function( name, parser ) { this.parsers[name] = parser; }, // The next 3 are exposed so you can use them quote:quote, literal:literal, join:join, // _depth_: 1, // This is the list of parsers, to modify them, use jsDump.setParser parsers:{ window: '[Window]', document: '[Document]', error:'[ERROR]', //when no parser is found, shouldn't happen unknown: '[Unknown]', 'null':'null', 'undefined':'undefined', 'function':function( fn ) { var ret = 'function', name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE if ( name ) ret += ' ' + name; ret += '('; ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); }, array: array, nodelist: array, arguments: array, object:function( map, stack ) { var ret = [ ]; QUnit.jsDump.up(); for ( var key in map ) { var val = map[key]; ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); } QUnit.jsDump.down(); return join( '{', ret, '}' ); }, node:function( node ) { var open = QUnit.jsDump.HTML ? '<' : '<', close = QUnit.jsDump.HTML ? '>' : '>'; var tag = node.nodeName.toLowerCase(), ret = open + tag; for ( var a in QUnit.jsDump.DOMAttrs ) { var val = node[QUnit.jsDump.DOMAttrs[a]]; if ( val ) ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); } return ret + close + open + '/' + tag + close; }, functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function var l = fn.length; if ( !l ) return ''; var args = Array(l); while ( l-- ) args[l] = String.fromCharCode(97+l);//97 is 'a' return ' ' + args.join(', ') + ' '; }, key:quote, //object calls it internally, the key part of an item in a map functionCode:'[code]', //function calls it internally, it's the content of the function attribute:quote, //node calls it internally, it's an html attribute value string:quote, date:quote, regexp:literal, //regex number:literal, 'boolean':literal }, DOMAttrs:{//attributes to dump from nodes, name=>realName id:'id', name:'name', 'class':'className' }, HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) indentChar:' ',//indentation unit multiline:true //if true, items in a collection, are separated by a \n, else just a space. }; return jsDump; })(); // from Sizzle.js function getText( elems ) { var ret = "", elem; for ( var i = 0; elems[i]; i++ ) { elem = elems[i]; // Get the text from text nodes and CDATA nodes if ( elem.nodeType === 3 || elem.nodeType === 4 ) { ret += elem.nodeValue; // Traverse everything else, except comment nodes } else if ( elem.nodeType !== 8 ) { ret += getText( elem.childNodes ); } } return ret; }; //from jquery.js function inArray( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[ i ] === elem ) { return i; } } return -1; } /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) * Modified by Chu Alan "sprite" * * Released under the MIT license. * * More Info: * http://ejohn.org/projects/javascript-diff-algorithm/ * * Usage: QUnit.diff(expected, actual) * * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { function diff(o, n) { var ns = {}; var os = {}; for (var i = 0; i < n.length; i++) { if (ns[n[i]] == null) ns[n[i]] = { rows: [], o: null }; ns[n[i]].rows.push(i); } for (var i = 0; i < o.length; i++) { if (os[o[i]] == null) os[o[i]] = { rows: [], n: null }; os[o[i]].rows.push(i); } for (var i in ns) { if ( !hasOwn.call( ns, i ) ) { continue; } if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { n[ns[i].rows[0]] = { text: n[ns[i].rows[0]], row: os[i].rows[0] }; o[os[i].rows[0]] = { text: o[os[i].rows[0]], row: ns[i].rows[0] }; } } for (var i = 0; i < n.length - 1; i++) { if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && n[i + 1] == o[n[i].row + 1]) { n[i + 1] = { text: n[i + 1], row: n[i].row + 1 }; o[n[i].row + 1] = { text: o[n[i].row + 1], row: i + 1 }; } } for (var i = n.length - 1; i > 0; i--) { if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && n[i - 1] == o[n[i].row - 1]) { n[i - 1] = { text: n[i - 1], row: n[i].row - 1 }; o[n[i].row - 1] = { text: o[n[i].row - 1], row: i - 1 }; } } return { o: o, n: n }; } return function(o, n) { o = o.replace(/\s+$/, ''); n = n.replace(/\s+$/, ''); var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); var str = ""; var oSpace = o.match(/\s+/g); if (oSpace == null) { oSpace = [" "]; } else { oSpace.push(" "); } var nSpace = n.match(/\s+/g); if (nSpace == null) { nSpace = [" "]; } else { nSpace.push(" "); } if (out.n.length == 0) { for (var i = 0; i < out.o.length; i++) { str += '' + out.o[i] + oSpace[i] + ""; } } else { if (out.n[0].text == null) { for (n = 0; n < out.o.length && out.o[n].text == null; n++) { str += '' + out.o[n] + oSpace[n] + ""; } } for (var i = 0; i < out.n.length; i++) { if (out.n[i].text == null) { str += '' + out.n[i] + nSpace[i] + ""; } else { var pre = ""; for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { pre += '' + out.o[n] + oSpace[n] + ""; } str += " " + out.n[i].text + nSpace[i] + pre; } } } return str; }; })(); })(this); BorisMoore-jsrender-734d3bd/test/resources/000077500000000000000000000000001200734767300210225ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/test/resources/dot.js000066400000000000000000000053631200734767300221550ustar00rootroot00000000000000// doT.js // 2011, Laura Doktorova // https://github.com/olado/doT // // doT is a custom blend of templating functions from jQote2.js // (jQuery plugin) by aefxx (http://aefxx.com/jquery-plugins/jqote2/) // and underscore.js (http://documentcloud.github.com/underscore/) // plus extensions. // // Licensed under the MIT license. // (function() { var doT = { version : '0.1.5' }; if (typeof module !== 'undefined' && module.exports) { module.exports = doT; } else { this.doT = doT; } doT.templateSettings = { evaluate: /\{\{([\s\S]+?)\}\}/g, interpolate: /\{\{=([\s\S]+?)\}\}/g, encode: /\{\{!([\s\S]+?)\}\}/g, use: /\{\{#([\s\S]+?)\}\}/g, //compile time evaluation define: /\{\{#\s*([\w$]+)\s*\:([\s\S]+?)#\}\}/g, //compile time defs varname: 'it', strip : true, append: true }; function resolveDefs(define, use, str, defs) { return str.replace(define, function (match, code, value) { if (!(code in defs)) defs[code]=value; return ''; }) .replace(use, function(match, code) { var value; // todo: detect circular use and convert into compiled functions with(defs) {try { value = eval(code);} catch(e) { value='';} } return value ? resolveDefs(define, use, value.toString(), defs) : value; }); } doT.template = function(tmpl, c, defs) { c = c || doT.templateSettings; var cstart = c.append ? "'+(" : "';out+=(", // optimal choice depends on platform/size of templates cend = c.append ? ")+'" : ");out+='"; var str = (c.use || c.define) ? resolveDefs(c.define, c.use, tmpl, defs || {}) : tmpl; str = ("var out='" + ((c.strip) ? str.replace(/\s*\s*|[\r\n\t]|(\/\*[\s\S]*?\*\/)/g, ''): str) .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(c.interpolate, function(match, code) { return cstart + code.replace(/\\'/g, "'").replace(/\\\\/g,"\\").replace(/[\r\t\n]/g, ' ') + cend; }) .replace(c.encode, function(match, code) { return cstart + code.replace(/\\'/g, "'").replace(/\\\\/g, "\\").replace(/[\r\t\n]/g, ' ') + ").toString().replace(/&(?!\\w+;)/g, '&').split('<').join('<').split('>').join('>').split('" + '"' + "').join('"').split(" + '"' + "'" + '"' + ").join(''').split('/').join('/'" + cend; }) .replace(c.evaluate, function(match, code) { return "';" + code.replace(/\\'/g, "'").replace(/\\\\/g,"\\").replace(/[\r\t\n]/g, ' ') + "out+='"; }) + "';return out;") .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') .replace(/\r/g, '\\r') .split("out+='';").join('') .split("var out='';out+=").join('var out='); try { return new Function(c.varname, str); } catch (e) { if (typeof console !== 'undefined') console.log("Could not create a template function: " + str); throw e; } }; }());BorisMoore-jsrender-734d3bd/test/resources/handlebars.js000066400000000000000000001410421200734767300234650ustar00rootroot00000000000000// lib/handlebars/base.js var Handlebars = {}; Handlebars.VERSION = "1.0.beta.6"; 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) { for(var i=0, j=context.length; i 0) { for(var i=0, j=context.length; i 2) { expected.push("'" + this.terminals_[p] + "'"); } var errStr = ""; if (this.lexer.showPosition) { errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + this.terminals_[symbol] + "'"; } else { errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); } this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); } } if (action[0] instanceof Array && action.length > 1) { throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); } switch (action[0]) { case 1: stack.push(symbol); vstack.push(this.lexer.yytext); lstack.push(this.lexer.yylloc); stack.push(action[1]); symbol = null; if (!preErrorSymbol) { yyleng = this.lexer.yyleng; yytext = this.lexer.yytext; yylineno = this.lexer.yylineno; yyloc = this.lexer.yylloc; if (recovering > 0) recovering--; } else { symbol = preErrorSymbol; preErrorSymbol = null; } break; case 2: len = this.productions_[action[1]][1]; yyval.$ = vstack[vstack.length - len]; yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); if (typeof r !== "undefined") { return r; } if (len) { stack = stack.slice(0, -1 * len * 2); vstack = vstack.slice(0, -1 * len); lstack = lstack.slice(0, -1 * len); } stack.push(this.productions_[action[1]][0]); vstack.push(yyval.$); lstack.push(yyval._$); newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; stack.push(newState); break; case 3: return true; } } return true; } };/* Jison generated lexer */ var lexer = (function(){ var lexer = ({EOF:1, parseError:function parseError(str, hash) { if (this.yy.parseError) { this.yy.parseError(str, hash); } else { throw new Error(str); } }, setInput:function (input) { this._input = input; this._more = this._less = this.done = false; this.yylineno = this.yyleng = 0; this.yytext = this.matched = this.match = ''; this.conditionStack = ['INITIAL']; this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; return this; }, input:function () { var ch = this._input[0]; this.yytext+=ch; this.yyleng++; this.match+=ch; this.matched+=ch; var lines = ch.match(/\n/); if (lines) this.yylineno++; this._input = this._input.slice(1); return ch; }, unput:function (ch) { this._input = ch + this._input; return this; }, more:function () { this._more = true; return this; }, pastInput:function () { var past = this.matched.substr(0, this.matched.length - this.match.length); return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); }, upcomingInput:function () { var next = this.match; if (next.length < 20) { next += this._input.substr(0, 20-next.length); } return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); }, showPosition:function () { var pre = this.pastInput(); var c = new Array(pre.length + 1).join("-"); return pre + this.upcomingInput() + "\n" + c+"^"; }, next:function () { if (this.done) { return this.EOF; } if (!this._input) this.done = true; var token, match, col, lines; if (!this._more) { this.yytext = ''; this.match = ''; } var rules = this._currentRules(); for (var i=0;i < rules.length; i++) { match = this._input.match(this.rules[rules[i]]); if (match) { lines = match[0].match(/\n.*/g); if (lines) this.yylineno += lines.length; this.yylloc = {first_line: this.yylloc.last_line, last_line: this.yylineno+1, first_column: this.yylloc.last_column, last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} this.yytext += match[0]; this.match += match[0]; this.matches = match; this.yyleng = this.yytext.length; this._more = false; this._input = this._input.slice(match[0].length); this.matched += match[0]; token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); if (token) return token; else return; } } if (this._input === "") { return this.EOF; } else { this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), {text: "", token: null, line: this.yylineno}); } }, lex:function lex() { var r = this.next(); if (typeof r !== 'undefined') { return r; } else { return this.lex(); } }, begin:function begin(condition) { this.conditionStack.push(condition); }, popState:function popState() { return this.conditionStack.pop(); }, _currentRules:function _currentRules() { return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; }, topState:function () { return this.conditionStack[this.conditionStack.length-2]; }, pushState:function begin(condition) { this.begin(condition); }}); lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { var YYSTATE=YY_START switch($avoiding_name_collisions) { case 0: if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); if(yy_.yytext) return 14; break; case 1: return 14; break; case 2: this.popState(); return 14; break; case 3: return 24; break; case 4: return 16; break; case 5: return 20; break; case 6: return 19; break; case 7: return 19; break; case 8: return 23; break; case 9: return 23; break; case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; break; case 11: return 22; break; case 12: return 34; break; case 13: return 33; break; case 14: return 33; break; case 15: return 36; break; case 16: /*ignore whitespace*/ break; case 17: this.popState(); return 18; break; case 18: this.popState(); return 18; break; case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28; break; case 20: return 30; break; case 21: return 30; break; case 22: return 29; break; case 23: return 33; break; case 24: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 33; break; case 25: return 'INVALID'; break; case 26: return 5; break; } }; lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^[^\x00]{2,}?(?=(\{\{))/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[\/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s\/.])/,/^\[[^\]]*\]/,/^./,/^$/]; lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,26],"inclusive":true}};return lexer;})() parser.lexer = lexer; return parser; })(); if (typeof require !== 'undefined' && typeof exports !== 'undefined') { exports.parser = handlebars; exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); } exports.main = function commonjsMain(args) { if (!args[1]) throw new Error('Usage: '+args[0]+' FILE'); if (typeof process !== 'undefined') { var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8"); } else { var cwd = require("file").path(require("file").cwd()); var source = cwd.join(args[1]).read({charset: "utf-8"}); } return exports.parser.parse(source); } if (typeof module !== 'undefined' && require.main === module) { exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args); } }; ; // lib/handlebars/compiler/base.js Handlebars.Parser = handlebars; Handlebars.parse = function(string) { Handlebars.Parser.yy = Handlebars.AST; return Handlebars.Parser.parse(string); }; Handlebars.print = function(ast) { return new Handlebars.PrintVisitor().accept(ast); }; Handlebars.logger = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, // override in the host environment log: function(level, str) {} }; Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); }; ; // lib/handlebars/compiler/ast.js (function() { Handlebars.AST = {}; Handlebars.AST.ProgramNode = function(statements, inverse) { this.type = "program"; this.statements = statements; if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } }; Handlebars.AST.MustacheNode = function(params, hash, unescaped) { this.type = "mustache"; this.id = params[0]; this.params = params.slice(1); this.hash = hash; this.escaped = !unescaped; }; Handlebars.AST.PartialNode = function(id, context) { this.type = "partial"; // TODO: disallow complex IDs this.id = id; this.context = context; }; var verifyMatch = function(open, close) { if(open.original !== close.original) { throw new Handlebars.Exception(open.original + " doesn't match " + close.original); } }; Handlebars.AST.BlockNode = function(mustache, program, close) { verifyMatch(mustache.id, close); this.type = "block"; this.mustache = mustache; this.program = program; }; Handlebars.AST.InverseNode = function(mustache, program, close) { verifyMatch(mustache.id, close); this.type = "inverse"; this.mustache = mustache; this.program = program; }; Handlebars.AST.ContentNode = function(string) { this.type = "content"; this.string = string; }; Handlebars.AST.HashNode = function(pairs) { this.type = "hash"; this.pairs = pairs; }; Handlebars.AST.IdNode = function(parts) { this.type = "ID"; this.original = parts.join("."); var dig = [], depth = 0; for(var i=0,l=parts.length; i": ">", '"': """, "'": "'", "`": "`" }; var badChars = /&(?!\w+;)|[<>"'`]/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/compiler/compiler.js Handlebars.Compiler = function() {}; Handlebars.JavaScriptCompiler = function() {}; (function(Compiler, JavaScriptCompiler) { Compiler.OPCODE_MAP = { appendContent: 1, getContext: 2, lookupWithHelpers: 3, lookup: 4, append: 5, invokeMustache: 6, appendEscaped: 7, pushString: 8, truthyOrFallback: 9, functionOrFallback: 10, invokeProgram: 11, invokePartial: 12, push: 13, assignToHash: 15, pushStringParam: 16 }; Compiler.MULTI_PARAM_OPCODES = { appendContent: 1, getContext: 1, lookupWithHelpers: 2, lookup: 1, invokeMustache: 3, pushString: 1, truthyOrFallback: 1, functionOrFallback: 1, invokeProgram: 3, invokePartial: 1, push: 1, assignToHash: 1, pushStringParam: 1 }; Compiler.DISASSEMBLE_MAP = {}; for(var prop in Compiler.OPCODE_MAP) { var value = Compiler.OPCODE_MAP[prop]; Compiler.DISASSEMBLE_MAP[value] = prop; } Compiler.multiParamSize = function(code) { return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]]; }; Compiler.prototype = { compiler: Compiler, disassemble: function() { var opcodes = this.opcodes, opcode, nextCode; var out = [], str, name, value; for(var i=0, l=opcodes.length; i 0) { this.source[1] = this.source[1] + ", " + locals.join(", "); } // Generate minimizer alias mappings if (!this.isChild) { var aliases = [] for (var alias in this.context.aliases) { this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; } } if (this.source[1]) { this.source[1] = "var " + this.source[1].substring(2) + ";"; } // Merge children if (!this.isChild) { this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; } if (!this.environment.isSimple) { this.source.push("return buffer;"); } var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } return "stack" + this.stackSlot; }, popStack: function() { return "stack" + this.stackSlot--; }, topStack: function() { return "stack" + this.stackSlot; }, quotedString: function(str) { return '"' + str .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') + '"'; } }; var reservedWords = ( "break else new var" + " case finally return void" + " catch for switch while" + " continue function this with" + " default if throw" + " delete in try" + " do instanceof typeof" + " abstract enum int short" + " boolean export interface static" + " byte extends long super" + " char final native synchronized" + " class float package throws" + " const goto private transient" + " debugger implements protected volatile" + " double import public let yield" ).split(" "); var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; for(var i=0, l=reservedWords.length; i)[^>]*$|\{\{\! /, newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = []; function newTmplItem( options, parentItem, fn, data ) { // Returns a template item data structure for a new rendered instance of a template (a 'template item'). // The content field is a hierarchical array of strings and nested items (to be // removed and replaced by nodes field of dom elements, once inserted in DOM). var newItem = { data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}), _wrap: parentItem ? parentItem._wrap : null, tmpl: null, parent: parentItem || null, nodes: [], calls: tiCalls, nest: tiNest, wrap: tiWrap, html: tiHtml, update: tiUpdate }; if ( options ) { jQuery.extend( newItem, options, { nodes: [], parent: parentItem }); } if ( fn ) { // Build the hierarchical content to be used during insertion into DOM newItem.tmpl = fn; newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem ); newItem.key = ++itemKey; // Keep track of new template item, until it is stored as jQuery Data on DOM element (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem; } return newItem; } // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core). jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems, parent = this.length === 1 && this[0].parentNode; appendToTmplItems = newTmplItems || {}; if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { insert[ original ]( this[0] ); ret = this; } else { for ( i = 0, l = insert.length; i < l; i++ ) { cloneIndex = i; elems = (i > 0 ? this.clone(true) : this).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); } cloneIndex = 0; ret = this.pushStack( ret, name, insert.selector ); } tmplItems = appendToTmplItems; appendToTmplItems = null; jQuery.tmpl.complete( tmplItems ); return ret; }; }); jQuery.fn.extend({ // Use first wrapped element as template markup. // Return wrapped set of template items, obtained by rendering template against data. tmpl: function( data, options, parentItem ) { return jQuery.tmpl( this[0], data, options, parentItem ); }, // Find which rendered template item the first wrapped DOM element belongs to tmplItem: function() { return jQuery.tmplItem( this[0] ); }, // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template. template: function( name ) { return jQuery.template( name, this[0] ); }, domManip: function( args, table, callback, options ) { if ( args[0] && jQuery.isArray( args[0] )) { var dmArgs = jQuery.makeArray( arguments ), elems = args[0], elemsLength = elems.length, i = 0, tmplItem; while ( i < elemsLength && !(tmplItem = jQuery.data( elems[i++], "tmplItem" ))) {} if ( tmplItem && cloneIndex ) { dmArgs[2] = function( fragClone ) { // Handler called by oldManip when rendered template has been inserted into DOM. jQuery.tmpl.afterManip( this, fragClone, callback ); }; } oldManip.apply( this, dmArgs ); } else { oldManip.apply( this, arguments ); } cloneIndex = 0; if ( !appendToTmplItems ) { jQuery.tmpl.complete( newTmplItems ); } return this; } }); jQuery.extend({ // Return wrapped set of template items, obtained by rendering template against data. tmpl: function( tmpl, data, options, parentItem ) { var ret, topLevel = !parentItem; if ( topLevel ) { // This is a top-level tmpl call (not from a nested template using {{tmpl}}) parentItem = topTmplItem; tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl ); wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level } else if ( !tmpl ) { // The template item is already associated with DOM - this is a refresh. // Re-evaluate rendered template for the parentItem tmpl = parentItem.tmpl; newTmplItems[parentItem.key] = parentItem; parentItem.nodes = []; if ( parentItem.wrapped ) { updateWrapped( parentItem, parentItem.wrapped ); } // Rebuild, without creating a new template item return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) )); } if ( !tmpl ) { return []; // Could throw... } if ( typeof data === "function" ) { data = data.call( parentItem || {} ); } if ( options && options.wrapped ) { updateWrapped( options, options.wrapped ); } ret = jQuery.isArray( data ) ? jQuery.map( data, function( dataItem ) { return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null; }) : [ newTmplItem( options, parentItem, tmpl, data ) ]; return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret; }, // Return rendered template item for an element. tmplItem: function( elem ) { var tmplItem; if ( elem instanceof jQuery ) { elem = elem[0]; } while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {} return tmplItem || topTmplItem; }, // Set: // Use $.template( name, tmpl ) to cache a named template, // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc. // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration. // Get: // Use $.template( name ) to access a cached template. // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString ) // will return the compiled template, without adding a name reference. // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent // to $.template( null, templateString ) template: function( name, tmpl ) { if (tmpl) { // Compile template and associate with name if ( typeof tmpl === "string" ) { // This is an HTML string being passed directly in. tmpl = buildTmplFn( tmpl ); } else if ( tmpl instanceof jQuery ) { tmpl = tmpl[0] || {}; } if ( tmpl.nodeType ) { // If this is a template block, use cached copy, or generate tmpl function and cache. tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML )); // Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attribute values whenever the value does not include white space. // This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x. // To correct this, include space in tag: foo="${ x }" -> foo="value of x" } return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl; } // Return named compiled template return name ? (typeof name !== "string" ? jQuery.template( null, name ): (jQuery.template[name] || // If not in map, and not containing at least on HTML tag, treat as a selector. // (If integrated with core, use quickExpr.exec) jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null; }, encode: function( text ) { // Do HTML encoding replacing < > & and ' and " by corresponding entities. return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'"); } }); jQuery.extend( jQuery.tmpl, { tag: { "tmpl": { _default: { $2: "null" }, open: "if($notnull_1){__=__.concat($item.nest($1,$2));}" // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions) // This means that {{tmpl foo}} treats foo as a template (which IS a function). // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}. }, "wrap": { _default: { $2: "null" }, open: "$item.calls(__,$1,$2);__=[];", close: "call=$item.calls();__=call._.concat($item.wrap(call,__));" }, "each": { _default: { $2: "$index, $value" }, open: "if($notnull_1){$.each($1a,function($2){with(this){", close: "}});}" }, "if": { open: "if(($notnull_1) && $1a){", close: "}" }, "else": { _default: { $1: "true" }, open: "}else if(($notnull_1) && $1a){" }, "html": { // Unecoded expression evaluation. open: "if($notnull_1){__.push($1a);}" }, "=": { // Encoded expression evaluation. Abbreviated form is ${}. _default: { $1: "$data" }, open: "if($notnull_1){__.push($.encode($1a));}" }, "!": { // Comment tag. Skipped by parser open: "" } }, // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events complete: function( items ) { newTmplItems = {}; }, // Call this from code which overrides domManip, or equivalent // Manage cloning/storing template items etc. afterManip: function afterManip( elem, fragClone, callback ) { // Provides cloned fragment ready for fixup prior to and after insertion into DOM var content = fragClone.nodeType === 11 ? jQuery.makeArray(fragClone.childNodes) : fragClone.nodeType === 1 ? [fragClone] : []; // Return fragment to original caller (e.g. append) for DOM insertion callback.call( elem, fragClone ); // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data. storeTmplItems( content ); cloneIndex++; } }); //========================== Private helper functions, used by code above ========================== function build( tmplItem, nested, content ) { // Convert hierarchical content into flat string array // and finally return array of fragments ready for DOM insertion var frag, ret = content ? jQuery.map( content, function( item ) { return (typeof item === "string") ? // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM. (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) : // This is a child template item. Build nested template. build( item, tmplItem, item._ctnt ); }) : // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}. tmplItem; if ( nested ) { return ret; } // top-level template ret = ret.join(""); // Support templates which have initial or final text nodes, or consist only of text // Also support HTML entities within the HTML markup. ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) { frag = jQuery( middle ).get(); storeTmplItems( frag ); if ( before ) { frag = unencode( before ).concat(frag); } if ( after ) { frag = frag.concat(unencode( after )); } }); return frag ? frag : unencode( ret ); } function unencode( text ) { // Use createElement, since createTextNode will not render HTML entities correctly var el = document.createElement( "div" ); el.innerHTML = text; return jQuery.makeArray(el.childNodes); } // Generate a reusable function that will serve to render a template against data function buildTmplFn( markup ) { return new Function("jQuery","$item", // Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10). "var $=jQuery,call,__=[],$data=$item.data;" + // Introduce the data as local variables using with(){} "with($data){__.push('" + // Convert the template into pure JavaScript jQuery.trim(markup) .replace( /([\\'])/g, "\\$1" ) .replace( /[\r\t\n]/g, " " ) .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" ) .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, function( all, slash, type, fnargs, target, parens, args ) { var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect; if ( !tag ) { throw "Unknown template tag: " + type; } def = tag._default || []; if ( parens && !/\w$/.test(target)) { target += parens; parens = ""; } if ( target ) { target = unescape( target ); args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : ""); // Support for target being things like a.toLowerCase(); // In that case don't call with template item as 'this' pointer. Just evaluate... expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target; exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))"; } else { exprAutoFnDetect = expr = def.$1 || "null"; } fnargs = unescape( fnargs ); return "');" + tag[ slash ? "close" : "open" ] .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" ) .split( "$1a" ).join( exprAutoFnDetect ) .split( "$1" ).join( expr ) .split( "$2" ).join( fnargs || def.$2 || "" ) + "__.push('"; }) + "');}return __;" ); } function updateWrapped( options, wrapped ) { // Build the wrapped content. options._wrap = build( options, true, // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string. jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()] ).join(""); } function unescape( args ) { return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null; } function outerHtml( elem ) { var div = document.createElement("div"); div.appendChild( elem.cloneNode(true) ); return div.innerHTML; } // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance. function storeTmplItems( content ) { var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m; for ( i = 0, l = content.length; i < l; i++ ) { if ( (elem = content[i]).nodeType !== 1 ) { continue; } elems = elem.getElementsByTagName("*"); for ( m = elems.length - 1; m >= 0; m-- ) { processItemKey( elems[m] ); } processItemKey( elem ); } function processItemKey( el ) { var pntKey, pntNode = el, pntItem, tmplItem, key; // Ensure that each rendered template inserted into the DOM has its own template item, if ( (key = el.getAttribute( tmplItmAtt ))) { while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { } if ( pntKey !== key ) { // The next ancestor with a _tmplitem expando is on a different key than this one. // So this is a top-level element within this template item // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment. pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0; if ( !(tmplItem = newTmplItems[key]) ) { // The item is for wrapped content, and was copied from the temporary parent wrappedItem. tmplItem = wrappedItems[key]; tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode] ); tmplItem.key = ++itemKey; newTmplItems[itemKey] = tmplItem; } if ( cloneIndex ) { cloneTmplItem( key ); } } el.removeAttribute( tmplItmAtt ); } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) { // This was a rendered element, cloned during append or appendTo etc. // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem. cloneTmplItem( tmplItem.key ); newTmplItems[tmplItem.key] = tmplItem; pntNode = jQuery.data( el.parentNode, "tmplItem" ); pntNode = pntNode ? pntNode.key : 0; } if ( tmplItem ) { pntItem = tmplItem; // Find the template item of the parent element. // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string) while ( pntItem && pntItem.key != pntNode ) { // Add this element as a top-level node for this rendered template item, as well as for any // ancestor items between this item and the item of its parent element pntItem.nodes.push( el ); pntItem = pntItem.parent; } // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering... delete tmplItem._ctnt; delete tmplItem._wrap; // Store template item as jQuery data on the element jQuery.data( el, "tmplItem", tmplItem ); } function cloneTmplItem( key ) { key = key + keySuffix; tmplItem = newClonedItems[key] = (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent )); } } } //---- Helper functions for template item ---- function tiCalls( content, tmpl, data, options ) { if ( !content ) { return stack.pop(); } stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options }); } function tiNest( tmpl, data, options ) { // nested template, using {{tmpl}} tag return jQuery.tmpl( jQuery.template( tmpl ), data, options, this ); } function tiWrap( call, wrapped ) { // nested template, using {{wrap}} tag var options = call.options || {}; options.wrapped = wrapped; // Apply the template, which may incorporate wrapped content, return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item ); } function tiHtml( filter, textOnly ) { var wrapped = this._wrap; return jQuery.map( jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ), function(e) { return textOnly ? e.innerText || e.textContent : e.outerHTML || outerHtml(e); }); } function tiUpdate() { var coll = this.nodes; jQuery.tmpl( null, null, null, this).insertBefore( coll[0] ); jQuery( coll ).remove(); } })( jQuery );BorisMoore-jsrender-734d3bd/test/resources/perf-compare.css000066400000000000000000000007411200734767300241160ustar00rootroot00000000000000body { padding: 10px; font-family: Verdana; font-size: small } h4 { font-size: inherit`; font-variant: small-caps; } .height { width: 100%; margin-bottom:10px; float: left; clear: both; } .bottom { height:400px; width: 100%; margin-bottom:10px; float: left; clear: both; } body > button { float: left; clear: right; margin: 3px } .subhead { margin: 15px 0 4px 0; font-weight:bolder; color:#116; font-family:Arial; font-size:10pt } a { color: #55b} .result { text-align:right; } BorisMoore-jsrender-734d3bd/test/unit-tests-no-jquery.html000066400000000000000000000013531200734767300237460ustar00rootroot00000000000000

JsRender QUnit Tests

    test markup, will be hidden
    BorisMoore-jsrender-734d3bd/test/unit-tests-with-jquery.html000066400000000000000000000013551200734767300243070ustar00rootroot00000000000000

    JsRender QUnit Tests

      test markup, will be hidden
      BorisMoore-jsrender-734d3bd/test/unit/000077500000000000000000000000001200734767300177675ustar00rootroot00000000000000BorisMoore-jsrender-734d3bd/test/unit/jsrender-tests-no-jquery.js000066400000000000000000000612121200734767300252320ustar00rootroot00000000000000/// function compileTmpl( template ) { try { return typeof jsviews.templates( template ).fn === "function" ? "compiled" : "failed compile"; } catch(e) { return e.message; } } function sort( array ) { var ret = ""; if ( this.props.reverse ) { // Render in reverse order if (arguments.length > 1) { for ( i = arguments.length; i; i-- ) { ret += sort.call( this, arguments[ i - 1 ]); } } else for ( var i = array.length; i; i-- ) { ret += this.tmpl.render( array[ i - 1 ] ); } } else { // Render in original order ret += this.tmpl.render( array ); } return ret; } var person = { name: "Jo" }, people = [{ name: "Jo" },{ name: "Bill" }], towns = [{ name: "Seattle" },{ name: "Paris" },{ name: "Delhi" }]; var tmplString = "A_{{:name}}_B"; jsviews.tags({ sort: sort }); module( "tagParser" ); test("{{if}} {{else}}", function() { expect(3); equal( compileTmpl( "A_{{if true}}{{/if}}_B" ), "compiled", "Empty if block: {{if}}{{/if}}" ); equal( compileTmpl( "A_{{if true}}yes{{/if}}_B" ), "compiled", "{{if}}...{{/if}}" ); equal( compileTmpl( "A_{{if true/}}yes{{/if}}_B" ), "Syntax error\nUnmatched or missing tag: \"{{/if}}\" in template:\nA_{{if true/}}yes{{/if}}_B"); }); module( "{{if}}" ); test("{{if}}", function() { expect(4); equal( jsviews.templates( "A_{{if true}}yes{{/if}}_B" ).render(), "A_yes_B", "{{if a}}: a" ); equal( jsviews.templates( "A_{{if false}}yes{{/if}}_B" ).render(), "A__B", "{{if a}}: !a" ); equal( jsviews.templates( "A_{{if true}}{{/if}}_B" ).render(), "A__B", "{{if a}}: empty: a" ); equal( jsviews.templates( "A_{{if false}}{{/if}}_B" ).render(), "A__B", "{{if a}}: empty: !a" ); }); test("{{if}} {{else}}", function() { expect(7); equal( jsviews.templates( "A_{{if true}}yes{{else}}no{{/if}}_B" ).render(), "A_yes_B", "{{if a}} {{else}}: a" ); equal( jsviews.templates( "A_{{if false}}yes{{else}}no{{/if}}_B" ).render(), "A_no_B", "{{if a}} {{else}}: !a" ); equal( jsviews.templates( "A_{{if true}}yes{{else true}}or{{else}}no{{/if}}_B" ).render(), "A_yes_B", "{{if a}} {{else b}} {{else}}: a" ); equal( jsviews.templates( "A_{{if false}}yes{{else true}}or{{else}}no{{/if}}_B" ).render(), "A_or_B", "{{if a}} {{else b}} {{else}}: b" ); equal( jsviews.templates( "A_{{if false}}yes{{else false}}or{{else}}no{{/if}}_B" ).render(), "A_no_B", "{{if a}} {{else b}} {{else}}: !a!b" ); equal( jsviews.templates( "A_{{if false}}
      x
      _B" ).render(), "A_
      x
      _B", "{{if}} and {{else}} work across HTML tags" ); equal( jsviews.templates( "A_
      x
      _B" ).render(), "A_
      x
      _B", "{{if}} and {{else}} work across quoted strings" ); }); test("{{if}} {{else}} external templates", function() { expect(2); equal( jsviews.templates( "A_{{if true tmpl='yes
      '/}}_B" ).render(), "A_yes
      _B", "{{if a tmpl=foo/}}: a" ); equal( jsviews.templates( "A_{{if false tmpl='yes
      '}}{{else false tmpl='or
      '}}{{else tmpl='no
      '}}{{/if}}_B" ).render(), "A_no
      _B", "{{if a tmpl=foo}}{{else b tmpl=bar}}{{else tmpl=baz}}: !a!b" ); }); module( "{{:}}" ); test("convert", function() { expect(4); equal( jsviews.templates( "{{>#data}}" ).render( "
      '\"&" ), "<br/>'"&", "default html converter" ); equal( jsviews.templates( "{{html:#data}}" ).render( "
      '\"&" ), "<br/>'"&", "html converter" ); equal( jsviews.templates( "{{:#data}}" ).render( "
      '\"&" ), "
      '\"&", "no convert" ); function loc( data ) { switch (data) { case "desktop": return "bureau"; }; } jsviews.converters("loc", loc); equal(jsviews.templates( "{{loc:#data}}:{{loc:'desktop'}}" ).render( "desktop" ), "bureau:bureau", 'jsviews.converters("loc", locFunction);... {{loc:#data}}' ); }); test("paths", function() { expect(17); equal( jsviews.templates( "{{:a}}" ).render({ a: "aVal" }), "aVal", "a" ); equal( jsviews.templates( "{{:a.b}}" ).render({ a: { b: "bVal" }}), "bVal", "a.b" ); equal( jsviews.templates( "{{:a.b.c}}" ).render({ a: { b: { c: "cVal" }}}), "cVal", "a.b.c" ); equal( jsviews.templates( "{{:a.name}}" ).render({ a: { name: "aName" }} ), "aName", "a.name" ); equal( jsviews.templates( "{{:a['name']}}" ).render({ a: { name: "aName"} } ), "aName", "a['name']"); equal( jsviews.templates( "{{:a['x - _*!']}}" ).render({ a: { "x - _*!": "aName"} } ), "aName", "a['x - _*!']"); equal( jsviews.templates( "{{:#data['x - _*!']}}" ).render({ "x - _*!": "aName"} ), "aName", "#data['x - _*!']"); equal( jsviews.templates( '{{:a["x - _*!"]}}').render({ a: { "x - _*!": "aName"} }), "aName", 'a["x - _*!"]'); equal( jsviews.templates( "{{:a.b[1].d}}" ).render({ a: { b: [0, { d: "dVal"}]} }), "dVal", "a.b[1].d"); equal( jsviews.templates( "{{:a.b[1].d}}" ).render({ a: { b: {1:{ d: "dVal" }}}}), "dVal", "a.b[1].d" ); equal( jsviews.templates( "{{:a.b[~incr(1-1)].d}}" ).render({ a: { b: {1:{ d: "dVal" }}}}, { incr:function(val) { return val + 1; }}), "dVal", "a.b[~incr(1-1)].d" ); equal( jsviews.templates( "{{:a.b.c.d}}" ).render({ a: { b: {'c':{ d: "dVal" }}}}), "dVal", "a.b.c.d" ); equal( jsviews.templates( "{{:a[0]}}" ).render({ a: [ "bVal" ]}), "bVal", "a[0]" ); equal( jsviews.templates( "{{:a.b[1][0].msg}}" ).render({ a: { b: [22,[{ msg: " yes - that's right. "}]] }}), " yes - that's right. ", "a.b[1][0].msg" ); equal( jsviews.templates( "{{:#data.a}}" ).render({ a: "aVal" }), "aVal", "#data.a" ); equal( jsviews.templates( "{{:#view.data.a}}" ).render({ a: "aVal" }), "aVal", "#view.data.a" ); equal( jsviews.templates( "{{:#index === 0}}" ).render([{ a: "aVal" }]), "true", "#index" ); }); test("types", function() { expect(10); equal( jsviews.templates( "{{:'abc'}}" ).render(), "abc", "'abc'" ); equal( jsviews.templates( "{{:true}}" ).render(), "true", "true" ); equal( jsviews.templates( "{{:false}}" ).render(), "false", "false" ); equal( jsviews.templates( "{{:null}}" ).render(), "", 'null -> ""' ); equal( jsviews.templates( "{{:199}}" ).render(), "199", "199" ); equal( jsviews.templates( "{{: 199.9 }}" ).render(), "199.9", "| 199.9 |" ); equal( jsviews.templates( "{{:-33.33}}" ).render(), "-33.33", "-33.33" ); equal( jsviews.templates( "{{: -33.33 }}" ).render(), "-33.33", "| -33.33 |" ); equal( jsviews.templates( "{{:-33.33 - 2.2}}" ).render(), "-35.53", "-33.33 - 2.2" ); equal( jsviews.templates( "{{:notdefined}}" ).render({}), "", "notdefined" ); }); test("comparisons", function() { expect(22); equal( jsviews.templates( "{{:1<2}}" ).render(), "true", "1<2" ); equal( jsviews.templates( "{{:2<1}}" ).render(), "false", "2<1" ); equal( jsviews.templates( "{{:5===5}}" ).render(), "true", "5===5" ); equal( jsviews.templates( "{{:0==''}}" ).render(), "true", "0==''" ); equal( jsviews.templates( "{{:'ab'=='ab'}}" ).render(), "true", "'ab'=='ab'" ); equal( jsviews.templates( "{{:2>1}}" ).render(), "true", "2>1" ); equal( jsviews.templates( "{{:2 == 2}}" ).render(), "true", "2 == 2" ); equal( jsviews.templates( "{{:2<=2}}" ).render(), "true", "2<=2" ); equal( jsviews.templates( "{{:'ab'<'ac'}}" ).render(), "true", "'ab'<'ac'" ); equal( jsviews.templates( "{{:3>=3}}" ).render(), "true", "3 =3" ); equal( jsviews.templates( "{{:3>=2}}" ).render(), "true", "3>=2" ); equal( jsviews.templates( "{{:3>=4}}" ).render(), "false", "3>=4" ); equal( jsviews.templates( "{{:3 !== 2}}" ).render(), "true", "3 !== 2" ); equal( jsviews.templates( "{{:3 != 2}}" ).render(), "true", "3 != 2" ); equal( jsviews.templates( "{{:0 !== null}}" ).render(), "true", "0 !== null" ); equal( jsviews.templates( "{{:(3 >= 4)}}" ).render(), "false", "3>=4" ); equal( jsviews.templates( "{{:3 >= 4}}" ).render(), "false", "3>=4" ); equal( jsviews.templates( "{{:(3>=4)}}" ).render(), "false", "3>=4" ); equal( jsviews.templates( "{{:(3 < 4)}}" ).render(), "true", "3>=4" ); equal( jsviews.templates( "{{:3 < 4}}" ).render(), "true", "3>=4" ); equal( jsviews.templates( "{{:(3<4)}}" ).render(), "true", "3>=4" ); equal( jsviews.templates( "{{:0 != null}}" ).render(), "true", "0 != null" ); }); test("array access", function() { equal( jsviews.templates( "{{:a[1]}}" ).render({ a: ["a0","a1"] }), "a1", "a[1]" ); equal( jsviews.templates( "{{:a[1+1]+5}}" ).render({ a: [11,22,33] }), "38", "a[1+1]+5)" ); equal( jsviews.templates( "{{:a[~incr(1)]+5}}" ).render({ a: [11,22,33] }, { incr:function(val) { return val + 1; }}), "38", "a[~incr(1)]+5" ); equal( jsviews.templates( "{{:true && (a[0] || 'default')}}" ).render({ a: [0,22,33] }, { incr:function(val) { return val + 1; }}), "default", "true && (a[0] || 'default')" ); }); test("context", function() { expect(4); equal( jsviews.templates( "{{:~val}}" ).render( 1, { val: "myvalue" }), "myvalue", "~val" ); function format(value, upper) { return value[upper ? "toUpperCase" : "toLowerCase"](); } equal( jsviews.templates( "{{:~format(name) + ~format(name, true)}}" ).render( person, { format: format }), "joJO", "render( data, { format: formatFn }); ... {{:~format(name, true)}}" ); equal( jsviews.templates( "{{for people[0]}}{{:~format(~type) + ~format(name, true)}}{{/for}}" ).render({ people: people}, { format: format, type: "PascalCase" }), "pascalcaseJO", "render( data, { format: formatFn }); ... {{:~format(name, true)}}" ); equal( jsviews.templates( "{{for people ~twn=town}}{{:name}} lives in {{:~format(~twn, true)}}. {{/for}}" ).render({ people: people, town:"Redmond" }, { format: format }), "Jo lives in REDMOND. Bill lives in REDMOND. ", "Passing in context to nested templates: {{for people ~twn=town}}" ); }); test("values", function() { expect(4); equal( jsviews.templates( "{{:a}}" ).render({ a: 0 }), "0", "0" ); equal( jsviews.templates( "{{:b}}" ).render({ a: 0 }), "", "undefined" ); equal( jsviews.templates( "{{:a}}" ).render({ a: "" }), "", "" ); equal( jsviews.templates( "{{:b}}" ).render({ a: null }), "", null ); }); test("expressions", function() { expect(8); equal( compileTmpl( "{{:a++}}" ), "Syntax error\na++", "a++" ); equal( compileTmpl( "{{:(a,b)}}" ), "Syntax error\n(a,b)", "(a,b)" ); equal( jsviews.templates( "{{: a+2}}" ).render({ a: 2, b: false }), "4", "a+2"); equal( jsviews.templates( "{{: b?'yes':'no' }}" ).render({ a: 2, b: false }), "no", "b?'yes':'no'"); equal( jsviews.templates( "{{:(a||-1) + (b||-1) }}" ).render({ a: 2, b: 0 }), "1", "a||-1"); equal( jsviews.templates( "{{:3*b()*!a*4/3}}" ).render({ a: false, b: function () { return 3; }}), "12", "3*b()*!a*4/3"); equal( jsviews.templates( "{{:a%b}}" ).render({ a: 30, b: 16}), "14", "a%b"); equal( jsviews.templates( "A_{{if v1 && v2 && v3 && v4}}no{{else !v1 && v2 || v3 && v4}}yes{{/if}}_B" ).render({v1:true,v2:false,v3:2,v4:"foo"}), "A_yes_B", "x && y || z"); }); module( "{{for}}" ); test("{{for}}", function() { expect(15); jsviews.templates( { forTmpl: "header_{{for people}}{{:name}}{{/for}}_footer", templateForArray: "header_{{for #data}}{{:name}}{{/for}}_footer", pageTmpl: '{{for [people] tmpl="templateForArray"/}}', simpleFor: "a{{for people}}Content{{:#data}}|{{/for}}b", forPrimitiveDataTypes: "a{{for people}}|{{:#data}}{{/for}}b" }); equal( jsviews.render.forTmpl({ people: people }), "header_JoBill_footer", '{{for people}}...{{/for}}' ); equal( jsviews.render.templateForArray( [people] ), "header_JoBill_footer", 'Can render a template against an array, as a "layout template", by wrapping array in an array' ); equal( jsviews.render.pageTmpl({ people: people }), "header_JoBill_footer", '{{for [people] tmpl="templateForArray"/}}' ); equal( jsviews.templates( "{{for people towns}}{{:name}}{{/for}}" ).render({ people: people, towns: towns }), "JoBillSeattleParisDelhi", "concatenated targets: {{for people towns}}" ); equal( jsviews.templates( "{{for}}xxx{{:#data===~test}}{{/for}}" ).render({},{test:undefined}), "xxxtrue", "no parameter - renders once with #data undefined: {{for}}" ); equal( jsviews.templates( "{{for missingProperty}}xxx{{:#data===~undefined}}{{/for}}" ).render({}), "xxxtrue", "missingProperty - renders once with #data undefined: {{for missingProperty}}" ); equal( jsviews.templates( "{{for null}}xxx{{:#data===null}}{{/for}}" ).render(), "xxxtrue", "null - renders once with #data null: {{for null}}" ); equal( jsviews.templates( "{{for false}}xxx{{:#data}}{{/for}}" ).render(), "xxxfalse", "false - renders once with #data false: {{for false}}" ); equal( jsviews.templates( "{{for 0}}xxx{{:#data}}{{/for}}" ).render(), "xxx0", "0 - renders once with #data false: {{for 0}}" ); equal( jsviews.templates( "{{for ''}}xxx{{:#data===''}}{{/for}}" ).render(), "xxxtrue", "'' - renders once with #data false: {{for ''}}" ); equal( jsviews.render.simpleFor({people:[]}), "ab", 'Empty array renders empty string' ); equal( jsviews.render.simpleFor({people:["",false,null,undefined,1]}), "aContent|Contentfalse|Content|Content|Content1|b", 'Empty string, false, null or undefined members of array are also rendered' ); equal( jsviews.render.simpleFor({people:null}), "aContent|b", 'null is rendered once with #data null' ); equal( jsviews.render.simpleFor({}), "aContent|b", 'if #data is undefined, renders once with #data undefined' ); equal( jsviews.render.forPrimitiveDataTypes({people:[0,1,"abc","",,null,true,false]}), "a|0|1|abc||||true|falseb", 'Primitive types render correctly, even if falsey' ); }); module( "api" ); test("templates", function() { expect(14); var tmpl = jsviews.templates( tmplString ); equal( tmpl.render( person ), "A_Jo_B", 'Compile from string: var tmpl = jsviews.templates( tmplString );' ); var fnToString = tmpl.fn.toString(); equal( jsviews.templates( "", tmplString ).fn.toString() === fnToString && jsviews.templates( null, tmplString ).fn.toString() === fnToString && jsviews.templates( undefined, tmplString ).fn.toString() === fnToString, true, 'if name is "", null, or undefined, then jsviews.templates( name, tmplString ) = jsviews.templates( tmplString );' ); jsviews.templates( "myTmpl", tmplString ); equal( jsviews.render.myTmpl( person ), "A_Jo_B", 'Compile and register named template: jsviews.templates( "myTmpl", tmplString );' ); jsviews.templates({ myTmpl2: tmplString, myTmpl3: "X_{{:name}}_Y" }); equal( jsviews.render.myTmpl2( person ) + jsviews.render.myTmpl3( person ), "A_Jo_BX_Jo_Y", 'Compile and register named templates: jsviews.templates({ myTmpl: tmplString, myTmpl2: tmplString2 });' ); jsviews.templates( "!'-#==", "x" ); jsviews.templates({ '&^~>"2': "y" }); equal( jsviews.render["!'-#=="]( person ) + jsviews.render['&^~>"2']( person ), "xy", 'Named templates can have arbitrary names;' ); jsviews.templates({ myTmpl4: "A_B" }); equal( jsviews.render.myTmpl4( person ), "A_B", 'jsviews.templates({ myTmpl: htmlWithNoTags });' ); jsviews.templates({ myTmpl5: { markup: tmplString } }); equal( jsviews.render.myTmpl5( person ), "A_Jo_B", 'jsviews.templates( "myTmpl", tmplObjWithMarkupString );' ); equal( jsviews.templates( "", { markup: tmplString }).render( person ), "A_Jo_B", 'Compile from template object without registering: jsviews.templates( "", tmplObjWithMarkupString );' ); jsviews.templates({ myTmpl6: { markup: tmplString } }); equal( jsviews.render.myTmpl6( person ), "A_Jo_B", 'jsviews.templates( "myTmpl", tmplObjWithMarkupString );' ); jsviews.templates( "myTmpl7", tmpl ); equal( jsviews.render.myTmpl7( person ), "A_Jo_B", 'Cloning a template: jsviews.templates( "newName", tmpl );' ); equal( jsviews.templates( "", tmpl ) === tmpl, true, 'jsviews.templates( tmpl ) returns tmpl' ); equal( jsviews.templates( "" ).render(), "", 'jsviews.templates( "" ) is a template with empty string as content' ); jsviews.templates( "myEmptyTmpl", "" ); equal( jsviews.templates.myEmptyTmpl.render(), "", 'jsviews.templates( "myEmptyTmpl", "" ) is a template with empty string as content' ); jsviews.templates( "myTmpl", null ); equal( jsviews.templates.myTmpl, undefined, 'Remove a named template: jsviews.templates( "myTmpl", null );' ); }); test("render", function() { expect(18); var tmpl1 = jsviews.templates( "myTmpl8", tmplString ); jsviews.templates( { simple: "Content{{:#data}}|", templateForArray: "Content{{for #data}}{{:#index}}{{/for}}", primitiveDataTypes: "|{{:#data}}" }); equal( tmpl1.render( person ), "A_Jo_B", 'tmpl1.render( data );' ); equal( jsviews.render.myTmpl8( person ), "A_Jo_B", 'jsviews.render.myTmpl8( data );' ); jsviews.templates( "myTmpl9", "A_{{for}}inner{{:name}}content{{/for}}_B" ); equal( jsviews.templates.myTmpl9.tmpls[0].render( person ), "innerJocontent", 'Access nested templates: jsviews.templates["myTmpl9[0]"];' ); jsviews.templates( "myTmpl10", "top index:{{:#index}}|{{for 1}}nested index:{{:#index}}|{{if #index===0}}nested if index:{{:#index}}|{{else}}nested else index:{{:#index}}|{{/if}}{{/for}}" ); equal( jsviews.render.myTmpl10(people), "top index:0|nested index:0|nested if index:0|top index:1|nested index:1|nested else index:1|", '#index gives the integer index even in nested blocks' ); jsviews.templates( "myTmpl11", "top index:{{:#index}}|{{for people}}nested index:{{:#index}}|{{if #index===0}}nested if index:{{:#index}}|{{else}}nested else index:{{:#index}}|{{/if}}{{/for}}" ); equal( jsviews.render.myTmpl11({ people: people }), "top index:|nested index:0|nested if index:0|nested index:1|nested else index:1|", '#index gives the integer index even in nested blocks' ); jsviews.helpers({ myKeyIsCorrect: function() { return this.parent.views[this.key] === this; }}); jsviews.templates( "myTmpl12", "{{for people}}nested {{:~myKeyIsCorrect()}}|{{if #index===0}}nested if {{:~myKeyIsCorrect()}}|{{else}}nested else {{:~myKeyIsCorrect()}}|{{/if}}{{/for}}" ); equal( jsviews.render.myTmpl12({ people: people }), "nested true|nested if true|nested true|nested else true|", '#key gives the key of this view in the parent views collection/object' ); equal( jsviews.templates( tmplString ).render( person ), "A_Jo_B", 'Compile from string: var html = jsviews.templates( tmplString ).render( data );' ); equal( jsviews.render.myTmpl8( people ), "A_Jo_BA_Bill_B", 'jsviews.render.myTmpl( array );' ); equal( jsviews.render.simple([]), "", 'Empty array renders empty string' ); equal( jsviews.render.simple(["",false,null,undefined,1]), "Content|Contentfalse|Content|Content|Content1|", 'Empty string, false, null or undefined members of array are also rendered' ); equal( jsviews.render.simple(null), "Content|", 'null renders once with #data null' ); equal( jsviews.render.simple(), "Content|", 'Undefined renders once with #data undefined' ); equal( jsviews.render.simple(false), "Contentfalse|", 'false renders once with #data false' ); equal( jsviews.render.simple(0), "Content0|", '0 renders once with #data 0' ); equal( jsviews.render.simple(""), "Content|", '"" renders once with #data ""' ); equal( jsviews.render.templateForArray([[null,undefined,1]]), "Content012", 'Can render a template against an array, and render once only, by wrapping array in an array' ); equal( jsviews.render.templateForArray([[]]), "Content", 'Can render a template against an empty array, and render once only, by wrapping array in an array' ); equal( jsviews.render.primitiveDataTypes([0,1,"abc","",,true,false]), "|0|1|abc|||true|false", 'Primitive types render correctly, even if falsey' ); }); test("converters", function() { expect(3); function loc( data ) { switch (data) { case "desktop": return "bureau"; }; } jsviews.converters({ loc2: loc }); equal(jsviews.templates( "{{loc2:#data}}:{{loc2:'desktop'}}" ).render( "desktop" ), "bureau:bureau", "jsviews.converters({ loc: locFunction })" ); var locFn = jsviews.converters("loc", loc); equal(locFn === loc && jsviews.converters.loc === loc && jsviews.converters.loc2 === loc, true, 'locFunction === jsviews.converters.loc === jsviews.converters.loc2' ); jsviews.converters({ loc2: null}); equal(jsviews.converters.loc2, undefined, 'jsviews.converters({ loc2: null }) to remove registered converter' ); }); test("tags", function() { expect(5); equal(jsviews.templates( "{{sort people reverse=true}}{{:name}}{{/sort}}" ).render({ people: people }), "BillJo", "jsviews.tags({ sort: sortFunction })" ); equal(jsviews.templates( "{{sort people reverse=true towns}}{{:name}}{{/sort}}" ).render({ people: people, towns:towns }), "DelhiParisSeattleBillJo", "Multiple parameters in arbitrary order: {{sort people reverse=true towns}}" ); equal(jsviews.templates( "{{sort reverse=false people reverse=true towns}}{{:name}}{{/sort}}" ).render({ people: people, towns:towns }), "DelhiParisSeattleBillJo", "Duplicate named parameters - last wins: {{sort reverse=false people reverse=true towns}}" ); var sort2 = jsviews.tags("sort2", sort); equal(sort2.render === sort && jsviews.tags.sort.render === sort && jsviews.tags.sort2.render === sort, true, 'sortFunction === jsviews.tags.sort.render === jsviews.tags.sort2.render' ); jsviews.tags("sort2", null); equal(jsviews.tags.sort2, undefined, 'jsviews.tags( "sort2", null ) to remove registered tag' ); }); test("helpers", function() { expect(4); jsviews.helpers({ not: function( value ) { return !value; }, concat: function() { return "".concat.apply( "", arguments ) + "top"; } }) equal( jsviews.templates( "{{:~concat(a, 'b', ~not(false))}}" ).render({ a: "aVal" }), "aValbtruetop", "~concat('a')" ); function toUpperCase(value) { return value.toUpperCase(); } var toUpperCaseFn = jsviews.helpers( "toUpperCase", toUpperCase ); equal( jsviews.templates( "{{:~toUpperCase(name)}} {{:~toUpperCase('Foo')}}" ).render( person ), "JO FOO", 'jsviews.helpers( "toUpperCase", toUpperCaseFn );... {{:~toUpperCase(name)}}' ); jsviews.helpers({ toUpperCase2: toUpperCase }); equal( toUpperCaseFn === toUpperCase && jsviews.helpers.toUpperCase === toUpperCase && jsviews.helpers.toUpperCase2 === toUpperCase, true, 'sortFunction === jsviews.helpers.toUpperCase === jsviews.helpers("toUpperCase")' ); jsviews.helpers("toUpperCase2", null); equal(jsviews.helpers.toUpperCase2, undefined, 'jsviews.helpers( "toUpperCase2", null ) to remove registered helper' ); }); test("template encapsulation", function() { expect(5); jsviews.helpers({ not: function( value ) { return !value; }, concat: function() { return "".concat.apply( "", arguments ) + "inner"; } }); jsviews.templates({ encapsulate1: { markup: "{{str:~not(true)}} {{sort people reverse=true tmpl='personTmpl'/}} {{str:~not2(false)}}", tags: { sort2: sort }, templates: { personTmpl: "{{upper:name}}" }, helpers: { not2: function( value ) { return "not2:" + !value; } }, converters: { str: function( value ) { return value.toString() + ":tostring"; }, upper: function( value ) { return value.toUpperCase(); } } } }); equal( $.trim( jsviews.render.encapsulate1({ people: people })), "false:tostring BILLJO not2:true:tostring", 'jsviews.templates( "myTmpl", tmplObjWithNestedItems);' ); jsviews.templates({ useLower: "{{lower a/}}", tmplWithNestedResources: { markup: "{{lower a/}} {{:~concat2(a, 'b', ~not2(false))}} {{for #data tmpl='nestedTmpl1'/}} {{for #data tmpl='nestedTmpl2'/}}", helpers: { not2: function( value ) { return !value; }, concat2: function() { return "".concat.apply( "", arguments ) + "%"; } }, converters: { "double": function( value ) { return "(double:" + value + "&" + value + ")"; } }, tags: { lower: function( val ) { return val.toLowerCase() } }, templates: { nestedTmpl1: "{{double:a}}", nestedTmpl2: { markup: "{{double:~upper(a)}}", helpers: { upper: function( value ) { return value.toUpperCase(); } }, converters: { "double": function( value ) { return "(override outer double:" + value + "|" + value + ")"; } } }, templateWithDebug: { markup: "{{double:~upper(a)}}", debug: true } } } }); var context = { upper: function( value ) { return "contextualUpper" + value.toUpperCase(); }, not: function( value ) { return "contextualNot" + !value; }, not2: function( value ) { return "contextualNot2" + !value; } }; jsviews.render.tmplWithNestedResources({ a: "aVal" }) equal( jsviews.render.tmplWithNestedResources({ a: "aVal" }), "aval aValbtrue% (double:aVal&aVal) (override outer double:AVAL|AVAL)", 'Access nested resources from template' ); equal( jsviews.render.useLower({ a: "aVal" }), "Error: Unknown tag: {{lower}}. ", 'Cannot access nested resources from a different template' ); equal( jsviews.render.tmplWithNestedResources({ a: "aVal" }, context), "aval aValbcontextualNot2true% (double:aVal&aVal) (override outer double:contextualUpperAVAL|contextualUpperAVAL)", 'Resources passed in with context override nested resources' ); equal( jsviews.templates.tmplWithNestedResources.templates.templateWithDebug.fn.toString().indexOf("debugger;") > 0, true, 'Can set debug=true on nested template' ); }); BorisMoore-jsrender-734d3bd/test/unit/jsrender-tests-with-jquery.js000066400000000000000000000140641200734767300255740ustar00rootroot00000000000000/// /// /// function compileTmpl( template ) { try { return typeof $.templates( null, template ).fn === "function" ? "compiled" : "failed compile"; } catch(e) { return "error:" + e; } } function sort( array ){ var ret = ""; if ( this.props.reverse ) { // Render in reverse order for ( var i = array.length; i; i-- ) { ret += this.tmpl.render( array[ i - 1 ] ); } } else { // Render in original order ret += this.tmpl.render( array ); } return ret; } var person = { name: "Jo" }, people = [{ name: "Jo" },{ name: "Bill" }], towns = [{ name: "Seattle" },{ name: "Paris" },{ name: "Delhi" }]; var tmplString = "A_{{:name}}_B"; $.views.allowCode = true; module( "api" ); test("templates", function() { expect(14); $.templates( "myTmpl", tmplString ); equal( $.render.myTmpl( person ), "A_Jo_B", 'Compile a template and then render it: $.templates( "myTmpl", tmplString ); $.render.myTmpl( data );' ); $.templates({ myTmpl2: tmplString }); equal( $.render.myTmpl2( person ), "A_Jo_B", 'Compile and register templates: $.templates({ "myTmpl", tmplString, ... }); $.render.myTmpl( data );' ); equal( $.templates.myTmpl2.render( person ), "A_Jo_B", 'Get named template: $.templates.myTmpl.render( data );' ); equal( $.templates( tmplString ).render( person ), "A_Jo_B", 'Compile without registering as named template: $.templates( tmplString ).render( person );' ); var tmpl2 = $.templates( "#myTmpl" ); equal( $.trim( tmpl2.render( person )), "A_Jo_B", 'var tmpl = $.templates( "#myTmpl" ); returns compiled template for script element' ); $.templates({ myTmpl3: { markup: "#myTmpl" } }); equal( $.trim( $.render.myTmpl3( person )), "A_Jo_B", 'Named template for template object with selector: { markup: "#myTmpl" }' ); var tmpl2 = $.templates( "#myTmpl" ); equal( $.trim( tmpl2.render( person )), "A_Jo_B", 'var tmpl = $.templates( "#myTmpl" ); returns compiled template for script element' ); var tmpl3 = $.templates( "", { markup: "#myTmpl" }); equal( $.trim( tmpl3.render( person )), "A_Jo_B", 'Compile from template object with selector, without registering: { markup: "#myTmpl" }' ); equal( $.templates( "#myTmpl" ).fn === tmpl2.fn && tmpl2.fn === tmpl3.fn, true, '$.templates( "#myTmpl" ) caches compiled template, and does not recompile each time;' ); equal( tmpl2 === $.templates( "", "#myTmpl" ), true, '$.templates( "#myTmpl" ) and $.templates( "", "#myTmpl" ) are equivalent' ); var cloned = $.templates( "cloned", "#myTmpl" ); equal( cloned !== tmpl2 && cloned.name == "cloned", true, '$.templates( "cloned", "#myTmpl" ) will clone the cached template' ); $.templates({ cloned: "#myTmpl" }); equal( $.templates.cloned !== tmpl2 && $.templates.cloned.name == "cloned", true, '$.templates({ cloned: "#myTmpl" }) will clone the cached template' ); $.templates( "myTmpl", null ); equal( $.templates.myTmpl, undefined, 'Remove a named template: $.templates( "myTmpl", null );' ); var tmpl3 = $.templates({ "scriptTmpl": { markup: "#myTmpl", debug:true }, "tmplFromString": { markup: "testDebug", debug:true } }); equal( $.templates.tmplFromString.fn.toString().indexOf("debugger;") > 0 && $.templates.scriptTmpl.fn.toString().indexOf("debugger;") > 0, true, 'Debug a template: set debug:true on object' ); }); test("render", function() { expect(5); equal( $.trim( $("#myTmpl").render( person )), "A_Jo_B", '$( tmplSelector ).render( data );' ); // Trimming because IE adds whitespace var tmpl3 = $.templates( "myTmpl4", tmplString ); equal( $.render.myTmpl4( person ), "A_Jo_B", '$.render.myTmpl( object );' ); equal( $.render.myTmpl4( people ), "A_Jo_BA_Bill_B", '$.render.myTmpl( array );' ); var tmplObject = $.templates.myTmpl4; equal( tmplObject.render( people ), "A_Jo_BA_Bill_B", 'var tmplObject = $.templates.myTmpl; tmplObject.render( data );' ); $.templates( "myTmpl5", "A_{{for}}inner{{:name}}content{{/for}}_B" ); equal( $.templates.myTmpl5.tmpls[0].render( person ), "innerJocontent", 'Nested template objects: $.templates.myTmpl.tmpls' ); }); test("converters", function() { expect(3); function loc( data ) { switch (data) { case "desktop": return "bureau"; }; } $.views.converters({ loc: loc }); equal( $.templates( "{{loc:#data}}:{{loc:'desktop'}}" ).render( "desktop" ), "bureau:bureau", "$.views.converters({ loc: locFunction })" ); $.views.converters( "loc2", loc ); equal( $.views.converters.loc2 === loc, true, 'locFunction === $.views.converters.loc' ); $.views.converters({ loc2: null}); equal( $.views.converters.loc2, undefined, 'Remove a registered converter: $.views.converters({ loc: null })'); }); test("tags", function() { expect(3); $.views.tags({ sort: sort }); equal( $.templates( "{{sort people reverse=true}}{{:name}}{{/sort}}" ).render({ people: people }), "BillJo", "$.views.tags({ sort: sortFunction })" ); $.views.tags( "sort2", sort ); equal( $.views.tags.sort.render === sort, true, 'sortFunction === $.views.tags.sort' ); $.views.tags( "sort2", null ); equal( $.views.tags.sort2, undefined, 'Remove a registered tag: $.views.tag({ sor: null })' ); }); test("helpers", function() { expect(3); function concat() { return "".concat.apply( "", arguments ); } $.views.helpers({ not: function( value ) { return !value; }, concat: concat }) equal( $.templates( "{{:~concat(a, 'b', ~not(false))}}" ).render({ a: "aVal" }), "aValbtrue", "$.views.helpers({ concat: concatFunction })"); $.views.helpers({ concat2: concat }); equal( $.views.helpers.concat === concat, true, 'concatFunction === $.views.helpers.concat' ); $.views.helpers("concat2", null); equal($.views.helpers.concat2, undefined, 'Remove a registered helper: $.views.helpers({ concat: null })' ); }); test("template encapsulation", function() { expect(1); $.templates({ myTmpl6: { markup: "{{sort reverse=true people}}{{:name}}{{/sort}}", tags: { sort: sort } } }); equal( $.render.myTmpl6({ people: people }), "BillJo", '$.templates( "myTmpl", tmplObjWithNestedItems );' ); });