├── .gitattributes ├── .gitignore ├── .npmignore ├── MIT-LICENSE.txt ├── README.md ├── bower.json ├── demos ├── demos.html ├── index.html ├── resources │ ├── demos.css │ └── movielist.css ├── scenarios │ ├── 01_default-values-scenario.html │ ├── 02_separators-scenario.html │ ├── 03_iterating-through-fields-scenario.html │ ├── 04_assigning-variables-scenario.html │ └── 05_arrays-plus-headers-and-footers.html ├── step-by-step │ ├── 01_inserting-data.html │ ├── 02_compiling-named-templates-from-strings.html │ ├── 03_converters-and-encoding.html │ ├── 04_if-else-tag.html │ ├── 05_for-tag.html │ ├── 06_template-composition.html │ ├── 07_paths.html │ ├── 08_custom-tags.html │ ├── 09_helper-functions.html │ ├── 10_comparison-tests.html │ ├── 11_accessing-parent-data.html │ ├── 12_passing-in-context.html │ ├── 13_associating-helpers-with-templates.html │ └── 20_without-jquery.html └── variants │ ├── accessing-templates │ ├── 01_compiling-template-objects-from-strings.html │ ├── 02_registering-named-template-from-script-declaration.html │ ├── 03_getting-template-objects-from-script-declaration.html │ ├── 04_template-composition-subtemplates.html │ └── 05_template-composition-templateobjects.html │ └── variants.html ├── gulpfile.js ├── index.js ├── jsrender-node.js ├── jsrender.js ├── jsrender.min.js ├── jsrender.min.js.map ├── package-lock.json ├── package.json ├── test ├── browserify │ ├── 1-unit-tests.js │ ├── 12-nested-unit-tests.js │ ├── 2-unit-tests.js │ ├── 3-unit-tests.js │ ├── bundles │ │ ├── 1-bundle.js │ │ ├── 12-nested-bundle.js │ │ ├── 2-bundle.js │ │ ├── 3-bundle.js │ │ ├── browserify-bundles-go-here.txt │ │ ├── htm-jsrender-tmpl-bundle.js │ │ └── html-jsr-tmpl-bundle.js │ ├── htm-jsrender-tmpl.js │ ├── html-jsr-tmpl.js │ └── tests-browserify-completed.js ├── index.html ├── perf-compare.html ├── requirejs │ ├── require.js │ └── require.min.js ├── resources │ ├── dot.js │ ├── dust-full.js │ ├── handlebars.js │ ├── hogan.js │ ├── jquery.tmpl.js │ └── perf-compare.css ├── templates │ ├── file │ │ └── path.html │ ├── inner.html │ ├── name-template.htm │ ├── name-template.html │ ├── name-template.jsr │ ├── name-template.jsrender │ └── outer.html ├── test-amd-scriptloader-no-jquery.html ├── test.min.map.html ├── unit-tests-amd-scriptloader.html ├── unit-tests-browserify.html ├── unit-tests-jsrender-no-jquery.html ├── unit-tests-jsrender-with-jquery.html ├── unit-tests-node-runner.js └── unit-tests │ ├── requirejs-config.js │ ├── tests-jsrender-amd-scriptloader.js │ ├── tests-jsrender-no-jquery-withIE.js │ ├── tests-jsrender-no-jquery.js │ ├── tests-jsrender-with-jquery-withIE.js │ ├── tests-jsrender-with-jquery.js │ └── tests-node.js ├── tmplify └── index.js └── typescript └── jsrender ├── index.d.ts └── test ├── tests.html ├── tests.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # JS files must always use LF for tools to work 5 | *.js eol=lf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | node_modules 3 | bower_components 4 | *.config 5 | desktop.ini 6 | built 7 | Scripts 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | 3 | /test/browserify/bundles/*.js 4 | *.config 5 | gulpfile.js 6 | desktop.ini 7 | /Scripts 8 | /node_modules 9 | /bower_components 10 | /demos 11 | /test 12 | /-* 13 | built -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Boris Moore https://github.com/BorisMoore/jsrender 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## JsRender: best-of-breed templating 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/jsrender)](https://www.npmjs.com/package/jsrender) 4 | [![CDNJS version](https://img.shields.io/cdnjs/v/jsrender.svg)](https://cdnjs.com/libraries/jsrender) 5 | 6 | *Simple and intuitive, powerful and extensible, lightning fast* 7 | 8 | *For templated content in the browser or on Node.js (with Express 4, Hapi and Browserify integration)* 9 | 10 | **JsRender** is a light-weight but powerful templating engine, highly extensible, and optimized for high-performance rendering, without DOM dependency. It is designed for use in the browser or on Node.js, with or without jQuery. 11 | 12 | **[JsRender](https://github.com/BorisMoore/jsrender)** and **[JsViews](https://github.com/BorisMoore/jsviews)** together provide the next-generation implementation of the official jQuery plugins *[JQuery Templates](https://github.com/BorisMoore/jquery-tmpl)*, and *[JQuery Data Link](https://github.com/BorisMoore/jquery-datalink)* -- and supersede those libraries. 13 | 14 | ### Documentation and downloads 15 | 16 | **[Documentation](http://www.jsviews.com)**, **[downloads](http://www.jsviews.com/#download)**, **[samples](http://www.jsviews.com/#samples)** and **[API docs and tutorials](http://www.jsviews.com/#jsrapi)** are available on the **[www.jsviews.com website](http://www.jsviews.com/#jsrender)**. 17 | 18 | The content of this ***ReadMe*** is available also as a *[JsRender Quickstart](http://www.jsviews.com/#jsr-quickstart)*. 19 | 20 | ### JsRender and JsViews 21 | 22 | JsRender is used for data-driven rendering of templates to strings, ready for insertion in the DOM. 23 | 24 | It is also used by the *[JsViews](http://www.jsviews.com/#jsviews)* platform, which adds data binding to JsRender templates, and provides a fully-fledged MVVM platform for easily creating interactive data-driven single page apps and websites. 25 | 26 | ## JsRender installation 27 | 28 | *jsrender.js* is available from [downloads](http://www.jsviews.com/#download) on the jsviews.com site. 29 | 30 | *CDN delivery* is available from the ***[jsdelivr](https://jsdelivr.com)*** CDN at [jsdelivr.com/package/npm/jsrender](https://jsdelivr.com/package/npm/jsrender?tab=files), and from the ***[cdnjs](https://cdnjs.com)*** CDN at [cdnjs.com/libraries/jsrender](https://cdnjs.com/libraries/jsrender). 31 | Alternatively: 32 | - It can be installed with **[Bower](http://bower.io/search/?q=jsrender)**, using `$ bower install jsrender` 33 | - It can be loaded using an *AMD script loader*, such as RequireJS 34 | - For installation using *Node.js* (*npm*) see *[JsRender Node.js Quickstart](http://www.jsviews.com/#jsr-node-quickstart)* 35 | - (For browser loading using *Browserify* or *webpack* - see *[JsRender Node.js Quickstart](http://www.jsviews.com/#jsr-node-quickstart)*, *[JsRender as a Browserify module](http://www.jsviews.com/#node/browserify@jsrender)* and *[JsRender as a webpack module](http://www.jsviews.com/#node/webpack@jsrender)*) 36 | 37 | #### Using JsRender with jQuery 38 | 39 | When jQuery is present, JsRender loads as a jQuery plugin and adds `$.views`, `$.templates` and `$.render` to the jQuery namespace object, `$` (or `window.jQuery`). 40 | 41 | *Example HTML page:* [JsRender with jQuery](http://www.jsviews.com/#download/pages-jsr-jq) 42 | 43 | #### Using JsRender without jQuery 44 | 45 | When jQuery is not present, JsRender provides its own global namespace object: `jsrender` (or `window.jsrender`) 46 | 47 | The `jsrender` namespace provides the same methods/APIs as with jQuery, so if jQuery is not present you can still use all the API examples, by simply writing: 48 | 49 | ```js 50 | var $ = window.jsrender; 51 | 52 | // Now use code as in samples/examples, with $.views... $.templates... $.render... 53 | ``` 54 | (*Note:* If jQuery is not loaded, then [passing a jQuery selector](http://www.jsviews.com/#compiletmpl@fromscriptblock) to `$.templates()` will only work for the *ID selector*) 55 | 56 | *Example HTML page:* [JsRender without jQuery](http://www.jsviews.com/#download/pages-jsr) 57 | 58 | #### JsRender on Node.js 59 | 60 | JsRender can be used to render templates on the server (using Node.js) as well as in the browser. JsRender on Node.js has all the features and APIs of JsRender in the browser, plus some additional ones specific to Node.js. 61 | 62 | It also provides built-in *Express*, *Hapi* and *Browserify* integration -- which makes it easy to register templates as simple `.html` files on the file system, and then load and render them either server-side, client-side or both. 63 | 64 | **Learn more:** *[JsRender Node.js Quickstart](http://www.jsviews.com/#jsr-node-quickstart)* and *[JsRender APIs for Node.js](http://www.jsviews.com/#jsrnode)*. 65 | 66 | **Code samples:** See *[JsRender Node Starter](https://github.com/BorisMoore/jsrender-node-starter)* for running code examples of Node.js scenarios, including with *Express*, *Hapi* and *Browserify*. 67 | 68 | ## JsRender usage 69 | 70 | ### _Define a template_ 71 | 72 | From a string: 73 | 74 | ```js 75 | var tmpl = $.templates("Name: {{:name}}"); 76 | ``` 77 | 78 | From a template declared as markup in a script block: 79 | 80 | ```html 81 | 84 | ``` 85 | 86 | then, somewhere in your script: 87 | 88 | ```js 89 | var tmpl = $.templates("#myTemplate"); // Pass in a jQuery selector for the script block 90 | ``` 91 | 92 | On Node.js, [from an .html file](https://www.jsviews.com/#jsr-node-quickstart@htmlfiles) containing the template markup: 93 | 94 | ```js 95 | var $ = require('jsrender'); // returns the jsrender namespace object 96 | var tmpl = $.templates("./templates/myTemplate.html"); 97 | ``` 98 | 99 | [Learn more...](http://www.jsviews.com/#d.templates) 100 | 101 | ### _Render a template_ 102 | 103 | `tmpl.render(object)` (or shortcut form: `tmpl(object)`) renders the template with the object as data context. 104 | 105 | ```js 106 | var tmpl = $.templates(" Name: {{:name}}
"); 107 | 108 | var person = {name: "Jim"}; 109 | 110 | // Render template for person object 111 | var html = tmpl.render(person); // ready for insertion, e.g $("#result").html(html); 112 | 113 | // result: "Name: Jim
" 114 | ``` 115 | 116 | `tmpl.render(array)` (or `tmpl(array)`) renders the template once for each item in the array. 117 | 118 | ```js 119 | var people = [{name: "Jim"}, {name: "Pedro"}]; 120 | 121 | // Render template for people array 122 | var html = tmpl.render(people); // ready for insertion... 123 | 124 | // result: "Name: Jim
Name: Pedro
" 125 | ``` 126 | 127 | [Learn more...](http://www.jsviews.com/#rendertmpl) 128 | 129 | ### _Register a named template - and render it_ 130 | 131 | ```js 132 | // Register named template - "myTmpl1 133 | $.templates("myTmpl1", "Name: {{:name}}
"); 134 | 135 | var person = {name: "Jim"}; 136 | 137 | // Render named template 138 | var html = $.templates.myTmpl1(person); 139 | 140 | // Alternative syntax: var html = $.render.myTmpl1(person); 141 | 142 | // result: "Name: Jim
" 143 | ``` 144 | 145 | [Learn more...](http://www.jsviews.com/#rendertmpl) 146 | 147 | ### _Template tags_ 148 | 149 | #### Template tag syntax 150 | 151 | - All tags other than `{{: ...}}` `{{> ...}}` `{{* ...}}` `{{!-- --}}` behave as *block tags* 152 | 153 | - Block tags can have content, unless they use the self-closing syntax: 154 | - *Block tag - with content:* `{{someTag ...}} content {{/someTag}}` 155 | - *Self-closing tag - no content (empty):* `{{someTag .../}}` 156 | 157 | - A particular case of self-closing syntax is when any block tag uses the named parameter `tmpl=...` to reference an external template, which then replaces what would have been the block content: 158 | 159 | - *Self-closing block tag referencing an external template:* `{{someTag ... tmpl=.../}}` 160 | (This lets you do [template composition](http://www.jsviews.com/#tagsyntax@composition). See [example](http://www.jsviews.com/#samples/jsr/composition/tmpl).) 161 | 162 | - Tags can take both unnamed arguments and named parameters: 163 | - `{{someTag argument1 param1=...}} content {{/someTag}}` 164 | - an example of a named parameter is the `tmpl=...` parameter mentioned above 165 | - arguments and named parameters can be assigned values from simple data-paths such as `address.street` or from richer expressions such as `product.quantity * 3.1 / 4.5`, or `name.toUpperCase()` 166 | 167 | [Learn more...](http://www.jsviews.com/#tagsyntax) 168 | 169 | #### Built-in tags 170 | 171 | #### _{{: ...}}_ (Evaluate) 172 | 173 | `{{: pathOrExpr}}` inserts the value of the path or expression. 174 | 175 | ```js 176 | var data = {address: {street: "Main Street"} }; 177 | var tmpl = $.templates("Street: {{:address.street}}"); 178 | var html = tmpl.render(data); 179 | 180 | // result: "Street: Main Street" 181 | ``` 182 | 183 | [Learn more...](http://www.jsviews.com/#assigntag) 184 | 185 | #### _{{> ...}}_ (HTML-encode) 186 | 187 | `{{> pathOrExpr}}` inserts the *HTML-encoded* value of the path or expression. 188 | 189 | ```js 190 | var data = {condition: "a < b"}; 191 | var tmpl = $.templates("Formula: {{>condition}}"); 192 | var html = tmpl.render(data); 193 | 194 | // result: "Formula: a < b" 195 | ``` 196 | 197 | [Learn more...](http://www.jsviews.com/#htmltag) 198 | 199 | #### _{{include ...}}_ (Template composition - partials) 200 | 201 | `{{include pathOrExpr}}...{{/include}}`evaluates the block content against a specified/modified data context. 202 | 203 | `{{include ... tmpl=.../}}` evaluates the specified template against an (optionally modified) context, and inserts the result. (Template composition). 204 | 205 | ```js 206 | var data = {name: "Jim", address: {street: "Main Street"} }; 207 | 208 | // Register two named templates 209 | $.templates({ 210 | streetTmpl: "{{:street}}", 211 | addressTmpl: "{{:name}}'s address is {{include address tmpl='streetTmpl'/}}." 212 | }); 213 | 214 | // Render outer template 215 | var html = $.templates.addressTmpl(data); 216 | 217 | // result: "Jim's address is Main Street" 218 | ``` 219 | 220 | [Learn more...](http://www.jsviews.com/#includetag) 221 | 222 | #### _{{for ...}}_ (Template composition, with iteration over arrays) 223 | 224 | `{{for pathOrExpr}}...{{/for}}`evaluates the block content against a specified data context. If the new data context is an array, it iterates over the array, renders the block content with each data item as context, and concatenates the result. 225 | 226 | `{{for pathOrExpr tmpl=.../}}` evaluates the specified template against a data context. If the new data context is an array, it iterates over the array, renders the template with each data item as context, and concatenates the result. 227 | 228 | ```html 229 | 234 | ``` 235 | 236 | ```js 237 | var data = {people: [{name: "Jim"}, {name: "Pedro"}] }; 238 | var tmpl = $.templates("#peopleTmpl"); 239 | var html = tmpl.render(data); 240 | 241 | // result: "" 242 | ``` 243 | 244 | [Learn more...](http://www.jsviews.com/#fortag) 245 | 246 | #### _{{props ...}}_ (Iteration over properties of an object) 247 | 248 | `{{props pathOrExpr}}...{{/prop}}` or `{{props pathOrExpr tmpl=.../}}` iterates over the properties of the object returned by the path or expression, and renders the content/template once for each property - using as data context: `{key: propertyName, prop: propertyValue}`. 249 | 250 | ```html 251 | 256 | ``` 257 | 258 | ```js 259 | var data = {person: {first: "Jim", last: "Varsov"} }; 260 | var tmpl = $.templates("#personTmpl"); 261 | var html = tmpl.render(data); 262 | 263 | // result: "" 264 | ``` 265 | 266 | [Learn more...](http://www.jsviews.com/#propstag) 267 | 268 | #### _{{if ...}}_ (Conditional inclusion) 269 | 270 | `{{if pathOrExpr}}...{{/if}}` or `{{if pathOrExpr tmpl=.../}}` renders the content/template only if the evaluated path or expression is 'truthy'. 271 | 272 | `{{if pathOrExpr}}...{{else pathOrExpr2}}...{{else}}...{{/if}}` behaves as '*if' - 'else if' - 'else'* and renders each block based on the conditions. 273 | 274 | ```html 275 | 284 | ``` 285 | 286 | ```js 287 | var data = {nickname: "Jim", name: "James"}; 288 | var tmpl = $.templates("#personTmpl"); 289 | var html = tmpl.render(data); 290 | 291 | // result: "Nickname: Jim" 292 | ``` 293 | 294 | [Learn more...](http://www.jsviews.com/#iftag) 295 | 296 | #### Other built-in tags 297 | 298 | For details on all the above built-in tags, as well as *[comment tags](http://www.jsviews.com/#commenttag)* _{{!-- ... --}}_ and *[allow code tags](http://www.jsviews.com/#allowcodetag)* _{{\* ... }} and {{\*: ...}}_, see the [tags documentation](http://www.jsviews.com/#jsrtags) on jsviews.com. 299 | 300 | #### Custom tags 301 | 302 | Creating your own custom tags is easy. You can provide an object, with render method, template, event handlers, etc. See samples [here](http://www.jsviews.com/#samples/jsr/tags) and [here](http://www.jsviews.com/#samples/tag-controls) on jsviews.com. But for simple tags, you may only need a simple render function, or a template string. 303 | 304 | For example the two following definitions for a `{{fullName/}}` tag provide equivalent behavior: 305 | 306 | As a render function: 307 | 308 | ```js 309 | $.views.tags("fullName", function(val) { 310 | return val.first + " " + val.last; 311 | }); 312 | ``` 313 | 314 | Or as a template string: 315 | 316 | ```js 317 | $.views.tags("fullName", "{{:first}} {{:last}}"); 318 | ``` 319 | 320 | Either way, the result will be as follows: 321 | 322 | ```js 323 | var tmpl = $.templates("{{fullName person/}}"); 324 | var data = {person: {first: "Jim", last: "Varsov"}}; 325 | var html = tmpl.render(data); 326 | 327 | // result: "Jim Varsov" 328 | ``` 329 | 330 | ### _Helpers_ 331 | 332 | For details on helpers, see the [Helpers](http://www.jsviews.com/#helpers) documentation topic on jsviews.com. 333 | 334 | Here is a simple example. Two helpers - a function, and a string: 335 | 336 | ```js 337 | var myHelpers = { 338 | upper: function(val) { return val.toUpperCase(); }, 339 | title: "Sir" 340 | }; 341 | ``` 342 | 343 | Access the helpers using the `~myhelper` syntax: 344 | 345 | ```js 346 | var tmpl = $.templates("{{:~title}} {{:first}} {{:~upper(last)}}"); 347 | ``` 348 | 349 | We can pass the helpers in with the `render()` method 350 | 351 | ```js 352 | var data = {first: "Jim", last: "Varsov"}; 353 | 354 | var html = tmpl.render(data, myHelpers); 355 | 356 | // result: "Sir Jim VARSOV" 357 | ``` 358 | 359 | Or we can register helpers globally: 360 | 361 | ```js 362 | $.views.helpers(myHelpers); 363 | 364 | var data = {first: "Jim", last: "Varsov"}; 365 | var html = tmpl.render(data); 366 | 367 | // result: "Sir Jim VARSOV" 368 | ``` 369 | 370 | [Learn more...](http://www.jsviews.com/#helpers) 371 | 372 | ### _Converters_ 373 | 374 | Converters are used with the `{{:...}}` tag, using the syntax `{{mycvtr: ...}}}`. 375 | 376 | Example - an *upper* converter, to convert to upper case: 377 | 378 | ```js 379 | $.views.converters("upper", function(val) { return val.toUpperCase(); }); 380 | 381 | var tmpl = $.templates("{{:first}} {{upper:last}}"); 382 | var data = {first: "Jim", last: "Varsov"}; 383 | var html = tmpl.render(data); 384 | 385 | // result: "Jim VARSOV" 386 | ``` 387 | 388 | [Learn more...](http://www.jsviews.com/#converters) 389 | 390 | ### _Logic and expressions_ 391 | 392 | JsRender supports rich expressions and logic, but at the same time encapsulates templates to prevent random access to globals. If you want to provide access to global variables within a template, you have to pass them in as data or as helpers. 393 | 394 | You can assign rich expressions to any template arguments or parameters, as in: 395 | 396 | `{{:person.nickname ? "Nickname: " + person.nickname : "(has no nickname)"}}` 397 | 398 | or 399 | 400 | ```html 401 | {{if ~limits.maxVal > (product.price*100 - discount)/rate}} 402 | ... 403 | {{else ~limits.minVal < product.price}} 404 | ... 405 | {{else}} 406 | ... 407 | {{/if}} 408 | ``` 409 | 410 | ### _Documentation and APIs_ 411 | 412 | See the [www.jsviews.com](http://www.jsviews.com) site, including the *[JsRender Quickstart](http://www.jsviews.com/#jsr-quickstart)* and [JsRender APIs](http://www.jsviews.com/#jsrapi) topics. 413 | 414 | ### _Demos_ 415 | 416 | Demos and samples can be found at [www.jsviews.com/#samples](http://www.jsviews.com/#samples/jsr), and throughout the [API documentation](http://www.jsviews.com/#jsrapi). 417 | 418 | (See also the [demos](https://github.com/BorisMoore/jsrender/tree/master/demos) folder of the GitHub repository - available [here](http://borismoore.github.io/jsrender/demos/index.html) as live samples). 419 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsrender", 3 | "main": "jsrender.js", 4 | "homepage": "http://www.jsviews.com/#jsrender", 5 | "authors": [ 6 | { 7 | "name": "Boris Moore", 8 | "email": "borismoore@gmail.com", 9 | "homepage": "https://github.com/borismoore" 10 | } 11 | ], 12 | "description": "Best-of-breed templating in browser or on Node.js (with Express 4, Hapi and Browserify integration)", 13 | "moduleType": [ 14 | "amd", 15 | "globals", 16 | "node" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/borismoore/jsrender.git" 21 | }, 22 | "keywords": [ 23 | "jsrender", 24 | "jquery", 25 | "node", 26 | "express", 27 | "hapi", 28 | "browserify", 29 | "templates", 30 | "template" 31 | ], 32 | "license": "MIT", 33 | "ignore": [ 34 | "**/.*", 35 | "node_modules", 36 | "bower_components", 37 | "test", 38 | "demos", 39 | "*.md", 40 | "gulpfile.js", 41 | "package.json" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /demos/demos.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | JsRender: Demos 5 | 6 | 7 | 8 | 9 |
10 |

The demos below have been superseded by the documentation 11 | and samples on jsviews.com, 12 | which are more up-to-date and complete

13 |
14 | 15 |
<< Index for JsRender and JsViews
16 | 17 |

JsRender: Demos

18 | 19 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JsRender: Demos 5 | 6 | 7 | 8 | 9 |

JsRender: Next-generation jQuery Templates

10 | 11 |
JsRender:
12 | 13 |
14 |
JsRender is a lightweight but powerful templating engine, highly extensible, 15 | and optimized for high-performance pure string-based rendering, without DOM or jQuery dependency. 16 |
17 |
JsRender and JsViews together provide the next-generation implementation of both JQuery Templates, and JQuery Data Link - and supersede those libraries.
18 |
Documentation, samples and downloads 19 | are available on the www.jsviews.com website. 20 |
21 |
22 | 23 |
24 |

Demos

25 |
The primary location for JsViews or JsRender 26 | samples and demos is now on jsviews.com
27 |
See also the following links:
28 |
JsRender: Demos
29 |
Source code:
30 |
https://github.com/BorisMoore/jsrender
31 |
32 | 33 |
See also on JsViews site:
34 | 35 |
36 |
JsViews are interactive data-driven views, built on top of JsRender templates
37 |
JsViews: Demos
38 |
JsViews and JsRender Overview:
39 |
Demo sequence from jQuery Conference October 2011
40 |
41 | 42 |
Other links:
43 | 44 |
45 |
Slide deck: jQuery Conference October 2011
46 |
MSDN 'Client Insight' articles on JsRender part one and 47 | part two
48 |
Training course: JsRender Fundamentals from John Papa on Pluralsight (3 hours of video)
49 |
50 | 51 |
Tests:
52 | 53 |
54 |
JsRender unit tests - with jQuery
55 |
JsRender unit tests - without jQuery
56 |
Additional tests
57 |
Perf comparison
58 |
59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /demos/resources/demos.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 10px; 3 | font-family: Verdana; 4 | font-size: small; 5 | } 6 | 7 | h4 { 8 | font-size: inherit`; 9 | font-variant: small-caps; 10 | } 11 | 12 | .height { 13 | width: 100%; 14 | margin-bottom: 10px; 15 | float: left; 16 | clear: both; 17 | } 18 | 19 | .bottom { 20 | height: 400px; 21 | width: 100%; 22 | margin-bottom: 10px; 23 | float: left; 24 | clear: both; 25 | } 26 | 27 | body > button { 28 | float: left; 29 | clear: right; 30 | margin: 3px; 31 | } 32 | 33 | .subhead { 34 | margin: 3px 0 5px 0; 35 | font-weight: bolder; 36 | color: #116; 37 | font-family: Arial; 38 | font-size: 10pt; 39 | } 40 | 41 | a { 42 | color: #55b; 43 | } 44 | 45 | pre { 46 | font-size: 10pt; 47 | font-weight: bold; 48 | } 49 | 50 | .inset { 51 | padding-left: 18px; 52 | } 53 | 54 | .box { 55 | border: 1px solid #777; 56 | padding: 10px; 57 | margin: 5px 0 30px; 58 | } 59 | 60 | .box div { 61 | margin: 3px 0 10px 0; 62 | } 63 | 64 | .box .label { 65 | margin: 0; 66 | padding: 10px 0 0 0; 67 | font-style: italic; 68 | color: #55b; 69 | } 70 | 71 | .box.label { 72 | font-style: italic; 73 | color: #55b; 74 | } 75 | 76 | .desc { 77 | font-style: italic; 78 | margin: 0 0 15px; 79 | color: #116; 80 | } 81 | 82 | pre { 83 | border-left: 3px solid #aaa; 84 | padding: 10px; 85 | margin-bottom: 30px; 86 | } 87 | 88 | .indexitems { 89 | list-style-type: none; 90 | padding-left: 10px; 91 | margin: 0 0 20px; 92 | } 93 | 94 | .indexitems li { 95 | margin-top: 14px; 96 | } 97 | 98 | .indexitems .inset { 99 | margin-top: 6px; 100 | } 101 | 102 | h3 { 103 | margin-bottom: 10px; 104 | font-size: 11pt; 105 | } 106 | 107 | select { 108 | margin-bottom: 15px; 109 | width: 200px; 110 | } 111 | -------------------------------------------------------------------------------- /demos/resources/movielist.css: -------------------------------------------------------------------------------- 1 | table tr { 2 | color: blue; 3 | height: 25px; 4 | } 5 | 6 | thead { 7 | color: #009; 8 | border-bottom: solid #77c 2px; 9 | background-color: #E8E8F7; 10 | } 11 | 12 | thead th { 13 | padding: 5px; 14 | border: 1px solid #77c; 15 | } 16 | 17 | #movieList tr td:first-child { 18 | width: 120px; 19 | } 20 | 21 | table { 22 | border-collapse: collapse; 23 | border: 2px solid blue; 24 | width: 650px; 25 | margin: 4px 0 24px 4px; 26 | padding: 2px; 27 | background-color: #f8f8f8; 28 | } 29 | 30 | table td { 31 | padding: 3px; 32 | margin: 3px; 33 | border: solid #77c 1px; 34 | } 35 | -------------------------------------------------------------------------------- /demos/scenarios/01_default-values-scenario.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | JsRender Demos
15 | 16 |

Example Scenario: providing default values for data.

17 | 18 | 19 | 20 |
The simplest (and best) way: Javascript expression '||':
21 | 22 |
 23 | {{>languages||'Languages unavailable'}}
 24 | 
25 | 26 | 27 | 28 | 29 |
Title{{>path}}
30 | 31 | 32 | 33 |
Creating a special custom tag:
34 | 35 |
 36 | {{get languages defaultValue="No languages!"/}}
 37 | 
 38 | $.views.tags({
 39 |     get: function( value ) {
 40 |         return value || this.ctx.props.defaultValue;
 41 |     }
 42 | });
 43 | 
44 | 45 | 46 | 47 | 48 |
Title{{get path default="..."}}
49 | 50 | 51 | 52 |
Creating a multi-purpose utility tag:
53 | 54 |
 55 | {{yesNo languages yes="Alternate languages available:" no="No alternate languages"/}}
 56 | 
 57 | $.views.tags({
 58 |     yesNo: function( value ) {
 59 |         return value ? this.tagCtx.props.yes : this.tagCtx.props.no;
 60 |     }
 61 | });
 62 | 
63 | 64 | 65 | 66 | 67 |
Title{{yesNo path yes="..." no="..."}}
68 | 69 | 70 | 71 | 72 | 73 | 81 | 82 | 90 | 91 | 100 | 101 | 102 | 103 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /demos/scenarios/02_separators-scenario.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

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

14 | 15 | 16 | 17 |
Example 1: Expressions in tags, and template parameters ({{if}} tag):
18 | 19 |
 20 |     {{for languages ~count=languages.length}}
 21 |         ...
 22 |         {{if #index === ~count-2}} and {{else #index < ~count-2}}, {{/if}}
 23 |         ...
 24 |     {{/for}}
 25 | 
26 | 27 | 37 | 38 | 39 | 40 | 41 |
TitleLanguages
42 | 43 | 44 | 45 |
Example 2: Expressions in tags, and template parameters (ternary expression):
46 | 47 |
 48 |     {{for languages ~count=languages.length}}
 49 |         ...
 50 |         {{: #index === ~count-2 ? " and " : #index < ~count-2 ? ", " : ""}}
 51 |         ...
 52 |     {{/for}}
 53 | 
54 | 55 | 65 | 66 | 67 | 68 | 69 |
TitleLanguages
70 |
71 | 72 | 73 | 74 |
Example 3: Custom helper functions:
75 | 76 |
 77 |     {{for languages}}
 78 |         ...
 79 |         {{if ~nextToLast()}} and {{else ~notLast()}}, {{/if}}
 80 |         ...
 81 |     {{/for}}
 82 | 
83 | 84 | 94 | 95 | 96 | 97 | 98 |
TitleLanguages
99 |
100 | 101 | 102 | 103 |

Using "allowCode"

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

The following two examples illustrate its use, but are not the recommended approach. The built-in expression support, 109 |
custom tags, helper functions etc. provide a better solution for almost all scenarios, as in the two examples above.
110 | 111 |
Example 4: allowCode, for program flow - with if(...) { ... }:
112 | 113 |
114 | $.templates( "movieTmpl", {
115 |     markup: "#movieTemplate",
116 |     allowCode: true
117 | });
118 | 
119 | {{*
120 |     if ( myexpression ) {
121 | }}
122 |     ...
123 | {{*
124 |     }
125 | }}
126 | 
127 | 128 | 146 | 147 | 148 | 149 | 150 |
TitleLanguages
151 | 152 | 153 | 154 |
Example 5: allowCode, for returning content - with ternary expression:
155 | 156 |
157 | $.templates( "movieTmpl", {
158 |     markup: "#movieTemplate",
159 |     allowCode: true
160 | });
161 | 
162 | {{*: myexpression ? ... : ...}}
163 | 
164 | 165 | 176 | 177 | 178 | 179 | 180 |
TitleLanguages
181 | 182 | 183 | 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /demos/scenarios/03_iterating-through-fields-scenario.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

Example Scenario: Using {{props}} tag to iterate through fields

14 | 15 |
16 | {{props details}}
17 |     <b>{{>key}}</b>: {{>prop}}
18 | {{/props}}
19 | 
20 | 21 | 22 | 23 | 24 |
TitleDetails
25 | 26 | 27 | 28 | 29 | 30 | 42 | 43 | 44 | 45 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /demos/scenarios/04_assigning-variables-scenario.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

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

14 | 15 |
16 | Note: This scenario implies understanding the processing sequence of template rendering, 17 |
and is somewhat in contradiction with the 'logicless' and declarative philosophy. 18 |
However it illustrates the power of the custom tags and helper function extensibility, 19 |
and is useful in certain advanced scenarios. 20 |
21 | 22 |
Declare setvar custom tag and getvar custom helper function
23 |
 24 | var vars = {};
 25 | 
 26 | $.views.tags({
 27 |     setvar: function(key, value) {
 28 |         ...
 29 |         vars[key] = value;
 30 |         ...
 31 |     }
 32 | });
 33 | 
 34 | $.views.helpers({
 35 |     getvar: function(key) {
 36 |         return vars[key];
 37 |     }
 38 | })
 39 | 
40 | 41 |
Use {{setvar}} to assign values or rendered content to variable
42 |
 43 | {{setvar "summary" languages/}}
 44 | 
45 | 46 |
 47 | {{setvar "summary"}}
 48 |     <b>Subtitles only:</b> {{>subtitles}}
 49 | {{/setvar}}
 50 | 
51 | 52 | 53 |
Use {{:~getvar}} to take values stored in the variable, and render them elsewhere in the template
54 |
 55 | {{:~getvar('summary')}}
 56 | 
57 | 58 | 80 | 81 | 82 | 83 | 84 |
titlelanguagessummary
85 | 86 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /demos/scenarios/05_arrays-plus-headers-and-footers.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

To render a non-repeating template against an array, with content above or below the repeating items,
14 | wrap the array - as render([myArray]) - and include {{for #data}} for the repeating section

15 | 16 |
Top-level layout:
17 |
 18 | $( "#movieList" ).html(
 19 |     // Pass second parameter true to choose noIteration even for arrays.
 20 |     $("#movieTemplate").render(movies, true)
 21 | );
 22 | 
 23 | Template:
 24 | 
 25 |    header
 26 |    {{for}}
 27 |       item
 28 |    {{/for}}
 29 |    footer
 30 | 
31 | 32 |
Nested layout:
33 |
 34 | {{include languages}}
 35 |     header
 36 |     {{for}}
 37 |         item
 38 |     {{/for}}
 39 |     footer
 40 | {{/for}}
 41 | 
42 | 43 | 44 | 45 | 77 | 78 |
79 | 80 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /demos/step-by-step/01_inserting-data.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | JsRender Demos
10 | 11 |

Render template against local data

12 | 13 | 18 | 19 |
20 | 21 | 36 | 37 | -------------------------------------------------------------------------------- /demos/step-by-step/02_compiling-named-templates-from-strings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

Compiling named templates from strings

14 | 15 |
16 |
17 | 18 | 19 |
20 | 21 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /demos/step-by-step/03_converters-and-encoding.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | JsRender Demos
17 | 18 |

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

19 | 20 |
28 |
29 | Note: A common use for converters is to protect against injection attacks from untrusted data. 30 |
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. 31 |
In the context of HTML attributes, use {{attr: }}.
32 | 33 | 41 | 42 | 43 | 44 | 45 |
Title (loc:English)Title (loc:French)No ConvertHTML Encode
46 | 47 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /demos/step-by-step/04_if-else-tag.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

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

14 | 15 | 29 | 30 | 31 | 32 | 33 |
titlelanguages
34 | 35 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /demos/step-by-step/05_for-tag.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

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

14 | 15 | 30 | 31 | 32 | 33 | 34 |
TitleLanguages
35 | 36 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /demos/step-by-step/06_template-composition.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

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

14 | 15 | 37 | 38 | 41 | 42 | 45 | 46 | 51 | 52 | 57 | 58 | 63 | 64 | 70 | 71 | 72 | 73 | 74 |
SynopsisFixed TemplateTemplate specified in dataConditional TemplateWrapper TemplateRepeating Wrapper Template
75 | 76 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /demos/step-by-step/07_paths.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | JsRender Demos
10 | 11 |

Accessing paths

12 | 13 | 54 | 55 | 65 | 66 |
67 | 68 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /demos/step-by-step/08_custom-tags.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | JsRender Demos
13 | 14 |

Custom Tags

15 | 16 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
Render method wrapping contentRender method with external contentTag template wrapping contentTag template with external contentSort tag: Reverse orderSort tag: Languages (external content)
56 | 57 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /demos/step-by-step/09_helper-functions.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

Helper functions

14 | 15 |
16 | {{>~format(name, "upper")}}
17 | 
18 | $.views.helpers({
19 | 
20 |     format: function( val, format ) {
21 |         ...
22 |         return val.toUpperCase();
23 |         ...
24 |     },
25 | 
26 |     ...
27 | });
28 | 
29 | 30 | 31 | 32 | 33 | 34 | 44 | 45 | 46 | 47 | 48 |
TitleLanguages
49 | 50 | 51 | 52 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /demos/step-by-step/10_comparison-tests.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

Comparison tests

14 | 15 |
16 | {{if !(languages && languages.length)}}...{{/if}}
17 | 
18 | {{if languages==null}}...{{/if}}
19 | 
20 | 21 | 22 | 23 | 34 | 35 | 38 | 39 | 40 | 41 | 42 |
Title{{if !(languages && languages.length)}}{{if a==null}}
43 | 44 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /demos/step-by-step/11_accessing-parent-data.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | JsRender Demos
15 | 16 |

Example Scenario: Accessing parent data.

17 | 18 | 19 | 20 |
Stepping up through the views (tree of nested rendered templates)
21 | 22 |
 23 | var model = {
 24 |     specialMessage: function(...) { ... },
 25 |     theater: "Rialto",
 26 |     movies: [ ... ]
 27 | }
 28 | 
 29 | {{for movies}}
 30 |     <tr>
 31 |         <td>'{{>title}}': showing at the '{{>#parent.parent.data.theater}}'</td>
 32 | 
33 | 34 | 35 | 36 | 37 |
TitleLanguages (+specialMessage)
38 | 39 | 40 | 41 |
Setting contextual template parameters, accessible in all nested contexts as ~nameOfParameter:
42 | 43 |
 44 | {{for movies ~theater=theater}}
 45 |     <tr>
 46 |         <td>'{{>title}}': showing at the '{{>~theater}}'</td>
 47 | 
48 | 49 | 50 | 51 | 52 |
TitleLanguages (+specialMessage)
53 | 54 | 55 | 56 |
Using the top-level data, accessible in all nested contexts as ~root:
57 | 58 |
 59 | {{for movies}}
 60 |     <tr>
 61 |         <td>'{{>title}}': showing at the '{{>~root.theater}}'</td>
 62 | 
63 | 64 | 65 | 66 | 67 |
TitleLanguages (+specialMessage)
68 | 69 | 70 | 71 | 72 | 73 | 87 | 88 | 100 | 101 | 113 | 114 | 115 | 116 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /demos/step-by-step/12_passing-in-context.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

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

14 | 15 |
Passing in contextual variables or helper functions, using the helpersOrContext parameter of ...render( data, helpersOrContext );
16 |
 17 | $( selector ).render( data, {
 18 |     reverseSort: reverse,
 19 |     format: myFormatFunction,
 20 |     buttonCaption: function(val) {
 21 |         ...
 22 |     }
 23 | })
 24 | 
25 | 26 |
Use ~name to access context variables or helpers by name - whether passed in with options,
27 |
registered globally as helpers, or registered as helpers for a specific template.
28 | 29 |
 30 | <th><button>{{>~buttonCaption('sort')}}</button></th>
 31 | ...
 32 | <td>{{>~format(title)}}</td>
 33 | ...
 34 | <td>{{sort languages reverse=~reverseSort}}...{{/sort}}</td>
 35 | 
36 | 37 | 38 | 39 | 61 | 62 |
63 |
64 | 65 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /demos/step-by-step/13_associating-helpers-with-templates.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | JsRender Demos
12 | 13 |

Associating specific contextual helpers with templates

14 | 15 |
Including helpers in a template definition.
16 |
 17 | $.templates({
 18 |     appTmpl: {
 19 |         markup:"#appTmpl",
 20 |         helpers: {
 21 |             supplierUtils: ...
 22 |         }
 23 |     }
 24 | });
 25 | 
26 | 27 |
Passing different helpers to a sub-template based on the context where it is used.
28 |
 29 | {{for suppliers tmpl="personTmpl" ~utils=~supplierUtils/}}
 30 | 
31 | 32 |
Accessing helper from nested template:
33 |
 34 | <b>ID:</b> <em>{{:~utils.format(id)}}</em>
 35 | 
36 | 37 | 38 | 39 |
40 | 41 | 52 | 53 | ​ 59 | 60 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /demos/step-by-step/20_without-jquery.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | JsRender Demos
11 | 12 |

JsRender without jQuery

13 | 14 |
 15 |     (function($) {
 16 |         ...
 17 |         $.templates({
 18 |             movieTemplate: document.getElementById( "movieTemplate" ).innerHTML,
 19 |             ...
 20 |         });
 21 | 
 22 |         document.getElementById( "movieList" ).innerHTML = $.render.movieTemplate( movies );
 23 | 
 24 |     })(this.jsrender);
 25 | 
26 | 27 | 39 | 40 | 43 | 44 |
45 | 46 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /demos/variants/accessing-templates/01_compiling-template-objects-from-strings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
<< JsRender Demos: Variants
12 | 13 |

Compiling template objects from strings

14 | 15 |
Variant of 'Compiling named templates' sample

16 | 17 |
18 |
19 | 20 | 21 |
22 | 23 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /demos/variants/accessing-templates/02_registering-named-template-from-script-declaration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
<< JsRender Demos: Variants
12 | 13 |

Register script block template declarations, as named templates

14 | 15 |
Variant of 'Compiling named templates' sample

16 | 17 | 22 | 23 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | 37 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /demos/variants/accessing-templates/03_getting-template-objects-from-script-declaration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
<< JsRender Demos: Variants
12 | 13 |

Getting template objects from script block template declarations

14 | 15 |
Variant of 'Compiling named templates' sample

16 | 17 | 22 | 23 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | 37 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /demos/variants/accessing-templates/04_template-composition-subtemplates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
<< JsRender Demos: Variants
12 | 13 |

Template composition. Registering and accessing sub-templates.

14 | 15 |
Variant of 'Template composition' sample

16 | 17 | 31 | 32 | 37 | 38 | 43 | 44 | 49 | 50 | 51 | 52 | 53 |
SynopsisFixed TemplateTemplate specified in dataConditional Template
54 | 55 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /demos/variants/accessing-templates/05_template-composition-templateobjects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
<< JsRender Demos: Variants
12 | 13 |

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

14 | 15 |
Variant of 'Template composition' sample

16 | 17 | 31 | 32 | 37 | 38 | 43 | 44 | 49 | 50 | 51 | 52 | 53 |
SynopsisFixed TemplateTemplate specified in dataConditional Template
54 | 55 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /demos/variants/variants.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | JsRender: Demos 5 | 6 | 7 | 8 | 9 |
<< JsRender Demos
10 | 11 |

JsRender: Variants and Details

12 | 13 |

Accessing templates

14 | 15 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | browserify = require('browserify'), 3 | fs = require('fs'); 4 | 5 | //================================= BUNDLE - Run Browserify - create client bundles for test cases =================================// 6 | // See https://github.com/gulpjs/gulp/blob/master/docs/recipes/browserify-with-globs.md 7 | 8 | // Task to create Browserify client-side bundle scripts for Browserify test cases. 9 | gulp.task('bundle', function() { 10 | var tmplify = require('./tmplify'); 11 | var gs = require('glob-stream'); 12 | 13 | // return gs.create('./test/browserify/*-unit-tests.js') 14 | return gs('./test/browserify/*-unit-tests.js') 15 | .on('data', function(file) { 16 | // file has path, base, and cwd attrs 17 | var fileName = file.path.slice(file.base.length, -14); 18 | browserify(file.path, {debug:true}) 19 | .transform(tmplify) 20 | .bundle() 21 | .pipe(fs.createWriteStream('./test/browserify/bundles/' + fileName + "-bundle.js")) 22 | .on('error', function(err) { 23 | // Make sure failed tests cause gulp to exit non-zero 24 | throw err; 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! JsRender 2 | * Version for web: jsrender.js 3 | * Version for Node.js (jsrender-node.js): - https://www.npmjs.com/package/jsrender 4 | */ 5 | 6 | 'use strict'; 7 | 8 | module.exports = require('./jsrender-node.js'); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsrender", 3 | "version": "v1.0.16", 4 | "description": "Best-of-breed templating in browser or on Node.js (with Express 4, Hapi and Browserify integration)", 5 | "main": "./jsrender-node.js", 6 | "browser": "./jsrender.js", 7 | "author": { 8 | "name": "Boris Moore", 9 | "url": "https://github.com/borismoore" 10 | }, 11 | "homepage": "http://www.jsviews.com/#jsrender", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/borismoore/jsrender.git" 15 | }, 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/borismoore/jsrender/issues" 19 | }, 20 | "scripts": { 21 | "test": "node ./test/unit-tests-node-runner.js" 22 | }, 23 | "types": "./typescript/jsrender/index.d.ts", 24 | "keywords": [ 25 | "jsrender", 26 | "node", 27 | "express", 28 | "hapi", 29 | "browserify", 30 | "templates", 31 | "template", 32 | "jquery-plugin", 33 | "ecosystem:jquery" 34 | ], 35 | "devDependencies": { 36 | "@types/jquery": "^3.3.31", 37 | "browserify": "^17.0.0", 38 | "gulp": "^5.0.0", 39 | "glob-stream": "^8.0.2", 40 | "jquery": "^3.4.1", 41 | "qunit": "^2.21.0" 42 | }, 43 | "dependencies": { 44 | "through2": "^3.0.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/browserify/1-unit-tests.js: -------------------------------------------------------------------------------- 1 | /*global QUnit, test, equal, ok*/ 2 | (function(undefined) { 3 | "use strict"; 4 | 5 | browserify.done.one = true; 6 | 7 | QUnit.module("Browserify - client code"); 8 | 9 | var isIE8 = window.attachEvent && !window.addEventListener; 10 | 11 | if (!isIE8) { 12 | 13 | test("No jQuery global: require('jsrender')()", function() { 14 | // ............................... Hide QUnit global jQuery and any previous global jsrender................................. 15 | var jQuery = global.jQuery, jsr = global.jsrender; 16 | global.jQuery = global.jsrender = undefined; 17 | 18 | // =============================== Arrange =============================== 19 | var data = {name: "Jo"}; 20 | 21 | // ................................ Act .................................. 22 | var jsrender = require('../../')(); // Not passing in jQuery, so returns the jsrender namespace 23 | 24 | // Use require to get server template, thanks to Browserify bundle that used jsrender/tmplify transform 25 | var tmpl = require('../templates/name-template.html')(jsrender); // Provide jsrender 26 | 27 | var result = tmpl(data); 28 | 29 | result += " " + (jsrender !== jQuery); 30 | 31 | // ............................... Assert ................................. 32 | equal(result, "Name: Jo (name-template.html) true", "result: No jQuery global: require('jsrender')()"); 33 | 34 | // ............................... Reset ................................. 35 | global.jQuery = jQuery; // Replace QUnit global jQuery 36 | global.jsrender = jsr; // Replace any previous global jsrender 37 | }); 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /test/browserify/12-nested-unit-tests.js: -------------------------------------------------------------------------------- 1 | /*global QUnit, test, equal, ok*/ 2 | (function(undefined) { 3 | "use strict"; 4 | 5 | browserify.done.twelve = true; 6 | 7 | QUnit.module("Browserify - client code"); 8 | 9 | var isIE8 = window.attachEvent && !window.addEventListener; 10 | 11 | if (!isIE8) { 12 | 13 | test("No jQuery global: require('jsrender')() nested template", function() { 14 | // ............................... Hide QUnit global jQuery and any previous global jsrender................................. 15 | var jQuery = global.jQuery, jsr = global.jsrender; 16 | global.jQuery = global.jsrender = undefined; 17 | 18 | // =============================== Arrange =============================== 19 | var data = {name: "Jo"}; 20 | 21 | // ................................ Act .................................. 22 | var jsrender = require('../../')(); // Not passing in jQuery, so returns the jsrender namespace 23 | 24 | // Use require to get server template, thanks to Browserify bundle that used jsrender/tmplify transform 25 | var tmpl = require('../templates/outer.html')(jsrender); // Provide jsrender 26 | 27 | var result = tmpl(data); 28 | 29 | result += " " + (jsrender !== jQuery); 30 | 31 | // ............................... Assert ................................. 32 | equal(result, "Name: Jo (outer.html) Name: Jo (inner.html) true", "result: No jQuery global: require('jsrender')(), nested templates"); 33 | 34 | // ............................... Reset ................................. 35 | global.jQuery = jQuery; // Replace QUnit global jQuery 36 | global.jsrender = jsr; // Replace any previous global jsrender 37 | }); 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /test/browserify/2-unit-tests.js: -------------------------------------------------------------------------------- 1 | /*global QUnit, test, equal, ok*/ 2 | (function(undefined) { 3 | "use strict"; 4 | 5 | browserify.done.two = true; 6 | 7 | QUnit.module("Browserify - client code"); 8 | 9 | var isIE8 = window.attachEvent && !window.addEventListener; 10 | 11 | if (!isIE8) { 12 | 13 | test("No jQuery global: require('jsrender')($)", function() { 14 | // ............................... Hide QUnit global jQuery and any previous global jsrender................................. 15 | var jQuery = global.jQuery, jsr = global.jsrender; 16 | global.jQuery = global.jsrender = undefined; 17 | 18 | // =============================== Arrange =============================== 19 | var data = {name: "Jo"}; 20 | 21 | // ................................ Act .................................. 22 | var $jq = require('jquery'); 23 | var $jsr = require('../../')($jq); // Provide jQuery, so $jsr === $jq is local jQuery namespace 24 | 25 | // Use require to get server template, thanks to Browserify bundle that used jsrender/tmplify transform 26 | var tmpl = require('../templates/name-template.html')($jsr); // Provide jsrender 27 | 28 | var result = tmpl(data); 29 | 30 | result += " " + ($jsr !== jQuery); 31 | 32 | // ............................... Assert ................................. 33 | equal(result, "Name: Jo (name-template.html) true", "result: No jQuery global: require('jsrender')($)"); 34 | 35 | // ............................... Reset ................................. 36 | global.jQuery = jQuery; // Replace QUnit global jQuery 37 | global.jsrender = jsr; // Replace any previous global jsrender 38 | }); 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /test/browserify/3-unit-tests.js: -------------------------------------------------------------------------------- 1 | /*global QUnit, test, equal, ok*/ 2 | (function(undefined) { 3 | "use strict"; 4 | 5 | browserify.done.three = true; 6 | 7 | QUnit.module("Browserify - client code"); 8 | 9 | var isIE8 = window.attachEvent && !window.addEventListener; 10 | 11 | if (!isIE8) { 12 | 13 | test("jQuery global: require('jsrender')", function() { 14 | // ............................... Hide QUnit global jQuery and any previous global jsrender................................. 15 | var jQuery = global.jQuery, jsr = global.jsrender; 16 | global.jQuery = global.jsrender = undefined; 17 | 18 | // =============================== Arrange =============================== 19 | var data = {name: "Jo"}; 20 | 21 | // ................................ Act .................................. 22 | global.jQuery = require('jquery'); 23 | var $jsr = require('../../'); // Uses global jQuery, so $jsr === global.jQuery is global jQuery namespace 24 | 25 | // Use require to get server template, thanks to Browserify bundle that used jsrender/tmplify transform 26 | var tmpl = require('../templates/name-template.html'); // Uses jsrender attached to global jQuery 27 | 28 | var result = tmpl(data); 29 | 30 | result += " " + ($jsr !== jQuery) + " " + ($jsr === global.jQuery); 31 | 32 | // ............................... Assert ................................. 33 | equal(result, "Name: Jo (name-template.html) true true", "result: jQuery global: require('jsrender')"); 34 | 35 | // ............................... Reset ................................. 36 | global.jQuery = jQuery; // Replace QUnit global jQuery 37 | global.jsrender = jsr; // Replace any previous global jsrender 38 | }); 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /test/browserify/bundles/browserify-bundles-go-here.txt: -------------------------------------------------------------------------------- 1 | Run 2 | $ gulp bundle 3 | to generate browserify client bundles used in the unit tests: test/unit-tests-browserify.html 4 | -------------------------------------------------------------------------------- /test/browserify/htm-jsrender-tmpl.js: -------------------------------------------------------------------------------- 1 | /*global QUnit, test, equal, ok*/ 2 | (function(undefined) { 3 | "use strict"; 4 | 5 | browserify.done.htm = true; 6 | 7 | QUnit.module("Browserify - client code"); 8 | 9 | var isIE8 = window.attachEvent && !window.addEventListener; 10 | 11 | if (!isIE8) { 12 | 13 | test("jQuery global: require('jsrender')", function() { 14 | 15 | // ............................... Hide QUnit global jQuery and any previous global jsrender................................. 16 | var jQuery = global.jQuery, jsr = global.jsrender; 17 | global.jQuery = global.jsrender = undefined; 18 | 19 | // =============================== Arrange =============================== 20 | var data = {name: "Jo"}; 21 | 22 | // ................................ Act .................................. 23 | var jsrender = require('./../../')(); 24 | 25 | // Use require to get server template, thanks to Browserify bundle that used jsrender/tmplify transform 26 | var tmpl = require('../templates/name-template.htm')(jsrender); // Provide jsrender 27 | var tmpl2 = require('../templates/name-template.jsrender')(jsrender); // Provide jsrender 28 | 29 | var result = tmpl(data) + " " + tmpl2(data); 30 | 31 | // ............................... Assert ................................. 32 | equal(result, "Name: Jo (name-template.htm) Name: Jo (name-template.jsrender)", "result: jQuery global: require('jsrender') - htm"); 33 | 34 | // ............................... Reset ................................. 35 | global.jQuery = jQuery; // Replace QUnit global jQuery 36 | global.jsrender = jsr; // Replace any previous global jsrender 37 | }); 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /test/browserify/html-jsr-tmpl.js: -------------------------------------------------------------------------------- 1 | /*global QUnit, test, equal, ok*/ 2 | (function(undefined) { 3 | "use strict"; 4 | 5 | browserify.done.html = true; 6 | 7 | QUnit.module("Browserify - client code"); 8 | 9 | var isIE8 = window.attachEvent && !window.addEventListener; 10 | 11 | if (!isIE8) { 12 | 13 | test("jQuery global: require('jsrender')", function() { 14 | 15 | // ............................... Hide QUnit global jQuery and any previous global jsrender................................. 16 | var jQuery = global.jQuery, jsr = global.jsrender; 17 | global.jQuery = global.jsrender = undefined; 18 | 19 | // =============================== Arrange =============================== 20 | var data = {name: "Jo"}; 21 | 22 | // ................................ Act .................................. 23 | var jsrender = require('./../../')(); 24 | 25 | // Use require to get server template, thanks to Browserify bundle that used jsrender/tmplify transform 26 | var tmpl = require('../templates/name-template.html')(jsrender); // Provide jsrender 27 | var tmpl2 = require('../templates/name-template.jsr')(jsrender); // Provide jsrender 28 | 29 | var result = tmpl(data) + " " + tmpl2(data); 30 | 31 | // ............................... Assert ................................. 32 | equal(result, "Name: Jo (name-template.html) Name: Jo (name-template.jsr)", "result: jQuery global: require('jsrender') - html"); 33 | 34 | // ............................... Reset ................................. 35 | global.jQuery = jQuery; // Replace QUnit global jQuery 36 | global.jsrender = jsr; // Replace any previous global jsrender 37 | }); 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /test/browserify/tests-browserify-completed.js: -------------------------------------------------------------------------------- 1 | /*global test, equal, module, ok*/ 2 | (function(global, $, undefined) { 3 | "use strict"; 4 | 5 | module("browserify"); 6 | 7 | var isIE8 = window.attachEvent && !window.addEventListener; 8 | 9 | if (!isIE8) { 10 | 11 | test("browserify tests all run", function() { 12 | equal(JSON.stringify(browserify.done), 13 | '{"one":true,"two":true,"three":true,"twelve":true,"html":true,"htm":true}', 14 | "Browserify tests succeeded"); 15 | }); 16 | } 17 | })(this, this.jQuery); 18 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test pages for JsRender 5 | 6 | 11 | 12 | 13 |
<< Index for JsViews and JsRender
14 | 15 |

Unit tests for JsRender

16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/perf-compare.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Benchmark JsRender 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Perf comparison

18 | 19 | Rendered content:

20 |
21 | 22 |
Times in microseconds: 23 |
24 | 25 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /test/requirejs/require.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.1.22 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(ha){function L(b){return"[object Function]"===R.call(b)}function M(b){return"[object Array]"===R.call(b)}function x(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(L(l)){try{f=h.execCb(c,l,b,f)}catch(d){a=d}this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports: 19 | this.usingExports&&(f=this.exports));if(a){if(this.events.error&&this.map.isDefine||k.onError!==ia)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",B(this.error=a);if("undefined"!==typeof console&&console.error)console.error(a);else k.onError(a)}}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,k.onResourceLoad)){var e=[];x(this.depMaps,function(a){e.push(a.normalizedMap||a)});k.onResourceLoad(h, 20 | this.map,e)}D(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}},callPlugin:function(){var a=this.map,b=a.id,d=q(a.prefix);this.depMaps.push(d);v(d,"defined",y(this,function(f){var l,d,e=g(ga,this.map.id),N=this.map.name,p=this.map.parentMap?this.map.parentMap.name:null,r=h.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(N=f.normalize(N,function(a){return c(a, 21 | p,!0)})||""),d=q(a.prefix+"!"+N,this.map.parentMap),v(d,"defined",y(this,function(a){this.map.normalizedMap=d;this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),f=g(t,d.id)){this.depMaps.push(d);if(this.events.error)f.on("error",y(this,function(a){this.emit("error",a)}));f.enable()}}else e?(this.map.url=h.nameToUrl(e),this.load()):(l=y(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=y(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b]; 22 | E(t,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&D(a.map.id)});B(a)}),l.fromText=y(this,function(f,c){var d=a.name,e=q(d),N=T;c&&(f=c);N&&(T=!1);u(e);w(m.config,b)&&(m.config[d]=m.config[b]);try{k.exec(f)}catch(g){return B(G("fromtexteval","fromText eval for "+b+" failed: "+g,g,[b]))}N&&(T=!0);this.depMaps.push(e);h.completeLoad(d);r([d],l)}),f.load(a.name,r,l,m))}));h.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){aa[this.map.id]=this;this.enabling=this.enabled=!0;x(this.depMaps, 23 | y(this,function(a,b){var c,f;if("string"===typeof a){a=q(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=g(S,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;v(a,"defined",y(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?v(a,"error",y(this,this.errback)):this.events.error&&v(a,"error",y(this,function(a){this.emit("error",a)}))}c=a.id;f=t[c];w(S,c)||!f||f.enabled||h.enable(a,this)}));E(this.pluginMaps,y(this,function(a){var b= 24 | g(t,a.id);b&&!b.enabled&&h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};h={config:m,contextName:b,registry:t,defined:r,urlFetched:X,defQueue:H,defQueueMap:{},Module:ea,makeModuleMap:q,nextTick:k.nextTick,onError:B,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.shim,c={paths:!0, 25 | bundles:!0,config:!0,map:!0};E(a,function(a,b){c[b]?(m[b]||(m[b]={}),Z(m[b],a,!0,!0)):m[b]=a});a.bundles&&E(a.bundles,function(a,b){x(a,function(a){a!==b&&(ga[a]=b)})});a.shim&&(E(a.shim,function(a,c){M(a)&&(a={deps:a});!a.exports&&!a.init||a.exportsFn||(a.exportsFn=h.makeShimExports(a));b[c]=a}),m.shim=b);a.packages&&x(a.packages,function(a){var b;a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(m.paths[b]=a.location);m.pkgs[b]=a.name+"/"+(a.main||"main").replace(na,"").replace(V,"")});E(t, 26 | function(a,b){a.inited||a.map.unnormalized||(a.map=q(b,null,!0))});(a.deps||a.callback)&&h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ha,arguments));return b||a.exports&&ja(a.exports)}},makeRequire:function(a,n){function e(c,d,g){var m,p;n.enableBuildCallback&&d&&L(d)&&(d.__requireJsBuild=!0);if("string"===typeof c){if(L(d))return B(G("requireargs","Invalid require call"),g);if(a&&w(S,c))return S[c](t[a.id]);if(k.get)return k.get(h, 27 | c,a,e);m=q(c,a,!1,!0);m=m.id;return w(r,m)?r[m]:B(G("notloaded",'Module name "'+m+'" has not been loaded yet for context: '+b+(a?"":". Use require([])")))}Q();h.nextTick(function(){Q();p=u(q(null,a));p.skipMap=n.skipMap;p.init(c,d,g,{enabled:!0});I()});return e}n=n||{};Z(e,{isBrowser:F,toUrl:function(b){var d,e=b.lastIndexOf("."),n=b.split("/")[0];-1!==e&&("."!==n&&".."!==n||1e.attachEvent.toString().indexOf("[native code")||da?(e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)):(T=!0,e.attachEvent("onreadystatechange",b.onScriptLoad));e.src=d;Q=e;I?D.insertBefore(e,I):D.appendChild(e);Q=null;return e}if(ka)try{importScripts(d),b.completeLoad(c)}catch(q){b.onError(G("importscripts","importScripts failed for "+ 36 | c+" at "+d,q,[c]))}};F&&!v.skipDataMain&&Y(document.getElementsByTagName("script"),function(b){D||(D=b.parentNode);if(P=b.getAttribute("data-main"))return u=P,v.baseUrl||(J=u.split("/"),u=J.pop(),U=J.length?J.join("/")+"/":"./",v.baseUrl=U),u=u.replace(V,""),k.jsExtRegExp.test(u)&&(u=P),v.deps=v.deps?v.deps.concat(u):[u],!0});define=function(b,c,d){var g,e;"string"!==typeof b&&(d=c,c=b,b=null);M(c)||(d=c,c=null);!c&&L(d)&&(c=[],d.length&&(d.toString().replace(qa,"").replace(ra,function(b,d){c.push(d)}), 37 | c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));T&&(g=Q||pa())&&(b||(b=g.getAttribute("data-requiremodule")),e=K[g.getAttribute("data-requirecontext")]);e?(e.defQueue.push([b,c,d]),e.defQueueMap[b]=!0):W.push([b,c,d])};define.amd={jQuery:!0};k.exec=function(b){return eval(b)};k(v)}})(this); -------------------------------------------------------------------------------- /test/resources/dot.js: -------------------------------------------------------------------------------- 1 | // doT.js 2 | // 2011-2014, Laura Doktorova, https://github.com/olado/doT 3 | // Licensed under the MIT license. 4 | 5 | (function() { 6 | "use strict"; 7 | 8 | var doT = { 9 | version: "1.0.3", 10 | templateSettings: { 11 | evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g, 12 | interpolate: /\{\{=([\s\S]+?)\}\}/g, 13 | encode: /\{\{!([\s\S]+?)\}\}/g, 14 | use: /\{\{#([\s\S]+?)\}\}/g, 15 | useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g, 16 | define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g, 17 | defineParams:/^\s*([\w$]+):([\s\S]+)/, 18 | conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g, 19 | iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g, 20 | varname: "it", 21 | strip: true, 22 | append: true, 23 | selfcontained: false, 24 | doNotSkipEncoded: false 25 | }, 26 | template: undefined, //fn, compile template 27 | compile: undefined //fn, for express 28 | }, _globals; 29 | 30 | doT.encodeHTMLSource = function(doNotSkipEncoded) { 31 | var encodeHTMLRules = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/" }, 32 | matchHTML = doNotSkipEncoded ? /[&<>"'\/]/g : /&(?!#?\w+;)|<|>|"|'|\//g; 33 | return function(code) { 34 | return code ? code.toString().replace(matchHTML, function(m) {return encodeHTMLRules[m] || m;}) : ""; 35 | }; 36 | }; 37 | _globals = (function(){ return this || (0,eval)("this"); }()); 38 | if (typeof module !== "undefined" && module.exports) { 39 | module.exports = doT; 40 | } else if (typeof define === "function" && define.amd) { 41 | define(function(){return doT;}); 42 | } else { 43 | _globals.doT = doT; 44 | } 45 | var startend = { 46 | append: { start: "'+(", end: ")+'", startencode: "'+encodeHTML(" }, 47 | split: { start: "';out+=(", end: ");out+='", startencode: "';out+=encodeHTML(" } 48 | }, skip = /$^/; 49 | function resolveDefs(c, block, def) { 50 | return ((typeof block === "string") ? block : block.toString()) 51 | .replace(c.define || skip, function(m, code, assign, value) { 52 | if (code.indexOf("def.") === 0) { 53 | code = code.substring(4); 54 | } 55 | if (!(code in def)) { 56 | if (assign === ":") { 57 | if (c.defineParams) value.replace(c.defineParams, function(m, param, v) { 58 | def[code] = {arg: param, text: v}; 59 | }); 60 | if (!(code in def)) def[code]= value; 61 | } else { 62 | new Function("def", "def['"+code+"']=" + value)(def); 63 | } 64 | } 65 | return ""; 66 | }) 67 | .replace(c.use || skip, function(m, code) { 68 | if (c.useParams) code = code.replace(c.useParams, function(m, s, d, param) { 69 | if (def[d] && def[d].arg && param) { 70 | var rw = (d+":"+param).replace(/'|\\/g, "_"); 71 | def.__exp = def.__exp || {}; 72 | def.__exp[rw] = def[d].text.replace(new RegExp("(^|[^\\w$])" + def[d].arg + "([^\\w$])", "g"), "$1" + param + "$2"); 73 | return s + "def.__exp['"+rw+"']"; 74 | } 75 | }); 76 | var v = new Function("def", "return " + code)(def); 77 | return v ? resolveDefs(c, v, def) : v; 78 | }); 79 | } 80 | 81 | function unescape(code) { 82 | return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " "); 83 | } 84 | 85 | doT.template = function(tmpl, c, def) { 86 | c = c || doT.templateSettings; 87 | var cse = c.append ? startend.append : startend.split, needhtmlencode, sid = 0, indv, 88 | str = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl; 89 | 90 | str = ("var out='" + (c.strip ? str.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g," ") 91 | .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,""): str) 92 | .replace(/'|\\/g, "\\$&") 93 | .replace(c.interpolate || skip, function(m, code) { 94 | return cse.start + unescape(code) + cse.end; 95 | }) 96 | .replace(c.encode || skip, function(m, code) { 97 | needhtmlencode = true; 98 | return cse.startencode + unescape(code) + cse.end; 99 | }) 100 | .replace(c.conditional || skip, function(m, elsecase, code) { 101 | return elsecase ? 102 | (code ? "';}else if(" + unescape(code) + "){out+='" : "';}else{out+='") : 103 | (code ? "';if(" + unescape(code) + "){out+='" : "';}out+='"); 104 | }) 105 | .replace(c.iterate || skip, function(m, iterate, vname, iname) { 106 | if (!iterate) return "';} } out+='"; 107 | sid+=1; indv=iname || "i"+sid; iterate=unescape(iterate); 108 | return "';var arr"+sid+"="+iterate+";if(arr"+sid+"){var "+vname+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+")[^>]*$|\{\{\! /, 12 | newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = []; 13 | 14 | function newTmplItem( options, parentItem, fn, data ) { 15 | // Returns a template item data structure for a new rendered instance of a template (a 'template item'). 16 | // The content field is a hierarchical array of strings and nested items (to be 17 | // removed and replaced by nodes field of dom elements, once inserted in DOM). 18 | var newItem = { 19 | data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}), 20 | _wrap: parentItem ? parentItem._wrap : null, 21 | tmpl: null, 22 | parent: parentItem || null, 23 | nodes: [], 24 | calls: tiCalls, 25 | nest: tiNest, 26 | wrap: tiWrap, 27 | html: tiHtml, 28 | update: tiUpdate 29 | }; 30 | if ( options ) { 31 | jQuery.extend( newItem, options, { nodes: [], parent: parentItem }); 32 | } 33 | if ( fn ) { 34 | // Build the hierarchical content to be used during insertion into DOM 35 | newItem.tmpl = fn; 36 | newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem ); 37 | newItem.key = ++itemKey; 38 | // Keep track of new template item, until it is stored as jQuery Data on DOM element 39 | (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem; 40 | } 41 | return newItem; 42 | } 43 | 44 | // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core). 45 | jQuery.each({ 46 | appendTo: "append", 47 | prependTo: "prepend", 48 | insertBefore: "before", 49 | insertAfter: "after", 50 | replaceAll: "replaceWith" 51 | }, function( name, original ) { 52 | jQuery.fn[ name ] = function( selector ) { 53 | var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems, 54 | parent = this.length === 1 && this[0].parentNode; 55 | 56 | appendToTmplItems = newTmplItems || {}; 57 | if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { 58 | insert[ original ]( this[0] ); 59 | ret = this; 60 | } else { 61 | for ( i = 0, l = insert.length; i < l; i++ ) { 62 | cloneIndex = i; 63 | elems = (i > 0 ? this.clone(true) : this).get(); 64 | jQuery( insert[i] )[ original ]( elems ); 65 | ret = ret.concat( elems ); 66 | } 67 | cloneIndex = 0; 68 | ret = this.pushStack( ret, name, insert.selector ); 69 | } 70 | tmplItems = appendToTmplItems; 71 | appendToTmplItems = null; 72 | jQuery.tmpl.complete( tmplItems ); 73 | return ret; 74 | }; 75 | }); 76 | 77 | jQuery.fn.extend({ 78 | // Use first wrapped element as template markup. 79 | // Return wrapped set of template items, obtained by rendering template against data. 80 | tmpl: function( data, options, parentItem ) { 81 | return jQuery.tmpl( this[0], data, options, parentItem ); 82 | }, 83 | 84 | // Find which rendered template item the first wrapped DOM element belongs to 85 | tmplItem: function() { 86 | return jQuery.tmplItem( this[0] ); 87 | }, 88 | 89 | // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template. 90 | template: function( name ) { 91 | return jQuery.template( name, this[0] ); 92 | }, 93 | 94 | domManip: function( args, table, callback, options ) { 95 | if ( args[0] && jQuery.isArray( args[0] )) { 96 | var dmArgs = jQuery.makeArray( arguments ), elems = args[0], elemsLength = elems.length, i = 0, tmplItem; 97 | while ( i < elemsLength && !(tmplItem = jQuery.data( elems[i++], "tmplItem" ))) {} 98 | if ( tmplItem && cloneIndex ) { 99 | dmArgs[2] = function( fragClone ) { 100 | // Handler called by oldManip when rendered template has been inserted into DOM. 101 | jQuery.tmpl.afterManip( this, fragClone, callback ); 102 | }; 103 | } 104 | oldManip.apply( this, dmArgs ); 105 | } else { 106 | oldManip.apply( this, arguments ); 107 | } 108 | cloneIndex = 0; 109 | if ( !appendToTmplItems ) { 110 | jQuery.tmpl.complete( newTmplItems ); 111 | } 112 | return this; 113 | } 114 | }); 115 | 116 | jQuery.extend({ 117 | // Return wrapped set of template items, obtained by rendering template against data. 118 | tmpl: function( tmpl, data, options, parentItem ) { 119 | var ret, topLevel = !parentItem; 120 | if ( topLevel ) { 121 | // This is a top-level tmpl call (not from a nested template using {{tmpl}}) 122 | parentItem = topTmplItem; 123 | tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl ); 124 | wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level 125 | } else if ( !tmpl ) { 126 | // The template item is already associated with DOM - this is a refresh. 127 | // Re-evaluate rendered template for the parentItem 128 | tmpl = parentItem.tmpl; 129 | newTmplItems[parentItem.key] = parentItem; 130 | parentItem.nodes = []; 131 | if ( parentItem.wrapped ) { 132 | updateWrapped( parentItem, parentItem.wrapped ); 133 | } 134 | // Rebuild, without creating a new template item 135 | return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) )); 136 | } 137 | if ( !tmpl ) { 138 | return []; // Could throw... 139 | } 140 | if ( typeof data === "function" ) { 141 | data = data.call( parentItem || {} ); 142 | } 143 | if ( options && options.wrapped ) { 144 | updateWrapped( options, options.wrapped ); 145 | } 146 | ret = jQuery.isArray( data ) ? 147 | jQuery.map( data, function( dataItem ) { 148 | return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null; 149 | }) : 150 | [ newTmplItem( options, parentItem, tmpl, data ) ]; 151 | return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret; 152 | }, 153 | 154 | // Return rendered template item for an element. 155 | tmplItem: function( elem ) { 156 | var tmplItem; 157 | if ( elem instanceof jQuery ) { 158 | elem = elem[0]; 159 | } 160 | while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {} 161 | return tmplItem || topTmplItem; 162 | }, 163 | 164 | // Set: 165 | // Use $.template( name, tmpl ) to cache a named template, 166 | // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc. 167 | // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration. 168 | 169 | // Get: 170 | // Use $.template( name ) to access a cached template. 171 | // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString ) 172 | // will return the compiled template, without adding a name reference. 173 | // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent 174 | // to $.template( null, templateString ) 175 | template: function( name, tmpl ) { 176 | if (tmpl) { 177 | // Compile template and associate with name 178 | if ( typeof tmpl === "string" ) { 179 | // This is an HTML string being passed directly in. 180 | tmpl = buildTmplFn( tmpl ); 181 | } else if ( tmpl instanceof jQuery ) { 182 | tmpl = tmpl[0] || {}; 183 | } 184 | if ( tmpl.nodeType ) { 185 | // If this is a template block, use cached copy, or generate tmpl function and cache. 186 | tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML )); 187 | // 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. 188 | // This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x. 189 | // To correct this, include space in tag: foo="${ x }" -> foo="value of x" 190 | } 191 | return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl; 192 | } 193 | // Return named compiled template 194 | return name ? (typeof name !== "string" ? jQuery.template( null, name ): 195 | (jQuery.template[name] || 196 | // If not in map, and not containing at least on HTML tag, treat as a selector. 197 | // (If integrated with core, use quickExpr.exec) 198 | jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null; 199 | }, 200 | 201 | encode: function( text ) { 202 | // Do HTML encoding replacing < > & and ' and " by corresponding entities. 203 | return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'"); 204 | } 205 | }); 206 | 207 | jQuery.extend( jQuery.tmpl, { 208 | tag: { 209 | "tmpl": { 210 | _default: { $2: "null" }, 211 | open: "if($notnull_1){__=__.concat($item.nest($1,$2));}" 212 | // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions) 213 | // This means that {{tmpl foo}} treats foo as a template (which IS a function). 214 | // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}. 215 | }, 216 | "wrap": { 217 | _default: { $2: "null" }, 218 | open: "$item.calls(__,$1,$2);__=[];", 219 | close: "call=$item.calls();__=call._.concat($item.wrap(call,__));" 220 | }, 221 | "each": { 222 | _default: { $2: "$index, $value" }, 223 | open: "if($notnull_1){$.each($1a,function($2){with(this){", 224 | close: "}});}" 225 | }, 226 | "if": { 227 | open: "if(($notnull_1) && $1a){", 228 | close: "}" 229 | }, 230 | "else": { 231 | _default: { $1: "true" }, 232 | open: "}else if(($notnull_1) && $1a){" 233 | }, 234 | "html": { 235 | // Unecoded expression evaluation. 236 | open: "if($notnull_1){__.push($1a);}" 237 | }, 238 | "=": { 239 | // Encoded expression evaluation. Abbreviated form is ${}. 240 | _default: { $1: "$data" }, 241 | open: "if($notnull_1){__.push($.encode($1a));}" 242 | }, 243 | "!": { 244 | // Comment tag. Skipped by parser 245 | open: "" 246 | } 247 | }, 248 | 249 | // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events 250 | complete: function( items ) { 251 | newTmplItems = {}; 252 | }, 253 | 254 | // Call this from code which overrides domManip, or equivalent 255 | // Manage cloning/storing template items etc. 256 | afterManip: function afterManip( elem, fragClone, callback ) { 257 | // Provides cloned fragment ready for fixup prior to and after insertion into DOM 258 | var content = fragClone.nodeType === 11 ? 259 | jQuery.makeArray(fragClone.childNodes) : 260 | fragClone.nodeType === 1 ? [fragClone] : []; 261 | 262 | // Return fragment to original caller (e.g. append) for DOM insertion 263 | callback.call( elem, fragClone ); 264 | 265 | // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data. 266 | storeTmplItems( content ); 267 | cloneIndex++; 268 | } 269 | }); 270 | 271 | //========================== Private helper functions, used by code above ========================== 272 | 273 | function build( tmplItem, nested, content ) { 274 | // Convert hierarchical content into flat string array 275 | // and finally return array of fragments ready for DOM insertion 276 | var frag, ret = content ? jQuery.map( content, function( item ) { 277 | return (typeof item === "string") ? 278 | // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM. 279 | (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) : 280 | // This is a child template item. Build nested template. 281 | build( item, tmplItem, item._ctnt ); 282 | }) : 283 | // 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()}}. 284 | tmplItem; 285 | if ( nested ) { 286 | return ret; 287 | } 288 | 289 | // top-level template 290 | ret = ret.join(""); 291 | 292 | // Support templates which have initial or final text nodes, or consist only of text 293 | // Also support HTML entities within the HTML markup. 294 | ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) { 295 | frag = jQuery( middle ).get(); 296 | 297 | storeTmplItems( frag ); 298 | if ( before ) { 299 | frag = unencode( before ).concat(frag); 300 | } 301 | if ( after ) { 302 | frag = frag.concat(unencode( after )); 303 | } 304 | }); 305 | return frag ? frag : unencode( ret ); 306 | } 307 | 308 | function unencode( text ) { 309 | // Use createElement, since createTextNode will not render HTML entities correctly 310 | var el = document.createElement( "div" ); 311 | el.innerHTML = text; 312 | return jQuery.makeArray(el.childNodes); 313 | } 314 | 315 | // Generate a reusable function that will serve to render a template against data 316 | function buildTmplFn( markup ) { 317 | return new Function("jQuery","$item", 318 | // Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10). 319 | "var $=jQuery,call,__=[],$data=$item.data;" + 320 | 321 | // Introduce the data as local variables using with(){} 322 | "with($data){__.push('" + 323 | 324 | // Convert the template into pure JavaScript 325 | jQuery.trim(markup) 326 | .replace( /([\\'])/g, "\\$1" ) 327 | .replace( /[\r\t\n]/g, " " ) 328 | .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" ) 329 | .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, 330 | function( all, slash, type, fnargs, target, parens, args ) { 331 | var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect; 332 | if ( !tag ) { 333 | throw "Unknown template tag: " + type; 334 | } 335 | def = tag._default || []; 336 | if ( parens && !/\w$/.test(target)) { 337 | target += parens; 338 | parens = ""; 339 | } 340 | if ( target ) { 341 | target = unescape( target ); 342 | args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : ""); 343 | // Support for target being things like a.toLowerCase(); 344 | // In that case don't call with template item as 'this' pointer. Just evaluate... 345 | expr = parens ? (target.indexOf(".") > -1 ? target + unescape( parens ) : ("(" + target + ").call($item" + args)) : target; 346 | exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))"; 347 | } else { 348 | exprAutoFnDetect = expr = def.$1 || "null"; 349 | } 350 | fnargs = unescape( fnargs ); 351 | return "');" + 352 | tag[ slash ? "close" : "open" ] 353 | .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" ) 354 | .split( "$1a" ).join( exprAutoFnDetect ) 355 | .split( "$1" ).join( expr ) 356 | .split( "$2" ).join( fnargs || def.$2 || "" ) + 357 | "__.push('"; 358 | }) + 359 | "');}return __;" 360 | ); 361 | } 362 | function updateWrapped( options, wrapped ) { 363 | // Build the wrapped content. 364 | options._wrap = build( options, true, 365 | // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string. 366 | jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()] 367 | ).join(""); 368 | } 369 | 370 | function unescape( args ) { 371 | return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null; 372 | } 373 | function outerHtml( elem ) { 374 | var div = document.createElement("div"); 375 | div.appendChild( elem.cloneNode(true) ); 376 | return div.innerHTML; 377 | } 378 | 379 | // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance. 380 | function storeTmplItems( content ) { 381 | var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m; 382 | for ( i = 0, l = content.length; i < l; i++ ) { 383 | if ( (elem = content[i]).nodeType !== 1 ) { 384 | continue; 385 | } 386 | elems = elem.getElementsByTagName("*"); 387 | for ( m = elems.length - 1; m >= 0; m-- ) { 388 | processItemKey( elems[m] ); 389 | } 390 | processItemKey( elem ); 391 | } 392 | function processItemKey( el ) { 393 | var pntKey, pntNode = el, pntItem, tmplItem, key; 394 | // Ensure that each rendered template inserted into the DOM has its own template item, 395 | if ( (key = el.getAttribute( tmplItmAtt ))) { 396 | while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { } 397 | if ( pntKey !== key ) { 398 | // The next ancestor with a _tmplitem expando is on a different key than this one. 399 | // So this is a top-level element within this template item 400 | // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment. 401 | pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0; 402 | if ( !(tmplItem = newTmplItems[key]) ) { 403 | // The item is for wrapped content, and was copied from the temporary parent wrappedItem. 404 | tmplItem = wrappedItems[key]; 405 | tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode] ); 406 | tmplItem.key = ++itemKey; 407 | newTmplItems[itemKey] = tmplItem; 408 | } 409 | if ( cloneIndex ) { 410 | cloneTmplItem( key ); 411 | } 412 | } 413 | el.removeAttribute( tmplItmAtt ); 414 | } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) { 415 | // This was a rendered element, cloned during append or appendTo etc. 416 | // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem. 417 | cloneTmplItem( tmplItem.key ); 418 | newTmplItems[tmplItem.key] = tmplItem; 419 | pntNode = jQuery.data( el.parentNode, "tmplItem" ); 420 | pntNode = pntNode ? pntNode.key : 0; 421 | } 422 | if ( tmplItem ) { 423 | pntItem = tmplItem; 424 | // Find the template item of the parent element. 425 | // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string) 426 | while ( pntItem && pntItem.key != pntNode ) { 427 | // Add this element as a top-level node for this rendered template item, as well as for any 428 | // ancestor items between this item and the item of its parent element 429 | pntItem.nodes.push( el ); 430 | pntItem = pntItem.parent; 431 | } 432 | // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering... 433 | delete tmplItem._ctnt; 434 | delete tmplItem._wrap; 435 | // Store template item as jQuery data on the element 436 | jQuery.data( el, "tmplItem", tmplItem ); 437 | } 438 | function cloneTmplItem( key ) { 439 | key = key + keySuffix; 440 | tmplItem = newClonedItems[key] = 441 | (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent )); 442 | } 443 | } 444 | } 445 | 446 | //---- Helper functions for template item ---- 447 | 448 | function tiCalls( content, tmpl, data, options ) { 449 | if ( !content ) { 450 | return stack.pop(); 451 | } 452 | stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options }); 453 | } 454 | 455 | function tiNest( tmpl, data, options ) { 456 | // nested template, using {{tmpl}} tag 457 | return jQuery.tmpl( jQuery.template( tmpl ), data, options, this ); 458 | } 459 | 460 | function tiWrap( call, wrapped ) { 461 | // nested template, using {{wrap}} tag 462 | var options = call.options || {}; 463 | options.wrapped = wrapped; 464 | // Apply the template, which may incorporate wrapped content, 465 | return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item ); 466 | } 467 | 468 | function tiHtml( filter, textOnly ) { 469 | var wrapped = this._wrap; 470 | return jQuery.map( 471 | jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ), 472 | function(e) { 473 | return textOnly ? 474 | e.innerText || e.textContent : 475 | e.outerHTML || outerHtml(e); 476 | }); 477 | } 478 | 479 | function tiUpdate() { 480 | var coll = this.nodes; 481 | jQuery.tmpl( null, null, null, this).insertBefore( coll[0] ); 482 | jQuery( coll ).remove(); 483 | } 484 | })( jQuery ); -------------------------------------------------------------------------------- /test/resources/perf-compare.css: -------------------------------------------------------------------------------- 1 | body { padding: 10px; font-family: Verdana; font-size: small } 2 | .result { text-align:right; } 3 | -------------------------------------------------------------------------------- /test/templates/file/path.html: -------------------------------------------------------------------------------- 1 | ServerRenderedTemplate_{{:name}}_B -------------------------------------------------------------------------------- /test/templates/inner.html: -------------------------------------------------------------------------------- 1 | Name: {{:name}} (inner.html) -------------------------------------------------------------------------------- /test/templates/name-template.htm: -------------------------------------------------------------------------------- 1 | Name: {{:name}} (name-template.htm) -------------------------------------------------------------------------------- /test/templates/name-template.html: -------------------------------------------------------------------------------- 1 | Name: {{:name}} (name-template.html) -------------------------------------------------------------------------------- /test/templates/name-template.jsr: -------------------------------------------------------------------------------- 1 | Name: {{:name}} (name-template.jsr) -------------------------------------------------------------------------------- /test/templates/name-template.jsrender: -------------------------------------------------------------------------------- 1 | Name: {{:name}} (name-template.jsrender) -------------------------------------------------------------------------------- /test/templates/outer.html: -------------------------------------------------------------------------------- 1 | Name: {{:name}} (outer.html) {{include tmpl="./test/templates/inner.html"/}} -------------------------------------------------------------------------------- /test/test-amd-scriptloader-no-jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/test.min.map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Got to F12 debug and see if you have an error: 12 |

DevTools failed to load SourceMap: Could not parse content for ... 13 |

14 |
15 | 16 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/unit-tests-amd-scriptloader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |

JsRender AMD QUnit Tests

16 |

17 |
18 |

19 |
    20 | 21 | 22 | -------------------------------------------------------------------------------- /test/unit-tests-browserify.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

    Note: Run $ gulp bundle and $ npm test before running these tests

    10 |
    11 |

    JsViews QUnit Tests

    12 |

    13 |
    14 |

    15 |
      16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/unit-tests-jsrender-no-jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
      18 |

      JsRender QUnit Tests

      19 |

      20 |
      21 |

      22 |
        23 | 24 | 25 | -------------------------------------------------------------------------------- /test/unit-tests-jsrender-with-jquery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
        20 |

        JsRender QUnit Tests

        21 |

        22 |
        23 |

        24 |
          25 | 26 | 27 | -------------------------------------------------------------------------------- /test/unit-tests-node-runner.js: -------------------------------------------------------------------------------- 1 | var testrunner = require("node-qunit"); 2 | 3 | testrunner.run({ 4 | code: {path: "jsrender-node.js", namespace: "jsrender"}, // sets global require('jsrender') 5 | tests: "test/unit-tests/tests-jsrender-no-jquery.js" 6 | }); 7 | 8 | testrunner.run({ 9 | code: {path: "jsrender-node.js", namespace: "jsrender"}, // sets global require('jsrender') 10 | tests: "test/unit-tests/tests-node.js" 11 | }); 12 | -------------------------------------------------------------------------------- /test/unit-tests/requirejs-config.js: -------------------------------------------------------------------------------- 1 | // Configure loading modules from the download directory, 2 | requirejs.config({ 3 | "baseUrl": "//www.jsviews.com/download", // Or point to correct local path on your system: "baseUrl": "/download", 4 | // "baseUrl": "../../download", // Or point to correct local path on your system: "baseUrl": "/download", 5 | "paths": { 6 | "jquery": "//code.jquery.com/jquery-3.7.1", 7 | "jsrender": "./jsrender", 8 | "jquery.observable": "./jquery.observable", 9 | "jquery.views": "./jquery.views", 10 | "jsviews": "./jsviews" 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /test/unit-tests/tests-jsrender-amd-scriptloader.js: -------------------------------------------------------------------------------- 1 | /*global test, equal, module, ok*/ 2 | (function(global, jQuery, undefined) { 3 | "use strict"; 4 | 5 | function undefine() { // Undefine registered modules from previously run tests. 6 | require.undef("jsrender"); 7 | require.undef("jquery"); 8 | delete window.jQuery; 9 | } 10 | 11 | if (!window.attachEvent || window.addEventListener) { // Running RequireJS in qunit async test seems to fail in IE8 12 | 13 | QUnit.module("AMD Script Loader"); 14 | 15 | QUnit.test("Loading JsRender, without jQuery, using RequireJS", function(assert) { 16 | var done; 17 | if (assert.async) { done = assert.async() } else { stop() } 18 | var jq = window.jQuery; 19 | undefine(); 20 | 21 | require(["//www.jsviews.com/download/jsrender.js"], function($) { // Or point to correct local path for jsrender.js on your system 22 | // require(["../../download/jsrender.js"], function($) { // Or point to correct local path for jsrender.js on your system 23 | // Here $ is the global jQuery object, (jq - loaded by script block in page header, for QUnit) 24 | // If there was no global jQuery it would be the jsviews object - but no global would be created. 25 | 26 | var result = $.templates("Name: {{:name}}").render({name: "Jo"}) + " " + (!!$.jsrender); 27 | assert.equal(result, "Name: Jo true", "JsRender Loaded"); 28 | if (assert.async) { done() } else { start() } 29 | }); 30 | }); 31 | 32 | QUnit.test("Loading JsRender and jQuery, without forcing load order, using RequireJS", function(assert) { 33 | var done; 34 | if (assert.async) { done = assert.async() } else { stop() } 35 | var jq = window.jQuery; 36 | undefine(); 37 | 38 | // Note JsRender does not require jQuery - so its AMD definition does not specify jQuery dependency. 39 | 40 | require(["./unit-tests/requirejs-config"], function() { 41 | require(["jquery", "jsrender"], function($jq, $) { 42 | // Note: $ is either the jQuery loaded by RequireJS, or the window.jsrender object, depending on load order 43 | // Either way, it is the jQuery instance that has a $.views, $.templates etc. 44 | 45 | var result = $.templates("Name: {{:name}}").render({name: "Jo"}) + " " + ($ === $jq || !!$.jsrender); 46 | assert.equal(result, "Name: Jo true", "JsRender Loaded"); 47 | if (assert.async) { done() } else { start() } 48 | }); 49 | }); 50 | }); 51 | 52 | QUnit.test("Loading JsRender with jQuery, and force jQuery to load before JsRender, using RequireJS", function(assert) { 53 | var done; 54 | if (assert.async) { done = assert.async() } else { stop() } 55 | var jq = window.jQuery; 56 | undefine(); 57 | 58 | // Note JsRender does not require jQuery - so its AMD definition does not specify jQuery dependency. 59 | // So we will force loading order here by nesting require call for JsRender inside require call for jQuery. 60 | // This is not optimized for loading speed. 61 | 62 | require(["./unit-tests/requirejs-config"], function() { 63 | require(["jquery"], function($jq) { 64 | require(["jsrender"], function($) { 65 | // Note: $ is a new instance of jQuery (=== $jq) loaded by RequireJS, not the instance loaded by script block in page header, for QUnit. 66 | 67 | var result = $.templates("Name: {{:name}}").render({name: "Jo"}) + " " + (jq !== $ && $ === window.jQuery && $ === $jq); 68 | assert.equal(result, "Name: Jo true", "JsRender LoadedX"); 69 | if (assert.async) { done() } else { start() } 70 | }); 71 | }); 72 | }); 73 | }); 74 | 75 | } 76 | })(this, this.jQuery); 77 | -------------------------------------------------------------------------------- /test/unit-tests/tests-jsrender-with-jquery-withIE.js: -------------------------------------------------------------------------------- 1 | /*global test, equal, module, ok*/ 2 | (function(global, $, undefined) { 3 | "use strict"; 4 | 5 | var isIE8 = window.attachEvent && !window.addEventListener; 6 | 7 | function sort(array) { 8 | var ret = ""; 9 | if (this.tagCtx.props.reverse) { 10 | // Render in reverse order 11 | for (var i = array.length; i; i--) { 12 | ret += this.tagCtx.render(array[ i - 1 ]); 13 | } 14 | } else { 15 | // Render in original order 16 | ret += this.tmpl.render(array); 17 | } 18 | return ret; 19 | } 20 | 21 | var person = {name: "Jo"}, 22 | people = [{name: "Jo"},{name: "Bill"}], 23 | towns = [{name: "Seattle"},{name: "Paris"},{name: "Delhi"}]; 24 | 25 | var tmplString = "A_{{:name}}_B"; 26 | 27 | QUnit.module("api"); 28 | 29 | QUnit.test("templates", function(assert) { 30 | var tmplElem = document.getElementById("./test/templates/file/path.html"); 31 | 32 | // =============================== Arrange =============================== 33 | $.removeData(tmplElem, "jsvTmpl"); // In case data has been set in a previous test 34 | 35 | // ................................ Act .................................. 36 | var tmpl0 = $.templates({markup: "./test/templates/file/path.html"}); // Compile template but do not cache 37 | 38 | // ............................... Assert ................................. 39 | assert.equal(!$.data(tmplElem).jsvTmpl && tmpl0.render({name: "Jo0"}), isIE8 ? "\nServerRenderedTemplate_Jo0_B" : "ServerRenderedTemplate_Jo0_B", "Compile server-generated template, without caching"); 40 | 41 | // ................................ Act .................................. 42 | var tmpl1 = $.templates("./test/templates/file/path.html"); // Compile and cache, using $.data(elem, "jsvTmpl", tmpl); 43 | 44 | // ............................... Assert ................................. 45 | assert.equal(tmpl1 !== tmpl0 && $.data(tmplElem).jsvTmpl === tmpl1 && tmpl1.render({name: "Jo1"}), isIE8 ? "\nServerRenderedTemplate_Jo1_B" : "ServerRenderedTemplate_Jo1_B", "Compile server-generated template, and cache on file path"); 46 | 47 | // ................................ Act .................................. 48 | var tmpl2 = $.templates("./test/templates/file/path.html"); // Use cached template, accessed by path as key 49 | 50 | // ............................... Assert ................................. 51 | assert.equal(tmpl2 === tmpl1 && tmpl1.render({name: "Jo2"}), isIE8 ? "\nServerRenderedTemplate_Jo2_B" : "ServerRenderedTemplate_Jo2_B", "Re-use cached server-generated template"); 52 | 53 | // ................................ Act .................................. 54 | var tmpl3 = $.templates({markup: "./test/templates/file/path.html"}); // Re-compile template but do not cache. Leaved cached template. 55 | 56 | // ............................... Assert ................................. 57 | assert.equal(tmpl3 !== tmpl0 && tmpl3 !== tmpl1 && $.data(tmplElem).jsvTmpl === tmpl1 && tmpl3.render({name: "Jo3"}), isIE8 ? "\nServerRenderedTemplate_Jo3_B" : "ServerRenderedTemplate_Jo3_B", "Recompile server-generated template, without caching"); 58 | 59 | // ................................ Reset ................................ 60 | delete $.data(tmplElem).jsvTmpl; 61 | document.getElementById("./test/templates/file/path.html").removeAttribute("data-jsv-tmpl"); 62 | 63 | tmplElem = $("#myTmpl")[0]; 64 | delete $.data(tmplElem).jsvTmpl; 65 | tmplElem.removeAttribute("data-jsv-tmpl"); 66 | 67 | // ................................ Act .................................. 68 | tmpl0 = $.templates({markup: "#myTmpl"}); // Compile template declared in script block, but do not cache 69 | 70 | // ............................... Assert ................................. 71 | assert.equal(!$.data(tmplElem).jsvTmpl && tmpl0.render({name: "Jo0"}), isIE8 ? "\nA_Jo0_B" : "A_Jo0_B", "Compile template declared in script block, without caching"); 72 | 73 | // ................................ Act .................................. 74 | tmpl1 = $.templates("#myTmpl"); // Compile and cache, using $.data(elem, "jsvTmpl", tmpl); 75 | 76 | // ............................... Assert ................................. 77 | assert.equal(tmpl1 !== tmpl0 && $.data(tmplElem).jsvTmpl === tmpl1 && tmpl1.render({name: "Jo1"}), isIE8 ? "\nA_Jo1_B" : "A_Jo1_B", "Compile template declared in script block, and cache on file path"); 78 | 79 | // ................................ Act .................................. 80 | tmpl2 = $.templates("#myTmpl"); // Use cached template, accessed by $.data(elem, "jsvTmpl") 81 | 82 | // ............................... Assert ................................. 83 | assert.equal(tmpl2 === tmpl1 && tmpl1.render({name: "Jo2"}), isIE8 ? "\nA_Jo2_B" : "A_Jo2_B", "Re-use cached template declared in script block"); 84 | 85 | // ................................ Act .................................. 86 | tmpl3 = $.templates({markup: "#myTmpl"}); // Re-compile template but do not cache. Leave cached template. 87 | 88 | // ............................... Assert ................................. 89 | assert.equal(tmpl3 !== tmpl0 && tmpl3 !== tmpl1 && $.data(tmplElem).jsvTmpl === tmpl1 && tmpl3.render({name: "Jo3"}), isIE8 ? "\nA_Jo3_B" : "A_Jo3_B", "Recompile template declared in script block, without caching"); 90 | 91 | // ................................ Reset ................................ 92 | delete $.data(tmplElem).jsvTmpl; 93 | tmplElem.removeAttribute("data-jsv-tmpl"); 94 | 95 | // =============================== Arrange =============================== 96 | // ............................... Assert ................................. 97 | assert.equal($.templates("#my_tmpl2").render(), isIE8 ? "\n' \" \\ \\' \\\"" : "' \" \\ \\' \\\"", "correct treatment of ' \" and ' in template declared in script block"); 98 | 99 | assert.equal($.templates("' \" \\ \\' \\\"").render(), "' \" \\ \\' \\\"", "correct treatment of ' \" and ' in template compiled from string"); 100 | 101 | $.templates("my_tmpl", tmplString); 102 | assert.equal($.render.my_tmpl(person), "A_Jo_B", 'Compile a template and then render it: $.templates("my_tmpl", tmplString); $.render.my_tmpl(data);'); 103 | 104 | $.templates({myTmpl2: tmplString}); 105 | assert.equal($.render.myTmpl2(person), "A_Jo_B", 'Compile and register templates: $.templates({"my_tmpl", tmplString, ...}); $.render.my_tmpl(data);'); 106 | 107 | assert.equal($.templates.myTmpl2.render(person), "A_Jo_B", 'Get named template: $.templates.my_tmpl.render(data);'); 108 | 109 | assert.equal($.templates(tmplString).render(person), "A_Jo_B", 'Compile without registering as named template: $.templates(tmplString).render(person);'); 110 | 111 | tmpl2 = $.templates("#my_tmpl"); 112 | tmpl3 = $.templates("#my_tmpl"); 113 | assert.equal(tmpl2 === tmpl3 && $.trim(tmpl2.render(person)), "A_Jo_B", 'var tmpl = $.templates("#my_tmpl"); returns compiled template for script element'); 114 | 115 | $.templates({ 116 | my_tmpl3: { 117 | markup: "#my_tmpl" 118 | } 119 | }); 120 | 121 | assert.equal($.render.my_tmpl3 === $.templates.my_tmpl3 && $.templates.my_tmpl3 !== tmpl2 && $.trim($.render.my_tmpl3(person)), "A_Jo_B", 'Named template for template object with selector: {markup: "#my_tmpl"}'); 122 | 123 | tmpl3 = $.templates("", { 124 | markup: "#my_tmpl" 125 | }); 126 | assert.equal($.trim(tmpl3.render(person)), "A_Jo_B", 'Compile from template object with selector, without registering: {markup: "#my_tmpl"}'); 127 | 128 | var tmpl4 = $.templates({ 129 | markup: "#my_tmpl" 130 | }); 131 | assert.equal($.trim(tmpl4.render(person)), "A_Jo_B", 'Compile from template object with selector, without registering: {markup: "#my_tmpl"}'); 132 | 133 | assert.equal($.templates("#my_tmpl"), $.templates("#my_tmpl"), '$.templates("#my_tmpl") caches compiled template, and does not recompile each time;'); 134 | 135 | assert.ok($.templates({markup: "#my_tmpl"}) !== $.templates({markup: "#my_tmpl"}), '$.templates({markup: "#my_tmpl" ...}) recompiles template, so as to merge additional options;'); 136 | 137 | assert.equal($.templates("", "#my_tmpl"), $.templates("#my_tmpl"), '$.templates("#my_tmpl") and $.templates("", "#my_tmpl") are equivalent'); 138 | 139 | var renamed = $.templates("renamed", "#my_tmpl"); 140 | assert.ok(renamed === tmpl2 && renamed.tmplName === "renamed", '$.templates("renamed", "#my_tmpl") will rename the cached template'); 141 | 142 | $.templates({renamed2: "#my_tmpl"}); 143 | assert.ok($.templates.renamed2 === tmpl2 && $.templates.renamed2.tmplName === "renamed2", '$.templates({renamed2: "#my_tmpl"}) will rename the cached template'); 144 | 145 | $.templates("cloned", {markup: "#my_tmpl"}); 146 | assert.ok($.templates.cloned !== tmpl2 && $.templates.cloned.tmplName === "cloned", '$.templates("cloned", {markup: "#my_tmpl"}}) will clone the cached template'); 147 | 148 | $.templates({cloned2: {markup: "#my_tmpl"}}); 149 | assert.ok($.templates.cloned2 !== tmpl2 && $.templates.cloned2.tmplName === "cloned2", '$.templates({cloned: {markup: "#my_tmpl"}}) will clone the cached template'); 150 | 151 | $.templates("my_tmpl", null); 152 | assert.equal($.templates.my_tmpl, undefined, 'Remove a named template: $.templates("my_tmpl", null);'); 153 | 154 | $.templates({ 155 | scriptTmpl: { 156 | markup: "#my_tmpl", 157 | debug:true 158 | }, 159 | tmplFromString: { 160 | markup: "X_{{:name}}_Y", 161 | debug:true 162 | } 163 | }); 164 | assert.equal($.templates.tmplFromString.fn.toString().indexOf("debugger;") > 0 165 | && $.templates.scriptTmpl.fn.toString().indexOf("debugger;") > 0 166 | && $.templates.scriptTmpl({name: "Jo"}) + $.templates.tmplFromString({name: "Jo"}), isIE8 167 | ? "\nA_Jo_BX_Jo_Y" 168 | : "A_Jo_BX_Jo_Y", 169 | 'Debug a template: set debug:true on object'); 170 | 171 | // reset 172 | $("#my_tmpl")[0].removeAttribute("data-jsv-tmpl"); 173 | 174 | delete $.templates.scriptTmpl; 175 | }); 176 | 177 | QUnit.test("render", function(assert) { 178 | assert.equal($.trim($("#my_tmpl").render(person)), "A_Jo_B", '$(tmplSelector).render(data);'); // Trimming because IE adds whitespace 179 | 180 | var tmpl3 = $.templates("my_tmpl4", tmplString); 181 | 182 | assert.equal($.render.my_tmpl4(person), "A_Jo_B", '$.render.my_tmpl(object);'); 183 | assert.equal($.render.my_tmpl4(people), "A_Jo_BA_Bill_B", '$.render.my_tmpl(array);'); 184 | 185 | var tmplObject = $.templates.my_tmpl4; 186 | assert.equal(tmplObject.render(people), "A_Jo_BA_Bill_B", 'var tmplObject = $.templates.my_tmpl; tmplObject.render(data);'); 187 | 188 | $.templates("my_tmpl5", "A_{{for}}inner{{:name}}content{{/for}}_B"); 189 | assert.equal($.templates.my_tmpl5.tmpls[0].render(person), "innerJocontent", 'Nested template objects: $.templates.my_tmpl.tmpls'); 190 | 191 | $("#result").html(""); 192 | assert.equal($("#tmpl").render([null,undefined,1], {foo:"foovalue"}, true), (isIE8 ? "\n" : "") + "Content012foovalue", 'render(array, helpers, true) renders an array without iteration, while passing in helpers'); 193 | 194 | $("#result").html(""); 195 | assert.equal($("#tmpl").render([null, undefined, 1], true), (isIE8 ? "\n" : "") + "Content012", 'render(array, true) renders an array without iteration'); 196 | $("#result").empty(); 197 | }); 198 | 199 | QUnit.test("converters", function(assert) { 200 | function loc(data) { 201 | switch (data) {case "desktop": return "bureau"; } 202 | } 203 | $.views.converters({loc: loc}); 204 | assert.equal($.templates("{{loc:#data}}:{{loc:'desktop'}}").render("desktop"), "bureau:bureau", "$.views.converters({loc: locFunction})"); 205 | 206 | $.views.converters("loc2", loc); 207 | assert.equal($.views.converters.loc2 === loc, true, 'locFunction === $.views.converters.loc'); 208 | 209 | $.views.converters({loc2: null}); 210 | assert.equal($.views.converters.loc2, undefined, 'Remove a registered converter: $.views.converters({loc: null})'); 211 | }); 212 | 213 | QUnit.test("tags", function(assert) { 214 | $.views.tags({sort1: sort}); 215 | assert.equal($.templates("{{sort1 people reverse=true}}{{:name}}{{/sort1}}").render({people: people}), "BillJo", "$.views.tags({sort: sortFunction})"); 216 | 217 | $.views.tags("sort2", sort); 218 | assert.equal($.views.tags.sort1.render === sort, true, 'sortFunction === $.views.tags.sort'); 219 | 220 | $.views.tags("sort2", null); 221 | assert.equal($.views.tags.sort2, undefined, 'Remove a registered tag: $.views.tag({sor: null})'); 222 | }); 223 | 224 | QUnit.test("helpers", function(assert) { 225 | function concat() { 226 | return "".concat.apply("", arguments); 227 | } 228 | 229 | $.views.helpers({ 230 | not: function(value) { 231 | return !value; 232 | }, 233 | concat: concat 234 | }); 235 | assert.equal($.templates("{{:~concat(a, 'b', ~not(false))}}").render({a: "aVal"}), "aValbtrue", "$.views.helpers({concat: concatFunction})"); 236 | 237 | $.views.helpers({concat2: concat}); 238 | 239 | assert.equal($.views.helpers.concat === concat, true, 'concatFunction === $.views.helpers.concat'); 240 | 241 | $.views.helpers("concat2", null); 242 | assert.equal($.views.helpers.concat2, undefined, 'Remove a registered helper: $.views.helpers({concat: null})'); 243 | }); 244 | 245 | QUnit.test("template encapsulation", function(assert) { 246 | $.templates({ 247 | myTmpl6: { 248 | markup: "{{sort reverse=true people}}{{:name}}{{/sort}}", 249 | tags: { 250 | sort: sort 251 | } 252 | } 253 | }); 254 | assert.equal($.render.myTmpl6({people: people}), "BillJo", '$.templates("my_tmpl", tmplObjWithNestedItems);'); 255 | }); 256 | 257 | QUnit.test("$.views.viewModels", function(assert) { 258 | // =============================== Arrange =============================== 259 | var Constr = $.views.viewModels({getters: ["a", "b"]}); 260 | // ................................ Act .................................. 261 | var vm = Constr("a1 ", "b1 "); 262 | var result = vm.a() + vm.b(); 263 | vm.a("a2 "); 264 | vm.b("b2 "); 265 | result += vm.a() + vm.b(); 266 | // ............................... Assert ................................. 267 | assert.equal(result, "a1 b1 a2 b2 ", "viewModels, two getters, no methods"); 268 | 269 | // =============================== Arrange =============================== 270 | Constr = $.views.viewModels({getters: ["a", "b", "c"], extend: {add: function(val) { 271 | this.c(val + this.a() + this.b() + this.c()); 272 | }}}); 273 | // ................................ Act .................................. 274 | vm = Constr("a1 ", "b1 ", "c1 "); 275 | vm.add("before "); 276 | result = vm.c(); 277 | // ............................... Assert ................................. 278 | assert.equal(result, "before a1 b1 c1 ", "viewModels, two getters, one method"); 279 | 280 | // =============================== Arrange =============================== 281 | Constr = $.views.viewModels({extend: {add: function(val) { 282 | this.foo = val; 283 | }}}); 284 | // ................................ Act .................................. 285 | vm = Constr(); 286 | vm.add("before"); 287 | result = vm.foo; 288 | // ............................... Assert ................................. 289 | assert.equal(result, "before", "viewModels, no getters, one method"); 290 | 291 | // =============================== Arrange =============================== 292 | Constr = $.views.viewModels({getters: []}); 293 | // ................................ Act .................................. 294 | vm = Constr(); 295 | result = JSON.stringify(vm); 296 | // ............................... Assert ................................. 297 | assert.equal(result, "{}", "viewModels, no getters, no methods"); 298 | 299 | // =============================== Arrange =============================== 300 | $.views.viewModels({ 301 | T1: { 302 | getters: ["a", "b"] 303 | } 304 | }); 305 | // ................................ Act .................................. 306 | vm = $.views.viewModels.T1.map({a: "a1 ", b: "b1 "}); 307 | 308 | result = vm.a() + vm.b(); 309 | vm.a("a2 "); 310 | vm.b("b2 "); 311 | result += vm.a() + vm.b(); 312 | 313 | // ............................... Assert ................................. 314 | assert.equal(result, "a1 b1 a2 b2 ", "viewModels, two getters, no methods"); 315 | 316 | // ................................ Act .................................. 317 | vm.merge({a: "a3 ", b: "b3 "}); 318 | 319 | result = vm.a() + vm.b(); 320 | 321 | // ............................... Assert ................................. 322 | assert.equal(result, "a3 b3 ", "viewModels merge, two getters, no methods"); 323 | 324 | // ................................ Act .................................. 325 | result = vm.unmap(); 326 | result = JSON.stringify(result); 327 | 328 | // ............................... Assert ................................. 329 | assert.equal(result, '{"a":"a3 ","b":"b3 "}', "viewModels unmap, two getters, no methods"); 330 | 331 | // =============================== Arrange =============================== 332 | var viewModels = $.views.viewModels({ 333 | T1: { 334 | getters: ["a", {getter: "b"}, "c", "d", {getter: "e", type: undefined}, {getter: "f", type: null}, {getter: "g", type: "foo"}, {getter: "h", type: ""}] 335 | } 336 | }, {}); 337 | // ................................ Act .................................. 338 | vm = viewModels.T1.map({a: "a1 ", b: "b1 ", c: "c1 ", d: "d1 ", e: "e1 ", f: "f1 ", g: "g1 ", h: "h1 "}); 339 | result = vm.a() + vm.b() + vm.c() + vm.d() + vm.e() + vm.f() + vm.g() + vm.h(); 340 | vm.a("a2 "); 341 | vm.b("b2 "); 342 | result += vm.a() + vm.b(); 343 | // ............................... Assert ................................. 344 | assert.equal(result, "a1 b1 c1 d1 e1 f1 g1 h1 a2 b2 ", 345 | "viewModels, multiple unmapped getters, no methods"); 346 | 347 | // ................................ Act .................................. 348 | vm.merge({a: "a3 ", b: "b3 ", c: "c3 ", d: "d3 ", e: "e3 ", f: "f3 ", g: "g3 ", h: "h3 "}); 349 | 350 | result = vm.a() + vm.b() + vm.c() + vm.d() + vm.e() + vm.f() + vm.g() + vm.h(); 351 | 352 | // ............................... Assert ................................. 353 | assert.equal(result, "a3 b3 c3 d3 e3 f3 g3 h3 ", 354 | "viewModels merge, multiple unmapped getters, no methods"); 355 | 356 | // ................................ Act .................................. 357 | result = vm.unmap(); 358 | result = JSON.stringify(result); 359 | 360 | // ............................... Assert ................................. 361 | assert.equal(result, '{"a":"a3 ","b":"b3 ","c":"c3 ","d":"d3 ","e":"e3 ","f":"f3 ","g":"g3 ","h":"h3 "}', 362 | "viewModels unmap, multiple unmapped getters, no methods"); 363 | 364 | // =============================== Arrange =============================== 365 | $.views.viewModels({ 366 | T1: { 367 | getters: ["a", "b", "c"], 368 | extend : { 369 | add: function(val) { 370 | this.c(val + this.a() + this.b() + this.c()); 371 | } 372 | } 373 | } 374 | }); 375 | 376 | // ................................ Act .................................. 377 | vm = $.views.viewModels.T1.map({a: "a1 ", b: "b1 ", c: "c1 "}); 378 | 379 | vm.add("before "); 380 | result = vm.c(); 381 | 382 | // ............................... Assert ................................. 383 | assert.equal(result, "before a1 b1 c1 ", "viewModels, getters and one method"); 384 | 385 | // ................................ Act .................................. 386 | vm.merge({a: "a3 ", b: "b3 ", c: "c3 "}); 387 | vm.add("updated "); 388 | result = vm.c(); 389 | 390 | // ............................... Assert ................................. 391 | assert.equal(result, "updated a3 b3 c3 ", "viewModels merge, getters and one method"); 392 | 393 | // ................................ Act .................................. 394 | result = vm.unmap(); 395 | result = JSON.stringify(result); 396 | 397 | // ............................... Assert ................................. 398 | assert.equal(result, '{"a":"a3 ","b":"b3 ","c":"updated a3 b3 c3 "}', "viewModels unmap, getters and one method"); 399 | 400 | // =============================== Arrange =============================== 401 | $.views.viewModels({ 402 | T1: { 403 | getters: ["a", "b"] 404 | }, 405 | T2: { 406 | getters: [{getter: "t1", type: "T1"}, {getter: "t1Arr", type: "T1"}, {getter: "t1OrNull", type: "T1", defaultVal: null}] 407 | } 408 | }); 409 | viewModels = $.views.viewModels; 410 | // ................................ Act .................................. 411 | var t1 = viewModels.T1.map({a: "a1 ", b: "b1 "}); // Create a T1 412 | var t2 = viewModels.T2.map({t1: {a: "a3 ", b: "b3 "}, t1Arr: [t1.unmap(), {a: "a2 ", b: "b2 "}]}); // Create a T2 (using unmap to scrape values the T1: vm) 413 | 414 | result = JSON.stringify(t2.unmap()); 415 | 416 | // ............................... Assert ................................. 417 | assert.equal(result, '{"t1":{"a":"a3 ","b":"b3 "},"t1Arr":[{"a":"a1 ","b":"b1 "},{"a":"a2 ","b":"b2 "}],"t1OrNull":null}', 418 | "viewModels, hierarchy"); 419 | 420 | // ................................ Act .................................. 421 | t2.t1Arr()[0].merge({a: "a1x ", b: "b1x "}); // merge not the root, but a VM instance within hierarchy: vm2.t1Arr()[0] - leaving rest unchanged 422 | result = JSON.stringify(t2.unmap()); 423 | 424 | // ............................... Assert ................................. 425 | assert.equal(result, '{"t1":{"a":"a3 ","b":"b3 "},"t1Arr":[{"a":"a1x ","b":"b1x "},{"a":"a2 ","b":"b2 "}],"t1OrNull":null}', 426 | "viewModels, merge deep node"); 427 | 428 | // ................................ Act .................................. 429 | var t1Arr = viewModels.T1.map([{a: "a1 ", b: "b1 "}, {a: "a2 ", b: "b2 "}]); // Create a T1 array 430 | var t2FromArr = viewModels.T2.map({t1: {a: "a3 ", b: "b3 "}, t1Arr: t1Arr.unmap()}); // Create a T2 (using unmap to scrape values the T1: vm) 431 | result = JSON.stringify(t2FromArr.unmap()); 432 | 433 | // ............................... Assert ................................. 434 | assert.equal(result, '{"t1":{"a":"a3 ","b":"b3 "},"t1Arr":[{"a":"a1 ","b":"b1 "},{"a":"a2 ","b":"b2 "}],"t1OrNull":null}', 435 | "viewModels, hierarchy"); 436 | 437 | // ................................ Act .................................. 438 | t1Arr = viewModels.T1.map([{a: "a1 ", b: "b1 "}, {a: "a2 ", b: "b2 "}]); // Create a T1 array 439 | t1Arr.push(viewModels.T1("a3 ", "b3 ")); 440 | t2FromArr = viewModels.T2.map({t1: {a: "a4 ", b: "b4 "}, t1Arr: t1Arr.unmap()}); // Create a T2 (using unmap to scrape values the T1: vm) 441 | result = JSON.stringify(t2FromArr.unmap()); 442 | 443 | // ............................... Assert ................................. 444 | assert.equal(result, '{"t1":{"a":"a4 ","b":"b4 "},"t1Arr":[{"a":"a1 ","b":"b1 "},{"a":"a2 ","b":"b2 "},{"a":"a3 ","b":"b3 "}],"t1OrNull":null}', 445 | "viewModels, hierarchy"); 446 | 447 | // ................................ Act .................................. 448 | var t2new= viewModels.T2(viewModels.T1("a3 ", "b3 "), [viewModels.T1("a1 ", "b1 "), viewModels.T1("a2 ", "b2 ")], viewModels.T1("a4 ", "b4 ")); 449 | result = JSON.stringify(t2new.unmap()); 450 | 451 | // ............................... Assert ................................. 452 | assert.equal(result, '{"t1":{"a":"a3 ","b":"b3 "},"t1Arr":[{"a":"a1 ","b":"b1 "},{"a":"a2 ","b":"b2 "}],"t1OrNull":{"a":"a4 ","b":"b4 "}}', 453 | "viewModels, hierarchy"); 454 | }); 455 | 456 | })(this, this.jQuery); 457 | -------------------------------------------------------------------------------- /test/unit-tests/tests-node.js: -------------------------------------------------------------------------------- 1 | /*global QUnit*/ 2 | (function(undefined) { 3 | "use strict"; 4 | 5 | var jsrender = require('./../../jsrender-node.js'); 6 | var tmplify = require('./../../tmplify/index.js'); 7 | 8 | function upper(val) { 9 | return val.toUpperCase(); 10 | } 11 | function lower(val) { 12 | return val.toLowerCase(); 13 | } 14 | QUnit.module("node"); 15 | QUnit.test("jsrender.renderFile / jsrender.__express", function(assert) { 16 | var html = jsrender.renderFile('./test/templates/name-template.html', {name: "Jo"}); 17 | assert.equal(html, "Name: Jo (name-template.html)", 'jsrender.renderFile("./file.path.html", data) loads and renders template'); 18 | 19 | html = jsrender.__express('./test/templates/name-template.html', {name: "Jo"}); 20 | assert.equal(html, "Name: Jo (name-template.html)", 'jsrender.__express("./file.path.html", data) loads and renders template'); 21 | }); 22 | 23 | QUnit.test("jsrender.templates", function(assert) { 24 | var tmpl = jsrender.templates('./test/templates/name-template.html'); 25 | var html = tmpl({name: "Jo"}); 26 | assert.equal(html, "Name: Jo (name-template.html)", 'jsrender.templates("./file.path.html") compiles template'); 27 | 28 | tmpl = jsrender.templates({markup: 'Some {{:~upper("Markup")}} Name: {{:~upper(name)}} {{lower:name}}', helpers: {upper:upper}, converters: {lower:lower}}); 29 | html = tmpl({name: "Jo"}); 30 | assert.equal(html, "Some MARKUP Name: JO jo", 'jsrender.templates({markup: ..., helpers: ..., ...}) compiles template with options'); 31 | }); 32 | 33 | QUnit.test("jsrender.compile", function(assert) { 34 | var tmpl = jsrender.compile('./test/templates/name-template.html'); 35 | var html = tmpl({name: "Jo"}); 36 | assert.equal(html, "Name: Jo (name-template.html)", 'jsrender.compile("./file.path.html") compiles template'); 37 | 38 | tmpl = jsrender.compile('Some {{:~upper("Markup")}} Name: {{:~upper(name)}} {{lower:name}}', {helpers: {upper:upper}, converters: {lower:lower}}); 39 | html = tmpl({name: "Jo"}); 40 | assert.equal(html, "Some MARKUP Name: JO jo", 'jsrender.compile("markup", {helpers: ..., ...}) compiles template with options'); 41 | }); 42 | 43 | QUnit.test("jsrender.tags.clientTemplate", function(assert) { 44 | jsrender.views.settings.delimiters("<%", "%>"); 45 | var tmpl = jsrender.compile( 46 | '\n' 47 | + '\n' 48 | + '<%clientTemplate "./test/templates/outer.html"/%>\n' 49 | + '<%clientTemplate "./test/templates/inner.html"/%>\n' 50 | + '\n' 51 | + '
          \n' 52 | + ''); 53 | var html = tmpl({name: "Jo"}); 54 | assert.equal(html, 55 | '\n' 56 | + '\n' 57 | + '\n' 58 | + '\n' 59 | + '\n' 60 | + '
          \n' 61 | + '', 62 | 'Server-rendered templates using {{clientTemplate "./.../tmpl.html"}}\nand direct rendering using different delimiters on server/client'); 63 | }); 64 | 65 | QUnit.test("jsrender/tmplify .html template", function(assert) { 66 | const done = assert.async(); 67 | var outputFile = 'test/browserify/bundles/html-jsr-tmpl-bundle.js'; 68 | var fs = require('fs'); 69 | var browserify = require('browserify'); 70 | browserify('test/browserify/html-jsr-tmpl.js') 71 | 72 | .transform(tmplify) // Use default extensions: "html jsr jsrender" 73 | .bundle() 74 | .pipe(fs.createWriteStream(outputFile) 75 | .on('finish', function() { 76 | assert.ok(fs.readFileSync(outputFile, 'utf8').indexOf("browserify.done.html ") > 0, 'browserify().transform(tmplify)'); 77 | done(); 78 | }) 79 | ) 80 | .on('error', function(err) { 81 | console.log(err); 82 | }); 83 | }); 84 | 85 | QUnit.test("jsrender/tmplify options: 'htm jsr'", function(assert) { 86 | const done = assert.async(); 87 | var outputFile = 'test/browserify/bundles/htm-jsrender-tmpl-bundle.js'; 88 | var fs = require('fs'); 89 | var browserify = require('browserify'); 90 | browserify('test/browserify/htm-jsrender-tmpl.js') 91 | .transform(tmplify, {extensions: 'htm jsrender'}) 92 | .bundle() 93 | .pipe(fs.createWriteStream(outputFile)) 94 | .on('finish', function() { 95 | assert.ok(fs.readFileSync(outputFile, 'utf8').indexOf("browserify.done.htm ") > 0, 'browserify().transform(tmplify, {extensions: "..., ..."})'); 96 | done(); 97 | }) 98 | .on('error', function(err) { 99 | console.log(err); 100 | }); 101 | }); 102 | 103 | })(); 104 | -------------------------------------------------------------------------------- /tmplify/index.js: -------------------------------------------------------------------------------- 1 | /*! JsRender tmplify submodule v1.0.2: http://jsviews.com/#jsrender */ 2 | /*! Browserify transform for JsRender templates */ 3 | /* 4 | * Copyright 2025, Boris Moore 5 | * Released under the MIT License. 6 | */ 7 | 8 | (function() { 9 | "use strict"; 10 | var jsrender = require('./../jsrender-node.js'), 11 | fs = require('fs'), 12 | path = require('path'), 13 | pathSep = path.sep, 14 | through = require('through2'), 15 | rootDirPath = path.resolve("./"), 16 | rootDirPathLen = rootDirPath.length + 1; 17 | 18 | function isTemplate(fileExt, extensions) { 19 | extensions = typeof extensions === "string" 20 | ? extensions 21 | : "html jsrender jsr"; // Default extensions 22 | return new RegExp("\\s" + fileExt + "\\s").test(" " + extensions + " "); 23 | } 24 | 25 | module.exports = function(file, options) { 26 | var nodeFileDirName = path.dirname(file); 27 | 28 | if (!isTemplate(path.extname(file).slice(1), options && (options.extensions || options.e))) { 29 | return through(); 30 | } 31 | return through(function(buf, enc, next) { 32 | var createTmplCode, ref, pathFromFileDir, 33 | markup = buf.toString().replace(/^\uFEFF/, ''), // Remove BOM if necessary 34 | tmpl = jsrender.templates(markup), 35 | bundledFile = 'var tmplRefs = [],\n' 36 | + " mkup = '" + markup.replace(/['"\\]/g, "\\$&").replace(/[ \t]*(\r\n|\n|\r)/g, '\\n') + "',\n" // Normalize newlines, and escape quotes and \ character 37 | + ' $ = global.jsrender || global.jQuery;\n\n', 38 | templateName = './' + file.slice(rootDirPathLen).split(pathSep).join('/'); 39 | 40 | for (ref in tmpl.refs) { 41 | // Recursively bundle any nested template references, e.g. {{include tmpl="./some/template.html/}}" 42 | fs.stat(ref, function(err, stat) { 43 | // Async check that file exists 44 | if(err && err.code == 'ENOENT') { 45 | throw new Error("Template '" + ref + "' not found at '" + err.path + "'. Use path relative to '" + rootDirPath + "'."); 46 | } 47 | }); 48 | pathFromFileDir = './' + path.relative(nodeFileDirName, ref).split(pathSep).join('/'); 49 | bundledFile += 'tmplRefs.push(require("' + pathFromFileDir + '"));\n'; 50 | } 51 | 52 | createTmplCode = '$.templates("' + templateName + '", mkup)'; 53 | bundledFile += 54 | 'module.exports = $ ? ' + createTmplCode 55 | + ' :\n function($) {\n' 56 | + ' if (!$ || !$.views) {throw "Requires jsrender/jQuery";}\n' 57 | + ' while (tmplRefs.length) {\n tmplRefs.pop()($); // compile nested template\n }\n\n' 58 | + ' return ' + createTmplCode 59 | + '\n };'; 60 | this.push(bundledFile); 61 | next(); 62 | }); 63 | }; 64 | }()); 65 | -------------------------------------------------------------------------------- /typescript/jsrender/index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for JsRender 1.0 2 | // Version: "v1.0.16" 3 | // Project: http://www.jsviews.com/#jsrender 4 | // Definitions by: Boris Moore 5 | // Definitions: https://www.jsviews.com/download/typescript/jsrender/index.d.ts 6 | // TypeScript Version: 2.3 7 | 8 | /// 9 | 10 | declare module 'jsrender' { 11 | export = jsrender; 12 | } 13 | 14 | declare const jsrender: ((jquery?: JQueryStatic) => JQueryStatic) & JQueryStatic; 15 | 16 | // ********************************** JsRender ********************************** 17 | 18 | interface JQueryStatic { 19 | /* var htmlString = $.render.templateName(data, myHelpersObject); // Render named template */ 20 | render: { 21 | [tmplName: string]: JsViews.TemplateRender; 22 | }; 23 | 24 | /* $.views.xxx ... // JsRender/JsViews APIs */ 25 | views: JsViews.Views; 26 | 27 | /* $.templates(...) or $.templates.templateName: Compile/get template */ 28 | templates: JsViews.Templates; 29 | } 30 | 31 | interface JQuery { 32 | /* var htmlString = $("#template").render(data, myHelpersObject); // Render template, and pass in helpers or context */ 33 | render: JsViews.TemplateRender; 34 | } 35 | 36 | declare namespace JsViews { 37 | 38 | /* Generic hash of objects of type T */ 39 | interface Hash { 40 | [option: string]: T; 41 | } 42 | 43 | /* $.views*/ 44 | interface Views { 45 | /* $.views.templates() */ 46 | templates: Templates; 47 | 48 | /* $.views.converters() */ 49 | converters: Store; 50 | 51 | /* $.views.tags() */ 52 | tags: Store; 53 | 54 | /* $.views.helpers() */ 55 | helpers: Store; 56 | 57 | /* $.views.viewModels() */ 58 | viewModels: ViewModels; 59 | 60 | /* $.views.settings */ 61 | settings: Settings; 62 | 63 | /* $.views.sub.xxx */ 64 | sub: Hash; 65 | 66 | /* $.views.map() */ 67 | map(any: any): any; 68 | } 69 | 70 | /* $.views.settings*/ 71 | interface Settings { 72 | /** 73 | * Set delimiters 74 | * $.views.settings.delimiters(...) 75 | * 76 | * @param {string} openChars 77 | * @param {string} [closeChars] 78 | * @param {string} [link] 79 | * @returns {Settings} 80 | */ 81 | delimiters(openChars: string, closeChars?: string, link?: string): Settings; 82 | delimiters(chars: string[]): Settings; 83 | /** 84 | * Get delimiters 85 | * delimsArray = $.views.settings.delimiters() 86 | * 87 | * @returns {string[]} 88 | */ 89 | delimiters(): string[]; 90 | 91 | /** 92 | * Set debug mode 93 | * $.views.settings.debugMode(true) 94 | * 95 | * @param {boolean} debugMode 96 | * @returns {Settings} 97 | */ 98 | debugMode(debugMode: boolean | ((e?: any, fallback?: string, view?: View) => any)): Settings; 99 | /** 100 | * Get debug mode setting 101 | * debugMode = $.views.settings.debugMode() 102 | * 103 | * @returns {boolean} 104 | */ 105 | debugMode(): boolean; 106 | 107 | /** 108 | * Set allowCode mode 109 | * $.views.settings.allowCode(true) 110 | * 111 | * @param {boolean} allowCode 112 | * @returns {Settings} 113 | */ 114 | allowCode(allowCode: boolean): Settings; 115 | /** 116 | * Get allowCode mode setting 117 | * allowCode = $.views.settings.allowCode() 118 | * 119 | * @returns {boolean} 120 | */ 121 | allowCode(): boolean; 122 | 123 | /** 124 | * Set advanced settings (useViews, _jsv ...) 125 | * $.views.settings.advanced({useViews: true}) 126 | * 127 | * @param {object} settings 128 | * @returns {Settings} 129 | */ 130 | advanced(settings: Hash < any>): Settings; 131 | /** 132 | * Get advanced settings 133 | * advancedSettings = $.views.settings.advanced() 134 | * 135 | * @returns {object} 136 | */ 137 | advanced(): Hash < any>; 138 | } 139 | 140 | interface Store { 141 | /** 142 | * Generic store() function to register item, named item, or hash of items 143 | * Also used as hash to store the registered items 144 | * Used as implementation of $.templates(), $.views.templates(), $.views.tags(), $.views.helpers() and $.views.converters() 145 | * 146 | * @param {string|hash} name name - or selector, in case of $.templates(). Or hash of items 147 | * @param {any} [item] (e.g. markup for named template) 148 | * @param {Template} [parentTmpl] For item being registered as private resource of template 149 | * @returns {any|Views} e.g. compiled template - or $.views in case of registering hash of items 150 | */ 151 | (name: string, item?: TO, parentTmpl?: Template): T; // named item 152 | (namedItems: Hash, parentTmpl?: Template): Views; // Multiple named items 153 | 154 | /** 155 | * var template = $.templates.templateName; // Get compiled named template - or similar for named tags, converters, helpers, viewModels 156 | */ 157 | [itemName: string]: T; 158 | } 159 | 160 | // Templates 161 | 162 | interface Templates extends Store { 163 | /** 164 | * Additional $.templates() signature for compiling unnamed template 165 | * 166 | * @param {string|TemplateOptions} markup or selector 167 | * @param {Template} [parentTmpl] For compling template as private resource of parent template 168 | * @returns {Template} compiled template 169 | */ 170 | (markupOrSelectorOrOptions: string | TemplateOptions, parentTmpl?: Template): Template; // Unnamed template 171 | } 172 | 173 | interface TemplateOptions { 174 | /* Template options hash */ 175 | tags?: Hash; 176 | templates?: Hash; 177 | 178 | markup: any; 179 | converters?: Hash; 180 | helpers?: Hash; 181 | useViews?: boolean; 182 | } 183 | 184 | type TemplateSetter = TemplateOptions | string; 185 | 186 | interface Template extends TemplateRender { 187 | /* Compiled template object */ 188 | tmplName: string; 189 | tags?: Hash; 190 | templates?: Hash