├── .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 | [](https://www.npmjs.com/package/jsrender)
4 | [](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 |
14 |
15 |
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 |
21 |
22 |
23 |
24 |
Demos
25 |
27 |
See also the following links:
28 |
29 |
Source code:
30 |
31 |
32 |
33 | See also on JsViews site:
34 |
35 |
36 |
JsViews are interactive data-driven views, built on top of JsRender templates
37 |
38 |
JsViews and JsRender Overview:
39 |
40 |
41 |
42 | Other links:
43 |
44 |
50 |
51 | Tests :
52 |
53 |
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 | Title {{>path}}
28 |
29 |
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 | Title {{get path default="..."}}
47 |
48 |
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 | Title {{yesNo path yes="..." no="..."}}
66 |
67 |
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 | Title Languages
40 |
41 |
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 | Title Languages
68 |
69 |
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 | Title Languages
97 |
98 |
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 | Title Languages
149 |
150 |
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 | Title Languages
179 |
180 |
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 | Title Details
23 |
24 |
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 | title languages summary
83 |
84 |
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 | Show full details
16 |
17 |
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 |
21 | {{:value}} — does not convert. Used to render values that include html markup.
22 | {{loc:value lang="..."}} — Uses custom converter.
23 | {{html:value}} — Converts using built-in HTML encoder. (Better security within element content, but slight perf cost).
24 | {{>value}} — Alternative syntax for built-in HTML encoder.
25 | {{attr:availability}} — Converts using built-in attribute encoder. (Better security within attributes).
26 | {{url:value lang="..."}} — Converts using built-in URL encoder.
27 |
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 | Title (loc:English) Title (loc:French) No Convert HTML Encode
44 |
45 |
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 | title languages
32 |
33 |
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 | Title Languages
33 |
34 |
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 | Synopsis Fixed Template Template specified in data Conditional Template Wrapper Template Repeating Wrapper Template
73 |
74 |
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 | Render method wrapping content
49 | Render method with external content
50 | Tag template wrapping content
51 | Tag template with external content
52 | Sort tag: Reverse order
53 | Sort tag: Languages (external content)
54 |
55 |
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 | Title Languages
47 |
48 |
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 | Title {{if !(languages && languages.length)}} {{if a==null}}
41 |
42 |
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 | Title Languages (+specialMessage)
36 |
37 |
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 | Title Languages (+specialMessage)
51 |
52 |
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 | Title Languages (+specialMessage)
66 |
67 |
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 |
12 |
13 | Compiling template objects from strings
14 |
15 |
16 |
17 | Show full details
18 |
19 |
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 |
12 |
13 | Register script block template declarations, as named templates
14 |
15 |
16 |
17 |
22 |
23 |
30 |
31 | Show full details
32 |
33 |
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 |
12 |
13 | Getting template objects from script block template declarations
14 |
15 |
16 |
17 |
22 |
23 |
30 |
31 | Show full details
32 |
33 |
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 |
12 |
13 | Template composition. Registering and accessing sub-templates.
14 |
15 |
16 |
17 |
31 |
32 |
37 |
38 |
43 |
44 |
49 |
50 |
51 | Synopsis Fixed Template Template specified in data Conditional Template
52 |
53 |
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 |
12 |
13 | Template composition. Passing in template objects as the contextual template parameters.
14 |
15 |
16 |
17 |
31 |
32 |
37 |
38 |
43 |
44 |
49 |
50 |
51 | Synopsis Fixed Template Template specified in data Conditional Template
52 |
53 |
54 |
55 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/demos/variants/variants.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | JsRender: Demos
5 |
6 |
7 |
8 |
9 |
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 |
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 |
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 |
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 |
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 |
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;
191 | render: TemplateRender;
192 | fn?: (...args: any[]) => string;
193 | _is: string;
194 |
195 | markup: string;
196 | converters?: Hash;
197 | helpers?: Hash;
198 | useViews?: boolean;
199 | }
200 |
201 | interface TemplateRender {
202 | /**
203 | * Template render method: render the template as a string, using the specified data and helpers/context
204 | * var htmlString = template(data, myHelpersObject);
205 | * var htmlString = template.render(data, myHelpersObject);
206 | *
207 | * $("#tmpl").render(), tmpl.render(), tagCtx.render(), $.render.namedTmpl()
208 | *
209 | * @param {any} data
210 | * @param {hash} [helpersOrContext]
211 | * @param {boolean} [noIteration]
212 | * @returns {string} rendered template
213 | */
214 | (data?: any, helpersOrContext?: Hash, noIteration?: boolean): string;
215 | (data?: any, noIteration?: boolean): string;
216 | }
217 |
218 | // ViewModels
219 |
220 | interface ViewModelOptions {
221 | /* ViewModel options hash */
222 | // getters?: string[] | ;
223 | getters?: any[];
224 | extend?: Hash;
225 | id?: string | ((a: any, b: any) => boolean);
226 | }
227 |
228 | interface ViewModel {
229 | /* ViewModel options hash */
230 | getters: string[];
231 | extend: Hash;
232 | map(data: any): any;
233 | (...args: any[]): any;
234 | [prop: string]: any;
235 | }
236 |
237 | interface ViewModels extends Hash {
238 | /* $.views.viewModels() */
239 | (viewModel: ViewModelOptions): ViewModel;
240 | (name: string, viewModel: ViewModelOptions, viewModels?: Hash): ViewModel;
241 | (namedItems: Hash, viewModels?: Hash): Hash;
242 | }
243 |
244 | // Converters
245 |
246 | interface Converter {
247 | /* Converter function */
248 | (value: any, ...restArgs: any[]): any;
249 | }
250 |
251 | // Tags
252 |
253 | interface TagOptionProps {
254 | /* Properties that can be set as tag options, and retrieved as Tag properties */
255 | init?: (this: TagInst, tagCtx?: TagCtx, linkCtx?: LinkCtx, ctx?: Context) => void ;
256 | render?: (this: TagInst, ...args: any[]) => string |void ;
257 | baseTag?: string | Tag;
258 | contentCtx?: boolean | ((this: TagInst, arg0: any) => any);
259 | convert?: string | Converter;
260 | argDefault?: boolean;
261 | bindTo?: number | string | Array < number |string>;
262 | bindFrom?: number | string | Array < number |string>;
263 | flow?: boolean;
264 | ctx?: Hash < any>;
265 | [prop: string]: any;
266 | }
267 |
268 | interface TagOptions extends TagOptionProps {
269 | /* Tag options hash */
270 | template?: TemplateSetter;
271 | }
272 |
273 | type TagSetter = TagOptions | string | ((...args: any[]) => any);
274 |
275 | interface Tag extends TagOptionProps {
276 | /* Tag object */
277 | tagCtx: TagCtx;
278 | tagCtxs: TagCtx[];
279 | tagName: string;
280 | parent?: Tag;
281 | parents?: Hash;
282 | rendering?: Hash;
283 | ctxPrm(name: string, value?: any): any; // get/set in JsViews but get only in JsRender
284 | cvtArgs(elseBlock?: number): any[] | void ;
285 | bndArgs(elseBlock?: number): any[] | void ;
286 | base?: (...args: any[]) => any;
287 | baseApply?: (args: any[]|IArguments) => any;
288 | }
289 |
290 | interface TagInst extends Tag {
291 | template?: TemplateSetter;
292 | }
293 |
294 | interface Context {
295 | /* ctx object */
296 | /* Root data object or array */
297 | root: any;
298 |
299 | /* This tag */
300 | tag?: Tag;
301 |
302 | /* Ancestor tags */
303 | parentTags?: Hash;
304 |
305 | /* tagCtx object */
306 | tagCtx?: any;
307 |
308 | [prop: string]: any;
309 | }
310 |
311 | interface TagCtxParams {
312 | /* tagCtx.params object */
313 | args: string[];
314 | props: Hash;
315 | ctx: Hash;
316 | }
317 |
318 | interface TagCtx {
319 | /* tagCtx object */
320 | /* Arguments passed declaratively */
321 | args: any[];
322 |
323 | /* Properties passed declaratively */
324 | props: Hash;
325 |
326 | /* Declarative tag params object */
327 | params: TagCtxParams;
328 |
329 | /* External tmpl, or else template for wrapped content. Otherwise, false */
330 | tmpl: Template;
331 |
332 | /* Template for wrapped content, or else external template. Otherwise, false */
333 | content: Template;
334 |
335 | /* Tag instance index (if siblings rendered against array data) */
336 | index: number;
337 |
338 | /* JsViews view containing this tag instance */
339 | view: View;
340 |
341 | /* View context for tag */
342 | ctx: Context;
343 |
344 | /* This tag instance */
345 | tag: Tag;
346 |
347 | /* Tag render method */
348 | render: TagRenderMethod;
349 |
350 | /* tagCtx.ctxPrm() method */
351 | ctxPrm(name: string, value?: any): any; // get/set in JsViews but get only in JsRender
352 |
353 | /* tagCtx.cvtArgs() method */
354 | cvtArgs(): any[]| void ;
355 |
356 | /* tagCtx.bndArgs() method */
357 | bndArgs(): any[] | void;
358 | }
359 |
360 | interface LinkCtx {
361 | /* linkCtx object, null in JsRender */
362 | }
363 |
364 | interface TagRenderMethod {
365 | /* tag render method */
366 | (...arguments: any[]): string;
367 | }
368 |
369 | interface View {
370 | /* Template rendered by view */
371 | tmpl: Template;
372 |
373 | /* Block content template (for block tags) */
374 | content?: Template;
375 |
376 | /* Child views */
377 | views: View[] & Hash;
378 |
379 | /* Parent view */
380 | parent: View;
381 |
382 | /* View context (helpers and parameters from parent views) */
383 | ctx: Context;
384 |
385 | /* View type */
386 | type: string;
387 |
388 | /* view.get() method: find parent or child views */
389 | get(type?: string): View;
390 | get(inner: boolean, type?: string): View;
391 |
392 | /* view.getIndex() method: get index of parent "item" view */
393 | getIndex(): number;
394 |
395 | /* view.ctxPrm() method: get/set contextual parameter or helper */
396 | ctxPrm(name: string, value?: any): any; // get/set in JsViews but get only in JsRender
397 |
398 | /* Find contextual template resource */
399 | getRsc(namedCollection: string, itemName: string): any;
400 |
401 | /* Index of this view in parent views collection */
402 | index: number;
403 |
404 | /* tag (for tag views) */
405 | tag: Tag;
406 |
407 | /* contextual data */
408 | data: any;
409 |
410 | /* root View (top-level) */
411 | root: View;
412 |
413 | scope: View;
414 | }
415 |
416 | } // end namespace
417 |
--------------------------------------------------------------------------------
/typescript/jsrender/test/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/typescript/jsrender/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "lib": [
5 | "es6",
6 | "dom"
7 | ],
8 | "noImplicitAny": true,
9 | "noImplicitThis": true,
10 | "strictNullChecks": false,
11 | "strictFunctionTypes": true,
12 | "outDir": "./built/",
13 | "sourceMap": true,
14 | "typeRoots": ["../../"],
15 | "types": ["jsrender"]
16 | },
17 | "include": ["tests.ts"],
18 | "noImplicitAny": true
19 | }
20 |
--------------------------------------------------------------------------------