├── .gitignore ├── CNAME ├── LICENSE.txt ├── README.markdown ├── css ├── tempo.css └── tempo.less ├── examples ├── append-prepend-elements.html ├── arrays.html ├── attributes-non-nested.html ├── attributes.html ├── css │ ├── examples.css │ └── grid.css ├── data-from-map.html ├── dataifhas.html ├── error-handling.html ├── events.html ├── fiddles │ ├── filters │ │ └── titlecase │ │ │ ├── demo.details │ │ │ ├── demo.html │ │ │ └── demo.js │ ├── preprocessing │ │ ├── demo.details │ │ ├── demo.html │ │ └── demo.js │ └── simple │ │ ├── demo.details │ │ ├── demo.html │ │ └── demo.js ├── filters-encodeURI.html ├── filters-escape.html ├── filters-multiple.html ├── filters-titlecase.html ├── images │ ├── loading.gif │ └── tweeter.png ├── jqueryelements.html ├── js │ └── jquery-1.5.1.min.js ├── jsperf.html ├── multicontainer.html ├── nested-conditional-templates.html ├── nested-generic-templates.html ├── nested-templates.html ├── null-test.html ├── param-escape.html ├── parentdata-arrays.html ├── parentdata.html ├── partials.html ├── partials │ └── movie.html ├── preprocessing.html ├── recursion.html ├── solr │ ├── index.html │ ├── partials │ │ ├── facet.html │ │ └── result.html │ └── solr.js ├── table-nested-template.html ├── table.html ├── template-attributes.html └── twitter │ └── index.html ├── index.html ├── node ├── express.js ├── http.js ├── module │ └── tempo │ │ ├── README.md │ │ ├── lib │ │ └── tempo.js │ │ ├── package.json │ │ └── tempo.js └── views │ ├── beatle.tpl │ └── beatles.tpl ├── tempo.js ├── tempo.min.js └── tests ├── tests.html └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.iml 3 | *.iws 4 | *.ipr 5 | *.idea 6 | *.tgz 7 | node_modules/ -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | tempojs.com -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Tempo 2.0 2 | 3 | > Tempo is an easy, intuitive JavaScript rendering engine that enables you to craft data templates in pure HTML. 4 | 5 | 6 | ### Why use Tempo? 7 | 8 | * Clear separation of concerns: no HTML in your JavaScript files, and no JavaScript in your HTML 9 | * Makes working with AJAX/JSON content a piece of cake 10 | * Works in Safari, Chrome, FireFox, Opera, and Internet Explorer 6+ 11 | 12 | 13 | ### Key Features 14 | 15 | * Itty-bitty footprint, lightning-fast rendering! 16 | * No dependencies - Use with or without jQuery 17 | * Supports partial, nested and conditional templates 18 | * Support for pre-processing, filter and formatting functions and safe attribute setters 19 | * Variable injection for inline JavaScript expressions 20 | * Degrades gracefully if JavaScript is not enabled 21 | * Configurable syntax for greater compatibility 22 | 23 | 24 | ## Quick start 25 | 26 | ### 1. Include the Tempo script 27 | 28 | 29 | 30 | 31 | ### 2. Compose the data template inline in HTML 32 | 33 |
    34 |
  1. 35 | 36 |

    {{from_user}}

    37 |

    {{text}}, {{created_at|date 'HH:mm on EEEE'}}

    38 |
  2. 39 |
  3. Sorry, JavaScript required!
  4. 40 |
41 | 42 | ### 3. Booyah! You're done! 43 | 44 | 45 | ## Usage 46 | 47 | You only need to include one little script: 48 | 49 | 50 | 51 | 52 | ### Data 53 | 54 | Tempo takes information encoded as JSON and renders it according to an HTML template. Below is a sample *array* of JSON data. Tempo can also iterate members of an associative array (object). 55 | 56 | var data = [ 57 | {'name':{'first':'Leonard','last':'Marx'},'nickname':'Chico','born':'March 21, 1887','actor': true,'solo_endeavours':[{'title':'Papa Romani'}]}, 58 | {'name':{'first':'Adolph','last':'Marx'},'nickname':'Harpo','born':'November 23, 1888','actor':true,'solo_endeavours':[{'title':'Too Many Kisses','rating':'favourite'},{'title':'Stage Door Canteen'}]}, 59 | {'name':{'first':'Julius Henry','last':'Marx'},'nickname':'Groucho','born': 'October 2, 1890','actor':true,'solo_endeavours':[{'title':'Copacabana'},{'title':'Mr. Music','rating':'favourite'},{'title':'Double Dynamite'}]}, 60 | {'name':{'first':'Milton','last':'Marx'},'nickname':'Gummo','born':'October 23, 1892'}, 61 | {'name':{'first':'Herbert','last':'Marx'},'nickname':'Zeppo','born':'February 25, 1901','actor':true,'solo_endeavours':[{'title':'A Kiss in the Dark'}]} 62 | ]; 63 | 64 | 65 | ### JavaScript 66 | 67 | #### Tempo.prepare() 68 | First you need to point Tempo at the container that contains the template elements: 69 | 70 | var template = Tempo.prepare(element); 71 | 72 | > #### `element` 73 | > The ID of the HTML element (or the element itself) containing your data template. If you're using jQuery, you may pass in a jQuery object instead. 74 | 75 | To initialize Tempo, run the `prepare()` function to scan an HTML container for data templates, cache them in memory, and remove the data template HTML elements from the page. Tempo.prepare(element) returns an instance of a renderer that knows how to layout the data you provide to it. 76 | 77 | If the container does not contain a default (that is without conditions and not nested) data-template the entire contents of the container will be considered to represent the template. 78 | 79 | 80 | #### template.render() 81 | 82 | template.render(data); 83 | 84 | The Tempo.prepare() function returns an instance of a template ready for rendering. Once the JSON data is available, run the render(data) function to add the data to the page. 85 | 86 | > #### `data` 87 | > The JSON data to be rendered - either an `array` or an `object` in which case the members are iterated and provided as key/value pairs. 88 | 89 | 90 | #### template.append() 91 | 92 | Renderer methods all return an instance of the renderer (a la fluent) so you can chain calls to it. The `append(data)` function will render the data you pass in and append it to the container. 93 | 94 | Tempo.prepare('marx-brothers').render( data ).append( more_brothers ); 95 | 96 | 97 | #### template.prepend() 98 | 99 | The `prepend(data)` function will render the data you pass in and insert it before others in the container. 100 | 101 | Tempo.prepare('marx-brothers').render( data ).prepend( brothers_we_didnt_know_about ); 102 | 103 | 104 | #### template.clear() 105 | 106 | The `clear()` function will empty the container, allowing you to e.g. render the data again. 107 | 108 | Tempo.prepare('marx-brothers').render( data ).clear(); 109 | 110 | #### template.errors(errorHandler) 111 | 112 | Tempo will attempt to deal with errors and failures silently but you can pass in your own handler for exceptions: 113 | 114 | > #### `errorHandler` 115 | > A function which will be called with the error object from the `try/catch` block. 116 | 117 | Tempo.prepare('list').errors(function (err) { 118 | console.log('Whoa! something happened!'); 119 | console.log(err); 120 | }).render(data); 121 | 122 | 123 | #### template.into(container) 124 | 125 | The `into(element)` function will allow you to render the original template to one or more different containers specified. The method will return a new template on which you can call the other template methods such as `render()` or `append()`. 126 | 127 | > #### `container` 128 | > The container to render the template to. 129 | 130 | ##### Render to different container: 131 | 132 | Tempo.prepare('marx-brothers').into('alternative-container').render( data ); 133 | 134 | ##### Reuse template for multiple different containers: 135 | 136 | var template = Tempo.prepare('marx-brothers'); 137 | template.into('alternative-container').render( data_1 ); 138 | template.into('yet-another-alternative-container').render( data_2 ); 139 | 140 | 141 | ### HTML 142 | 143 | #### data-template 144 | 145 | Any tag with the `data-template` attribute will be treated as a template. For compliance the full (non-minimized) form is also supported: `data-template="data-template"`. 146 | 147 | 148 | #### {{field}} 149 | 150 | Any field represented in the JSON data may be retrieved by referencing the field name inside double brackets. 151 | 152 |
    153 |
  1. {{nickname}} {{name.last}}
  2. 154 |
155 | 156 | The example above would produce the following output: 157 | 158 | 1. Chico Marx 159 | 2. Harpo Marx 160 | 3. Groucho Marx 161 | 4. Gummo Marx 162 | 5. Zeppo Marx 163 | 164 | 165 | Here's an example of a simple array of strings: 166 | 167 | var data = [ 'Leonard Marx', 'Adolph Marx', 'Julius Henry Marx', 'Milton Marx', 'Herbert Marx' ]; 168 | 169 | You can reference the object being iterated with `{{.}}`: 170 | 171 |
    172 |
  1. {{.}}
  2. 173 |
174 | 175 | If the JSON data represents an array of arrays (which can not be referenced by field/member name) for example: 176 | 177 | var data = [ ['Leonard','Marx'], ['Adolph','Marx'], ['Julius Henry','Marx'], ['Milton','Marx'], ['Herbert','Marx'] ]; 178 | 179 | You can reference array elements with the following notation: 180 | 181 |
    182 |
  1. {{[0]}} {{[1]}}
  2. 183 |
184 | 185 | Both examples would produce the following output: 186 | 187 | 1. Leonard Marx 188 | 2. Adolph Marx 189 | 3. Julius Henry Marx 190 | 4. Milton Marx 191 | 5. Herbert Marx 192 | 193 | 194 | #### Using data from associative arrays (objects) 195 | 196 | Normally data being iterated is represented as an array of objects. In some cases however the data is a series of objects in a map: 197 | 198 | var data = { 199 | 'leonard': 'Leonard Marx', 200 | 'adolph': 'Adolph Marx', 201 | 'julius': 'Julius Henry Marx', 202 | 'milton': 'Milton Marx', 203 | 'herbert': Herbert Marx' 204 | }; 205 | 206 | In this case you can iterate all the elements using the `data-from-map` attribute where the key name can be accessed with `{{key}}` and the value object via `{{value}}`: 207 | 208 |
    209 |
  1. {{value}} - {{key | append '@marx.com'}}
  2. 210 |
211 | 212 | 213 | #### Values are escaped by default 214 | 215 | All values are escaped by default. To disable automatic escaping pass in the 'escape': false parameter: 216 | 217 | Tempo.prepare('marx-brothers', {'escape': false}).render(data); 218 | 219 | If you disable escaping you can control this at individual value level using the `escape` and `encodeURI` filters. 220 | 221 | 222 | #### Nested data-templates 223 | 224 | Data templates can even be nested within other data templates. Multiple nested templates are supported. 225 | 226 |
  • 227 | {{nickname}} {{name.last}} 228 | 231 |
  • 232 | 233 | The example above would produce the following output: 234 | 235 | 1. Chico Marx 236 | ◦ Papa Romani 237 | 2. Harpo Marx 238 | ◦ Too Many Kisses 239 | ◦ Stage Door Canteen 240 | 3. Groucho Marx 241 | ◦ Copacabana 242 | ◦ Mr. Music 243 | ◦ Double Dynamite 244 | 4. Gummo Marx 245 | 5. Zeppo Marx 246 | ◦ A Kiss in the Dark 247 | 248 | You can (recursively) refer to parent objects within a nested template using the `_parent` variable. 249 | 250 |
  • {{_parent.name.first}} acted in {{title}}
  • 251 | 252 | 253 | #### Nested Templates as Partial Template Files 254 | 255 | Tempo supports separating more complex nested templates in to master and partial template files. Partials templates are loaded on demand from the server and do require you to use the *alternative asynchronous pattern*: 256 | 257 | ##### JavaScript: 258 | 259 | Tempo.prepare('marx-brothers', {}, function(template) { 260 | template.render(data); 261 | }); 262 | 263 | ##### Template: 264 | 265 |
  • 266 | {{name.first}} {{name.last}} 267 |
      268 |
    1. 269 |
    270 |
  • 271 | 272 | ##### Partial ('partials/movie.html'): 273 | 274 | {{title}} 275 | 276 | This would produce the same output as the example above: 277 | 278 | 1. Chico Marx 279 | ◦ Papa Romani 280 | 2. Harpo Marx 281 | ◦ Too Many Kisses 282 | ◦ Stage Door Canteen 283 | 3. Groucho Marx 284 | ◦ Copacabana 285 | ◦ Mr. Music 286 | ◦ Double Dynamite 287 | 4. Gummo Marx 288 | 5. Zeppo Marx 289 | ◦ A Kiss in the Dark 290 | 291 | 292 | #### Conditional Templates 293 | 294 | Tempo provides boolean and value-based conditionals, as well as the ability to define multiple data templates per container (the first matching template wins). 295 | 296 | 303 | 304 | This example would produce the following output: 305 | 306 | • Leonard, nicknamed 'Chico Marx' was born on March 21, 1887 307 | • Adolph, nicknamed 'Harpo Marx' was born on November 23, 1888 308 | • Groucho (aka Julius Henry) was grumpy! 309 | • Milton Marx was not in any movies! 310 | • Herbert, nicknamed 'Zeppo Marx' was born on February 25, 1901 311 | 312 | You can define templates based on data member existence as well: 313 | 314 |
  • {{friend}}>
  • 315 | 316 | 317 | #### Fallback Template 318 | 319 | Use the data-template-fallback attribute to identify HTML containers you would like to show if JavaScript is disabled. 320 | 321 | To ensure that your data template is not visible before being rendered (either because of JavaScript being disabled or due to latency retrieving the data), it's best practice to hide your templates with CSS. If you add an inline rule of `style="display: none;"`. Tempo will remove the inline rule once the data has been rendered. 322 | 323 | 327 | 328 | If JavaScript is disabled in the browser the above example would be rendered as: 329 | 330 | • Sorry, JavaScript required! 331 | 332 | 333 | #### Preserving other elements in the template container 334 | 335 | If the template container contains other elements that should be preserved or ignored you can mark these with the `data-before-template` and `data-after-template` attributes: 336 | 337 |
      338 |
    1. ...
    2. 339 |
    3. {{name.first}} {{name.last}}
    4. 340 |
    5. ...
    6. 341 |
    342 | 343 | 344 | #### Filter and Formatting Functions 345 | 346 | Tempo supports a number of filter functions to modify individual values before they are rendered. Filters can be chained to apply multiple ones to the same value. 347 | 348 | ##### `{{ field | escape }}` 349 | Escape HTML characters before rendering to page. 350 | 351 | ##### `{{ field | encodeURI }}` 352 | Encodes a Uniform Resource Identifier (URI) by replacing certain characters with escape sequences representing the UTF-8 encoding of the character. 353 | 354 | ##### `{{ field | decodeURI }}` 355 | Replaces each escape sequence in an encoded URI with the character that it represents. 356 | 357 | ##### `{{ field | truncate 25[, 'optional_suffix'] }}` 358 | If the value of this field is longer than 25 characters, then truncate to the length provided. If a second argument is provided then it is used as the suffix instead of the default ellipsis (...). 359 | 360 | ##### `{{ field | format[, numberOfDecimals] }}` 361 | Currently only formats numbers like 100000.25 by adding commas: 100,000.25. You can optionally specify the number of decimals to use. 362 | 363 | ##### `{{ field | default 'default value' }}` 364 | If the field is `undefined`, or `null`, then show the default value specified. 365 | 366 | ##### `{{ field | date 'preset or pattern like HH:mm, DD-MM-YYYY'[, 'UTC'] }}` 367 | Creates a date object from the field value, and formats according to presets, or using a date pattern. Available presets are localedate, localetime, date and time. The following pattern elements are supported: 368 | 369 | * `YYYY` or `YY`: year as 4 or 2 digits. 370 | * `MMMM`, `MMM`, `MM` or `M`: month of the year as either full name (September), abbreviated (Sep), padded two digit number or simple integer. 371 | * `DD` or `D`: day of the month. 372 | * `EEEE`, `EEE` or `E`: day of the week as either full (Tuesday), abbreviated (Tue) or number. 373 | * `HH`, `H` or `h`: hour of the day. 374 | * `mm` or `m`: minutes of the hour. 375 | * `ss` or `s`: seconds. 376 | * `SSS` or `S`: milliseconds. 377 | * `a`: AM/PM. 378 | 379 | If you would like to use any of the format characters verbatim in the pattern then use `\` to escape: `{{ some_date | date '\at HH:mm' }}`. In this case the a is not replaced with AM/PM. Additionally you can specify whether dates should be handled as UTC. 380 | 381 | ##### `{{ field | replace 'regex_pattern', 'replacement' }}` 382 | Replace all occurrences of the pattern (supports regular expressions) with the replacement. Replacement string can contain backreferences. See [Twitter code sample](http://tempojs.com/examples/twitter/) for an example. 383 | 384 | ##### `{{ field | trim }}` 385 | Trim any white space at the beginning or end of the value. 386 | 387 | ##### `{{ field | upper }}` 388 | Change any lower case characters in the value to upper case. 389 | 390 | ##### `{{ field | lower }}` 391 | Change any upper case characters in the value to lower case. 392 | 393 | ##### `{{ field | titlecase[, 'and for the'] }}` 394 | Format strings to title case, with an optional blacklist of words that should not be titled (unless first in string e.g. 'the last of the mohicans' to 'The Last of the Mohicans'). 395 | 396 | ##### `{{ field | append ' some suffix' }}` 397 | If the field value is not empty, then append the string to the value. Helpful if you don't want the suffix to show up in the template if the field is `undefined` or `null`. Use ` ` if you need before or after the suffix. 398 | 399 | ##### `{{ field | prepend 'some prefix ' }}` 400 | If the field value is not empty, then prepend the string to the value. Helpful if you don't want the prefix to show up in the template if the field is `undefined` or `null`. Use ` ` if you need before or after the prefix. 401 | 402 | ##### `{{ field | join 'separator' }}` 403 | If the field is an array joins the elements into a string using the supplied separator. 404 | 405 | 406 | #### Attribute setters 407 | 408 | If an HTML element attribute in a template is immediately followed by a second attribute with the same name, prefixed with `data-`, then as long as the second one is not empty will the value of the original be replaced with the value of the latter. 409 | 410 | In the following example, will the reference to `default_image.png` be replaced by an actual field value if one exists. Notice here that the `.png` suffix is only added if the field is not empty. 411 | 412 | 413 | 414 | 415 | #### Template tags 416 | 417 | Tempo also supports tag blocks: 418 | 419 | {% if javascript-expression %} ... {% else %} ... {% endif %} 420 | 421 | The body of the tag will only be rendered if the JavaScript expression evaluates to true. The `{% else %}` is optional. 422 | 423 | #### Variable injection for inline scripts 424 | 425 | If you're using scripts inline in your template, you can access JSON object members. You reference these via the `__` variable. 426 | 427 | {{name.last}} 428 | 429 | Similarly you can reference array elements (shorthand for for `__.this[0]`): 430 | 431 | {{[0]}} 432 | 433 | You can refer to the object being iterated with `__.this`. You can use normal dot notation to access members of that object: 434 | 435 | {{.}} 436 | 437 | At render time the accessor variable will be replaced by the object it references. If the object is a string then its value will be wrapped in single quotes, otherwise type is preserved. 438 | 439 | 440 | ### Pre-processing and Event Handling 441 | 442 | #### template.when(type, handler) 443 | 444 | After preparing a template you can register one or more event listeners by providing a callback function to be notified of events in the lifecycle. 445 | 446 | > ##### `type` 447 | > The type of the event. Constant values are defined in TempoEvent.Types. 448 | 449 | > * `TempoEvent.Types.RENDER_STARTING`: Indicates that rendering has started, or been manually triggered by calling starting() on the renderer object. 450 | > * `TempoEvent.Types.ITEM_RENDER_STARTING`: Indicates that the rendering of a given individual item is starting. 451 | > * `TempoEvent.Types.ITEM_RENDER_COMPLETE`: Indicates that the rendering of a given individual item has completed. 452 | > * `TempoEvent.Types.RENDER_COMPLETE`: Indicates that the rendering of all items is completed. 453 | > * `TempoEvent.Types.BEFORE_CLEAR`: Fires before the container is cleared of all elements. 454 | > * `TempoEvent.Types.AFTER_CLEAR`: Fires after the container is cleared of all elements. 455 | 456 | > ##### `handler` 457 | > The handler function to call when the specified event fires. 458 | 459 | The event listener will be called with a single argument, a `TempoEvent` which has the following properties: 460 | 461 | > ##### `type` 462 | > The type of the event. Constant values are defined in `TempoEvent.Types`. 463 | 464 | > ##### `item` 465 | > The item being rendered. This is only available for the `ITEM_RENDER_STARTING` and `ITEM_RENDER_COMPLETE` events. 466 | 467 | > ##### `element` 468 | > The HTML element or template being used for rendering. This is only available for the `ITEM_RENDER_STARTING` and `ITEM_RENDER_COMPLETE` events. 469 | 470 | Tempo.prepare('tweets').when(TempoEvent.Types.RENDER_STARTING, function (event) { 471 | $('#tweets').addClass('loading'); 472 | }).when(TempoEvent.Types.RENDER_COMPLETE, function (event) { 473 | $('#tweets').removeClass('loading'); 474 | }).render(data); 475 | 476 | Even though it's possible to modify the DOM via event handlers it's worth keeping in mind that Tempo is built on the premise of keeping the templates as semantic and readable as possible. We would therefore advise that you limit the use or pre-processing to data cleansing and restructuring as opposed to template modifications. 477 | 478 | #### template.starting() 479 | 480 | In some cases you prepare the templates ahead of calling render. In those cases if you would like to e.g. set loader graphics, call the renderer's `starting()` method just prior to issuing e.g. a jQuery request. This will fire the `ITEM_RENDER_STARTING` event. 481 | 482 | The following example demonstrates use of both methods above. In this case we prepare the templates, and register our event handler function. The event handler is then notified that the jQuery request is about to be issued (when we manually fire`RENDER_STARTING` with a call to `starting()`) adding a CSS class to the container. We are then notified that rendering is complete so we can remove the CSS class. 483 | 484 | var template = Tempo.prepare('tweets').when(TempoEvent.Types.RENDER_STARTING, function (event) { 485 | $('#tweets').addClass('loading'); 486 | }).when(TempoEvent.Types.RENDER_COMPLETE, function (event) { 487 | $('#tweets').removeClass('loading'); 488 | }); 489 | template.starting(); 490 | $.getJSON(url, function(data) { 491 | template.render(data.results); 492 | }); 493 | 494 | 495 | ### Using Tempo with jQuery 496 | 497 | jQuery's [`getJSON()`](http://api.jquery.com/jQuery.getJSON/) method provides an easy means of loading JSON-encoded data from the server using a `GET HTTP` request. 498 | 499 | var template = Tempo.prepare('tweets'); 500 | $.getJSON("http://search.twitter.com/search.json?q='marx brothers'&callback=?", function(data) { 501 | template.render(data.results); 502 | }); 503 | 504 | #### Binding event handlers 505 | Note that if you've bound event listeners to elements in your template, these will not be cloned when the template is used. In order to do this you have two options. 506 | You can either leave binding until the data has been rendered, e.g. by registering a `TempoEvent.Types.RENDER_COMPLETE` listener and doing the binding when that fires, or you can use jQuery [`live()`](http://api.jquery.com/live/) which attaches a handler to elements even though they haven't been created. 507 | 508 | The following example shows how to make a template clickable (assuming an `'ol li'` selector provides a reference to a template element) and how to obtain a reference to the clicked element: 509 | 510 | $('ol li').live('click', function() { 511 | // Do something with the clicked element 512 | alert(this); 513 | }); 514 | 515 | 516 | ### Advanced Topics 517 | 518 | #### Configuring the surrounding brace syntax 519 | 520 | In order to make it easier to use Tempo with other frameworks such as Django, you can configure Tempo to use surrounding braces other than the default `{{ ... }}` and `% ... %}`. 521 | 522 | To do this you pass the var_braces and tag_braces parameters to the `Tempo.prepare` function. These will be split down the middle to form the left and right braces. 523 | 524 | Tempo.prepare('marx-brothers', {'var_braces' : '\\[\\[\\]\\]', 'tag_braces' : '\\[\\?\\?\\]'}); 525 | 526 | You can now use this template syntax instead: 527 | 528 |
      529 |
    1. [[nickname]] is [? if nickname == 'Groucho' ?]grouchy[? else ?]happy[? endif ?]!
    2. 530 |
    531 | 532 | #### Tempo Renderer Information - the `_tempo` variable 533 | 534 | You can access information about the rendering process via the `_tempo` variable. 535 | 536 | > ##### `_tempo.index` 537 | > The `index` member tells you how many iterations of a given template have been carried out. This is a zero (0) based index. Nested templates have a separate counter. 538 | 539 | The following example adds the iteration count to the class attribute, prefixed with 'item-': 540 | 541 |
      542 |
    1. {{nickname}} {{name.last}}
    2. 543 |
    544 | 545 | This example shows how to access the iteration counter using inline JavaScript injection: 546 | 547 | a href="#" onclick="alert(__._tempo.index); return false;">{{name.last}} 548 | 549 | > ##### `_tempo.first` 550 | 551 | True if the item being iterated is the first one in the collection. 552 | 553 | > ##### `_tempo.last` 554 | 555 | True if the item being iterated is the last one in the collection. 556 | 557 | -------------------------------------------------------------------------------- /css/tempo.css: -------------------------------------------------------------------------------- 1 | .column { 2 | margin: 0 1.04%; 3 | overflow: hidden; 4 | float: left; 5 | display: inline; 6 | } 7 | .full-column { 8 | width: 102.25%; 9 | margin: 0 -1.1%; 10 | } 11 | .row { 12 | width: 97.875%; 13 | margin: 0 1.064%; 14 | overflow-x: auto; 15 | overflow-y: visible; 16 | } 17 | .nested-row { 18 | width: 97.875%; 19 | margin: 0 1.064%; 20 | overflow-x: auto; 21 | overflow-y: visible; 22 | min-width: 0; 23 | width: auto; 24 | display: inline-block; 25 | } 26 | * { 27 | margin: 0; 28 | padding: 0; 29 | } 30 | a img, form, fieldset { 31 | border: none; 32 | } 33 | p { 34 | margin-bottom: 20px; 35 | } 36 | nav, 37 | aside, 38 | header, 39 | article, 40 | section, 41 | footer { 42 | display: block; 43 | } 44 | body { 45 | font-family: Helvetica, Arial sans-serif; 46 | font-size: 1em; 47 | -webkit-text-size-adjust: 200%; 48 | } 49 | h1, h2, h3 { 50 | font-family: Georgia; 51 | margin: 1.5em 0 0.5em 0; 52 | } 53 | pre, code { 54 | font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; 55 | } 56 | pre, .example { 57 | background-color: #e5e5e5; 58 | padding: 1em; 59 | margin-bottom: 1.5em; 60 | overflow-x: auto; 61 | } 62 | pre em, .example em { 63 | font-style: normal; 64 | font-weight: bold; 65 | } 66 | pre span, .example span { 67 | color: #999; 68 | } 69 | pre ol, 70 | .example ol, 71 | pre ul, 72 | .example ul { 73 | margin-left: 1.5em; 74 | } 75 | pre a { 76 | text-decoration: none; 77 | } 78 | nav ul { 79 | overflow: hidden; 80 | } 81 | nav ul li { 82 | list-style: none outside none; 83 | float: left; 84 | margin-right: 0.5em; 85 | } 86 | nav ul li a { 87 | display: block; 88 | padding: 0.5em; 89 | } 90 | nav ul li a:hover { 91 | background-color: #e0e0e0; 92 | } 93 | nav ul li a:active { 94 | background-color: #ccc; 95 | } 96 | nav ul li.download a { 97 | color: white; 98 | background-color: blue; 99 | } 100 | nav ul li.download a:hover { 101 | background-color: darkblue; 102 | } 103 | nav ul li.download a:active { 104 | background-color: black; 105 | } 106 | div#top, footer#bottom { 107 | background-color: black; 108 | color: #fff; 109 | } 110 | div#top#bottom, footer#bottom#bottom { 111 | margin-top: 2em; 112 | padding: 2em 0; 113 | } 114 | div#top section, footer#bottom section { 115 | width: 97.875%; 116 | margin: 0 1.064%; 117 | overflow-x: auto; 118 | overflow-y: visible; 119 | } 120 | div#top section p, footer#bottom section p { 121 | margin: 0 1.04%; 122 | overflow: hidden; 123 | float: left; 124 | display: inline; 125 | width: 97.917%; 126 | line-height: 2em; 127 | } 128 | div#top section p a, footer#bottom section p a { 129 | color: white; 130 | } 131 | div#top#top, footer#bottom#top { 132 | padding: 0.5em 0; 133 | } 134 | header#overview { 135 | width: 97.875%; 136 | margin: 0 1.064%; 137 | overflow-x: auto; 138 | overflow-y: visible; 139 | } 140 | header#overview section#hero { 141 | margin: 0 1.04%; 142 | overflow: hidden; 143 | float: left; 144 | display: inline; 145 | width: 47.85%; 146 | } 147 | header#overview section#hero h1 { 148 | margin: 1em 0 0.25em 0; 149 | font-size: 4em; 150 | } 151 | header#overview section#hero p { 152 | font-family: Georgia; 153 | font-size: 1.5em; 154 | } 155 | header#overview section#hero nav#actions ul { 156 | margin: 2em 0; 157 | } 158 | header#overview section#hero nav#actions ul li a { 159 | padding: 1em; 160 | } 161 | header#overview section#hero ul#features li, header#overview section#hero ul#why li { 162 | list-style: circle outside none; 163 | margin: 0.75em 0 0.75em 2em; 164 | padding: 0 0 0 0.5em; 165 | } 166 | header#overview aside#example { 167 | margin: 0 1.04%; 168 | overflow: hidden; 169 | float: left; 170 | display: inline; 171 | width: 47.85%; 172 | padding-top: 2.5em; 173 | } 174 | header#overview aside#example h3 { 175 | font-size: 1.5em; 176 | font-weight: normal; 177 | } 178 | @media screen and (max-width: 900px) { 179 | header#overview section#hero { 180 | width: 97.917%; 181 | } 182 | header#overview aside#example { 183 | width: 97.917%; 184 | padding-top: 0; 185 | } 186 | } 187 | @media screen and (max-device-width: 480px) { 188 | header#overview section#hero { 189 | width: 97.917%; 190 | } 191 | header#overview aside#example { 192 | width: 97.917%; 193 | padding-top: 0; 194 | } 195 | } 196 | aside#quotes { 197 | margin: 2em 0 0 0; 198 | padding: 0.5em 0; 199 | border-top: 1px solid #ccc; 200 | border-bottom: 1px solid #ccc; 201 | } 202 | aside#quotes div { 203 | background-color: #f2f2f2; 204 | } 205 | aside#quotes div section { 206 | width: 97.875%; 207 | margin: 0 1.064%; 208 | overflow-x: auto; 209 | overflow-y: visible; 210 | padding: 2.5em 0; 211 | } 212 | aside#quotes div section blockquote { 213 | margin: 0 1.04%; 214 | overflow: hidden; 215 | float: left; 216 | display: inline; 217 | width: 97.917%; 218 | text-align: center; 219 | font-family: Georgia; 220 | font-size: 1.75em; 221 | color: #666; 222 | } 223 | aside#quotes div section blockquote p { 224 | margin: 0; 225 | } 226 | aside#quotes div section blockquote p span, aside#quotes div section blockquote p a { 227 | font-size: 0.75em; 228 | font-family: Helvetica; 229 | font-weight: 300; 230 | color: #999; 231 | } 232 | aside#quotes div section blockquote p span { 233 | display: block; 234 | margin-top: 0.5em; 235 | } 236 | article#usage { 237 | margin-top: 2em; 238 | border-top: 5px solid black; 239 | } 240 | article#usage header { 241 | overflow: hidden; 242 | background-color: #F0F0F0; 243 | } 244 | article#usage header div { 245 | width: 97.875%; 246 | margin: 0 1.064%; 247 | overflow-x: auto; 248 | overflow-y: visible; 249 | } 250 | article#usage header div section { 251 | margin: 0 1.04%; 252 | overflow: hidden; 253 | float: left; 254 | display: inline; 255 | width: 97.917%; 256 | } 257 | article#usage header div section h2 { 258 | font-size: 2.75em; 259 | font-weight: normal; 260 | float: left; 261 | margin: 0; 262 | padding: 0.66em 0; 263 | } 264 | article#usage header div section nav { 265 | float: left; 266 | margin: 2.66em 0 0 2em; 267 | } 268 | article#usage div.usage { 269 | width: 97.875%; 270 | margin: 0 1.064%; 271 | overflow-x: auto; 272 | overflow-y: visible; 273 | } 274 | article#usage div.usage section.usage { 275 | margin: 0 1.04%; 276 | overflow: hidden; 277 | float: left; 278 | display: inline; 279 | width: 81.25%; 280 | } 281 | article#usage div.usage section.usage h3 { 282 | clear: both; 283 | font-size: 1.5em; 284 | font-weight: normal; 285 | border-bottom: 1px solid #ccc; 286 | margin-top: 2em; 287 | } 288 | article#usage div.usage section.usage h5 { 289 | margin-left: 2em; 290 | margin-top: -1em; 291 | font-size: 0.9em; 292 | } 293 | article#usage div.usage section.usage .h5 { 294 | margin-left: 2em; 295 | margin-bottom: 2em; 296 | font-size: 0.9em; 297 | } 298 | article#usage div.usage section.usage ul.h5 { 299 | margin-top: -1em; 300 | margin-left: 5em; 301 | } 302 | article#usage div.usage aside#advert { 303 | margin: 0 1.04%; 304 | overflow: hidden; 305 | float: left; 306 | display: inline; 307 | width: 14.583%; 308 | } 309 | article#usage div.usage aside#advert .ad { 310 | margin: 4.7em 0 0 0; 311 | float: right; 312 | } 313 | @media screen and (max-width: 900px) { 314 | article#usage div.usage section.usage { 315 | width: 72.73%; 316 | } 317 | article#usage div.usage aside#advert { 318 | width: 22.916%; 319 | } 320 | } 321 | @media screen and (max-width: 700px) { 322 | article#usage header div section nav { 323 | display: none; 324 | } 325 | article#usage div.usage section.usage { 326 | width: 97.917%; 327 | } 328 | article#usage div.usage aside#advert { 329 | display: none; 330 | } 331 | } 332 | @media screen and (max-device-width: 480px) { 333 | article#usage header div section nav { 334 | display: none; 335 | } 336 | article#usage div.usage section.usage { 337 | width: 97.917%; 338 | } 339 | article#usage div.usage aside#advert { 340 | display: none; 341 | } 342 | } 343 | nav#actions2 { 344 | width: 97.875%; 345 | margin: 0 1.064%; 346 | overflow-x: auto; 347 | overflow-y: visible; 348 | text-align: center; 349 | } 350 | nav#actions2 ul { 351 | margin: 2em 0; 352 | display: inline-block; 353 | } 354 | nav#actions2 ul li a { 355 | padding: 1em; 356 | } 357 | body .adpacks { 358 | background: #fff; 359 | padding: 15px; 360 | margin: 15px 0 0; 361 | border: 3px solid #eee; 362 | } 363 | body .one .bsa_it_ad { 364 | background: transparent; 365 | border: none; 366 | font-family: inherit; 367 | padding: 0 15px 0 10px; 368 | width: 130px; 369 | text-align: left; 370 | } 371 | body .one .bsa_it_ad .bsa_it_i { 372 | display: block; 373 | padding: 0; 374 | float: none; 375 | margin: 0 0 5px; 376 | } 377 | body .one .bsa_it_ad .bsa_it_i img { 378 | padding: 0; 379 | border: none; 380 | } 381 | body .one .bsa_it_ad .bsa_it_t { 382 | padding: 6px 0 0 0; 383 | } 384 | body .one .bsa_it_p { 385 | display: none; 386 | } 387 | body #bsap_aplink, body #bsap_aplink:hover { 388 | display: block; 389 | font-size: 10px; 390 | margin: 8px 12px 0; 391 | text-align: left; 392 | } 393 | -------------------------------------------------------------------------------- /css/tempo.less: -------------------------------------------------------------------------------- 1 | ////////// 2 | // GRID // 3 | ////////// 4 | 5 | @1g: 08.333%; //60 6 | @2g: 14.583%; //140 7 | @3g: 22.916%; //220 8 | @4g: 31.250%; //300 9 | @5g: 39.583%; //380 10 | @6g: 47.850%; //460 11 | @7g: 56.250%; //540 12 | @8g: 64.585%; //620 13 | @9g: 72.730%; //700 14 | @10g: 81.250%; //780 15 | @11g: 89.583%; //860 16 | @12g: 97.917%; //940 17 | 18 | .column { 19 | margin: 0 1.04%; 20 | overflow: hidden; 21 | float: left; 22 | display: inline; 23 | } 24 | .full-column { 25 | width: 102.25%; 26 | margin: 0 -1.1%; 27 | } 28 | .row { 29 | width: 97.875%; 30 | margin: 0 1.064%; 31 | overflow-x: auto; 32 | overflow-y: visible; 33 | } 34 | .nested-row { 35 | .row; 36 | min-width: 0; 37 | width: auto; 38 | display: inline-block; 39 | } 40 | 41 | 42 | /////////// 43 | // RESET // 44 | /////////// 45 | 46 | * { margin: 0; padding: 0; } 47 | a img, form, fieldset { border: none; } 48 | p { margin-bottom: 20px; } 49 | nav, aside, header, article, section, footer { display: block; } 50 | 51 | 52 | ////////// 53 | // TYPE // 54 | ////////// 55 | 56 | body { 57 | font-family: Helvetica, Arial sans-serif; 58 | font-size: 1em; 59 | -webkit-text-size-adjust: 200%; 60 | } 61 | h1, h2, h3 { 62 | font-family: Georgia; 63 | margin: 1.5em 0 0.5em 0; 64 | } 65 | pre, 66 | code { 67 | font-family: Consolas, 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; 68 | } 69 | 70 | 71 | 72 | ////////// 73 | // CODE // 74 | ////////// 75 | 76 | pre, 77 | .example { 78 | background-color: #e5e5e5; 79 | padding: 1em; 80 | margin-bottom: 1.5em; 81 | overflow-x: auto; 82 | 83 | em { 84 | font-style: normal; 85 | font-weight: bold; 86 | } 87 | span { 88 | color: #999; 89 | } 90 | 91 | ol, 92 | ul { 93 | margin-left: 1.5em; 94 | } 95 | } 96 | 97 | 98 | ///////// 99 | // NAV // 100 | ///////// 101 | 102 | nav ul { 103 | overflow: hidden; 104 | 105 | li { 106 | list-style: none outside none; 107 | float: left; 108 | margin-right: 0.5em; 109 | 110 | a { 111 | display: block; 112 | padding: 0.5em; 113 | 114 | &:hover { 115 | background-color: #e0e0e0; 116 | } 117 | &:active { 118 | background-color: #ccc; 119 | } 120 | } 121 | 122 | &.download { 123 | a { 124 | color: white; 125 | background-color: blue; 126 | 127 | &:hover { 128 | background-color: darkblue; 129 | } 130 | &:active { 131 | background-color: black; 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | 139 | ///////// 140 | // TOP // 141 | ///////// 142 | 143 | div#top, 144 | footer#bottom { 145 | background-color: black; 146 | color: #fff; 147 | 148 | &#bottom { 149 | margin-top: 2em; 150 | padding: 2em 0; 151 | } 152 | 153 | section { 154 | .row; 155 | 156 | p { 157 | .column; 158 | width: @12g; 159 | line-height: 2em; 160 | 161 | a { 162 | color: white; 163 | } 164 | } 165 | } 166 | &#top { 167 | padding: 0.5em 0; 168 | } 169 | } 170 | 171 | 172 | //////////// 173 | // HEADER // 174 | //////////// 175 | 176 | header#overview { 177 | .row; 178 | 179 | section#hero { 180 | .column; 181 | width: @6g; 182 | 183 | h1 { 184 | margin: 1em 0 0.25em 0; 185 | font-size: 4em; 186 | } 187 | p { 188 | font-family: Georgia; 189 | font-size: 1.5em; 190 | } 191 | nav#actions ul { 192 | margin: 2em 0; 193 | 194 | li { 195 | a { 196 | padding: 1em; 197 | } 198 | } 199 | } 200 | ul#features, 201 | ul#why { 202 | li { 203 | list-style: circle outside none; 204 | margin: 0.75em 0 0.75em 2em; 205 | padding: 0 0 0 0.5em; 206 | } 207 | } 208 | } 209 | aside#example { 210 | .column; 211 | width: @6g; 212 | 213 | padding-top: 2.5em; 214 | 215 | h3 { 216 | font-size: 1.5em; 217 | font-weight: normal; 218 | } 219 | } 220 | } 221 | @media screen and (max-width: 900px) { 222 | header#overview section#hero { 223 | width: @12g; 224 | } 225 | header#overview aside#example { 226 | width: @12g; 227 | padding-top: 0; 228 | } 229 | } 230 | @media screen and (max-device-width: 480px) { 231 | header#overview section#hero { 232 | width: @12g; 233 | } 234 | header#overview aside#example { 235 | width: @12g; 236 | padding-top: 0; 237 | } 238 | } 239 | 240 | 241 | //////////// 242 | // QUOTES // 243 | //////////// 244 | 245 | aside#quotes { 246 | margin: 2em 0 0 0; 247 | padding: 0.5em 0; 248 | border-top: 1px solid #ccc; 249 | border-bottom: 1px solid #ccc; 250 | 251 | div { 252 | background-color: #f2f2f2; 253 | 254 | section { 255 | .row; 256 | padding: 2.5em 0; 257 | 258 | blockquote { 259 | .column; 260 | width: @12g; 261 | text-align: center; 262 | font-family: Georgia; 263 | font-size: 1.75em; 264 | color: #666; 265 | 266 | p { 267 | margin: 0; 268 | 269 | span, a { 270 | font-size: 0.75em; 271 | font-family: Helvetica; 272 | font-weight: 300; 273 | color: #999; 274 | } 275 | span { 276 | display: block; 277 | margin-top: 0.5em; 278 | } 279 | } 280 | } 281 | } 282 | } 283 | } 284 | 285 | 286 | /////////// 287 | // USAGE // 288 | /////////// 289 | 290 | article#usage { 291 | margin-top: 2em; 292 | border-top: 5px solid black; 293 | 294 | header { 295 | overflow: hidden; 296 | background-color: #F0F0F0; 297 | 298 | div { 299 | .row; 300 | 301 | section { 302 | .column; 303 | width: @12g; 304 | 305 | h2 { 306 | font-size: 2.75em; 307 | font-weight: normal; 308 | float: left; 309 | margin: 0; 310 | padding: 0.66em 0; 311 | } 312 | nav { 313 | float: left; 314 | margin: 2.66em 0 0 2em; 315 | } 316 | } 317 | } 318 | } 319 | 320 | div.usage { 321 | .row; 322 | 323 | section.usage { 324 | .column; 325 | width: @10g; 326 | 327 | h3 { 328 | clear: both; 329 | font-size: 1.5em; 330 | font-weight: normal; 331 | border-bottom: 1px solid #ccc; 332 | margin-top: 2em; 333 | } 334 | h5 { 335 | margin-left: 2em; 336 | margin-top: -1em; 337 | font-size: 0.9em; 338 | } 339 | .h5 { 340 | margin-left: 2em; 341 | margin-bottom: 2em; 342 | font-size: 0.9em; 343 | } 344 | ul.h5 { 345 | margin-top: -1em; 346 | margin-left: 5em; 347 | } 348 | } 349 | aside#advert { 350 | .column; 351 | width: @2g; 352 | 353 | .ad { 354 | margin:4.7em 0 0 0; 355 | float: right; 356 | } 357 | } 358 | } 359 | } 360 | @media screen and (max-width: 900px) { 361 | article#usage div.usage section.usage { 362 | width: @9g; 363 | } 364 | article#usage div.usage aside#advert { 365 | width: @3g; 366 | } 367 | } 368 | @media screen and (max-width: 700px) { 369 | article#usage header div section nav { 370 | display: none; 371 | } 372 | article#usage div.usage section.usage { 373 | width: @12g; 374 | } 375 | article#usage div.usage aside#advert { 376 | display: none; 377 | } 378 | } 379 | @media screen and (max-device-width: 480px) { 380 | article#usage header div section nav { 381 | display: none; 382 | } 383 | article#usage div.usage section.usage { 384 | width: @12g; 385 | } 386 | article#usage div.usage aside#advert { 387 | display: none; 388 | } 389 | } 390 | 391 | 392 | ////////////// 393 | //BOTTOM NAV// 394 | ////////////// 395 | 396 | nav#actions2 { 397 | .row; 398 | text-align: center; 399 | 400 | ul { 401 | margin: 2em 0; 402 | display: inline-block; 403 | 404 | li a { 405 | padding: 1em; 406 | } 407 | } 408 | } 409 | 410 | 411 | ///////////// 412 | // AD PACK // 413 | ///////////// 414 | 415 | body .adpacks{background:#fff;padding:15px;margin:15px 0 0;border:3px solid #eee;} 416 | body .one .bsa_it_ad{background:transparent;border:none;font-family:inherit;padding:0 15px 0 10px;width: 130px;text-align:left;} 417 | body .one .bsa_it_ad .bsa_it_i{display:block;padding:0;float:none;margin:0 0 5px;} 418 | body .one .bsa_it_ad .bsa_it_i img{padding:0;border:none;} 419 | body .one .bsa_it_ad .bsa_it_t{padding:6px 0 0 0;} 420 | body .one .bsa_it_p{display:none;} 421 | body #bsap_aplink,body #bsap_aplink:hover{display:block;font-size:10px;margin:8px 12px 0;text-align:left;} -------------------------------------------------------------------------------- /examples/append-prepend-elements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
    IDNicknameBorn
    -1
    0
    {{id}}{{nickname}}{{born}}
    6
    7
    -------------------------------------------------------------------------------- /examples/arrays.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 19 | 20 | 21 |

    Hello

    22 | 25 | 26 | -------------------------------------------------------------------------------- /examples/attributes-non-nested.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/attributes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/css/examples.css: -------------------------------------------------------------------------------- 1 | /* universal footer */ 2 | ul#tempo-actions { 3 | text-align: center; 4 | background-color: #f0f0f0; 5 | margin: 3em 0 0 0; 6 | padding: 3em; 7 | } 8 | ul#tempo-actions li { 9 | display: inline-block; 10 | margin-right: 0.5em; 11 | } 12 | ul#tempo-actions li a { 13 | display: block; 14 | padding: 1em; 15 | } 16 | ul#tempo-actions li:hover { 17 | background-color: #e0e0e0; 18 | } 19 | ul#tempo-actions li:active { 20 | background-color: #ccc; 21 | } 22 | ul#tempo-actions li.download a { 23 | color: white; 24 | background-color: blue; 25 | } 26 | ul#tempo-actions li.download a:hover { 27 | background-color: darkblue; 28 | } 29 | ul#tempo-actions li.download a:active { 30 | background-color: black; 31 | } 32 | #footer { 33 | background-color: black; 34 | color: white; 35 | padding: 2em 0px; 36 | } 37 | #footer a { 38 | color: white; 39 | } -------------------------------------------------------------------------------- /examples/css/grid.css: -------------------------------------------------------------------------------- 1 | /* ================ */ 2 | /* = The 1Kb Grid = */ /* 12 columns, 60 pixels each, with 20 pixel gutter */ 3 | /* ================ */ 4 | 5 | .grid_1 { width:60px; } 6 | .grid_2 { width:140px; } 7 | .grid_3 { width:220px; } 8 | .grid_4 { width:300px; } 9 | .grid_5 { width:380px; } 10 | .grid_6 { width:460px; } 11 | .grid_7 { width:540px; } 12 | .grid_8 { width:620px; } 13 | .grid_9 { width:700px; } 14 | .grid_10 { width:780px; } 15 | .grid_11 { width:860px; } 16 | .grid_12 { width:940px; } 17 | 18 | .column { 19 | margin: 0 10px; 20 | overflow: hidden; 21 | float: left; 22 | display: inline; 23 | } 24 | .row { 25 | width: 960px; 26 | margin: 0 auto; 27 | overflow: hidden; 28 | } 29 | .row .row { 30 | margin: 0 -10px; 31 | width: auto; 32 | display: inline-block; 33 | } -------------------------------------------------------------------------------- /examples/data-from-map.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 |
      19 |
    1. {{value}} - {{key | append '@marx.com'}}
    2. 20 |
    -------------------------------------------------------------------------------- /examples/dataifhas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 21 | 22 | 23 |

    Hello

    24 | 30 | 31 | -------------------------------------------------------------------------------- /examples/error-handling.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | 27 |

    Null test

    28 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 24 | 25 |
      26 |
    1. 27 | {{name}} 28 |
    2. 29 |
    -------------------------------------------------------------------------------- /examples/fiddles/filters/titlecase/demo.details: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Title case filter example 4 | description: 5 | authors: 6 | - H. Stefan Olafsson 7 | resources: 8 | - http://tempojs.com/tempo2.min.js 9 | ... 10 | */ 11 | 12 | URL: http://jsfiddle.net/gh/get/jquery/1.8/twigkit/tempo/tree/2.0/examples/fiddles/filters/titlecase/ -------------------------------------------------------------------------------- /examples/fiddles/filters/titlecase/demo.html: -------------------------------------------------------------------------------- 1 | 2 |
      3 |
    1. {{name | titlecase 'and the of'}}
    2. 4 |
    -------------------------------------------------------------------------------- /examples/fiddles/filters/titlecase/demo.js: -------------------------------------------------------------------------------- 1 | var data = [ 2 | {'name': 'snow white and the seven dwarfs'}, 3 | {'name': 'the last of the mohicans'} 4 | ]; 5 | 6 | // Render 7 | $(document).ready( function () { 8 | Tempo.prepare('list').render(data); 9 | }); -------------------------------------------------------------------------------- /examples/fiddles/preprocessing/demo.details: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Pre-processing example 4 | description: Shows pre-processing of data and modification of the template using events 5 | authors: 6 | - H. Stefan Olafsson 7 | resources: 8 | - http://tempojs.com/tempo2.min.js 9 | normalize_css: no 10 | ... 11 | */ 12 | 13 | URL: http://jsfiddle.net/gh/get/jquery/1.8/twigkit/tempo/tree/2.0/examples/fiddles/preprocessing/ -------------------------------------------------------------------------------- /examples/fiddles/preprocessing/demo.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/fiddles/preprocessing/demo.js: -------------------------------------------------------------------------------- 1 | var data = [ 2 | {"type":'java', "name":'guice'}, 3 | {"type":'javascript', "name":'jQuery'}, 4 | {"type":'java', "name":'guice', "owner":'Google'}, 5 | {"type":'php', "name":'django'} 6 | ]; 7 | 8 | $(document).ready( 9 | setTimeout(function () { 10 | Tempo.prepare($('ul'), {}, function (template) { 11 | var i = 0; 12 | 13 | template.when(TempoEvent.Types.RENDER_STARTING,function (event) { 14 | $(event.element).before('

    Before

    '); 15 | 16 | }).when(TempoEvent.Types.ITEM_RENDER_STARTING,function (event) { 17 | if (event.item.type == 'javascript') { 18 | event.item.name += ' is fun!'; 19 | } 20 | event.item.even = i++ % 2 ? 'even' : 'odd'; 21 | 22 | }).when(TempoEvent.Types.RENDER_COMPLETE,function (event) { 23 | $(event.element).after('

    After

    '); 24 | 25 | }).render(data); 26 | }); 27 | }, 2500) 28 | ); -------------------------------------------------------------------------------- /examples/fiddles/simple/demo.details: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Simple Demo 4 | description: Simple Tempo demo with no external dependencies. 5 | authors: 6 | - H. Stefan Olafsson 7 | resources: 8 | - http://tempojs.com/tempo2.min.js 9 | ... 10 | */ 11 | 12 | URL: http://jsfiddle.net/gh/get/jquery/1.8/twigkit/tempo/tree/2.0/examples/fiddles/simple/ -------------------------------------------------------------------------------- /examples/fiddles/simple/demo.html: -------------------------------------------------------------------------------- 1 |
      2 |
    1. {{nickname}} {{name.last}}
    2. 3 |
    -------------------------------------------------------------------------------- /examples/fiddles/simple/demo.js: -------------------------------------------------------------------------------- 1 | var data = [ 2 | {'name':{'first':'Leonard', 'last':'Marx'}, 'nickname':'Chico', 'born':'March 21, 1887', 'actor':true, 'solo_endeavours':[ 3 | {'title':'Papa Romani'} 4 | ]}, 5 | {'name':{'first':'Adolph', 'last':'Marx'}, 'nickname':'Harpo', 'born':'November 23, 1888', 'actor':true, 'solo_endeavours':[ 6 | {'title':'Too Many Kisses', 'rating':'favourite'}, 7 | {'title':'Stage Door Canteen'} 8 | ]}, 9 | {'name':{'first':'Julius Henry', 'last':'Marx'}, 'nickname':'Groucho', 'born':'October 2, 1890', 'actor':true, 'solo_endeavours':[ 10 | {'title':'Copacabana'}, 11 | {'title':'Mr. Music', 'rating':'favourite'}, 12 | {'title':'Double Dynamite'} 13 | ]}, 14 | {'name':{'first':'Milton', 'last':'Marx'}, 'nickname':'Gummo', 'born':'October 23, 1892'}, 15 | {'name':{'first':'Herbert', 'last':'Marx'}, 'nickname':'Zeppo', 'born':'February 25, 1901', 'actor':true, 'solo_endeavours':[ 16 | {'title':'A Kiss in the Dark'} 17 | ]} 18 | ]; 19 | 20 | // Render 21 | $(document).ready( function () { 22 | Tempo.prepare('marx-brothers').render(data); 23 | }); -------------------------------------------------------------------------------- /examples/filters-encodeURI.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 |
      17 |
    1. {{url | encodeURI | decodeURI}} ({{url | encodeURI}})
    2. 18 |
    -------------------------------------------------------------------------------- /examples/filters-escape.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 |
      17 |
    1. {{name | escape}}
    2. 18 |
    -------------------------------------------------------------------------------- /examples/filters-multiple.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 19 | 20 | 21 |
      22 |
    1. {{name | titlecase 'and the of' | append '"' | prepend '"'}}
    2. 23 |
    -------------------------------------------------------------------------------- /examples/filters-titlecase.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 19 | 20 | 21 |
      22 |
    1. {{name | titlecase 'and the of'}}
    2. 23 |
    -------------------------------------------------------------------------------- /examples/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twigkit/tempo/9eb4ec9686d841dba89c9839a48b659463214925/examples/images/loading.gif -------------------------------------------------------------------------------- /examples/images/tweeter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twigkit/tempo/9eb4ec9686d841dba89c9839a48b659463214925/examples/images/tweeter.png -------------------------------------------------------------------------------- /examples/jqueryelements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 20 | 21 | 22 |

    Hello

    23 | 28 | 29 | -------------------------------------------------------------------------------- /examples/jsperf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 28 | 29 | 30 |

    Hello

    31 | 32 |
    33 |

    {{header}}

    34 |

    {{header2}}

    35 |

    {{header3}}

    36 |

    {{header4}}

    37 |
    {{header5}}
    38 |
    {{header6}}
    39 | 42 |
    43 | 44 | -------------------------------------------------------------------------------- /examples/multicontainer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 30 | 31 | 32 |

    One Template - Multiple Containers

    33 | 34 |

    data_1

    35 | 38 | 39 |

    data_2

    40 | 41 | 42 |

    data_3

    43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/nested-conditional-templates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 |
      23 |
    1. {{name}} is a boy!
    2. 24 |
    3. {{name}} is a girl
    4. 25 |
    26 | 27 |
      28 |
    1. 29 | {{parent}} 30 | 34 |
    2. 35 |
    -------------------------------------------------------------------------------- /examples/nested-generic-templates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 |
      18 |
    1. 19 | 22 |
    2. 23 |
    -------------------------------------------------------------------------------- /examples/nested-templates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 |
      18 |
    1. 19 | {{parent}} 20 | 23 |
    2. 24 |
    -------------------------------------------------------------------------------- /examples/null-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 30 | 31 | 32 |

    Null test

    33 | 34 | 37 | 38 |
    39 | 40 | 41 | 42 | 43 |
    44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/param-escape.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 |

    Escaped (default)

    17 |
      18 |
    1. {{name}}
    2. 19 |
    20 | 21 |

    Unescaped

    22 |
      23 |
    1. {{name}}
    2. 24 |
    -------------------------------------------------------------------------------- /examples/parentdata-arrays.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 23 | 24 | 25 |

    Hello

    26 | 39 | 40 | -------------------------------------------------------------------------------- /examples/parentdata.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 23 | 24 | 25 |

    Hello

    26 | 39 | 40 | -------------------------------------------------------------------------------- /examples/partials.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 35 | 36 | 37 |

    Partials in Template

    38 |
      39 |
    1. 40 |
      {{name.first}} {{name.last}}
      41 | 44 |
    2. 45 |
    46 | 47 | -------------------------------------------------------------------------------- /examples/partials/movie.html: -------------------------------------------------------------------------------- 1 | {{title}} -------------------------------------------------------------------------------- /examples/preprocessing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 38 | 47 | 48 | 49 |

    Preprocessing

    50 | 51 |
    52 |     var data = [
    53 |                 {"type":'java', "name":'guice'},
    54 |                 {"type":'javascript', "name":'jQuery'},
    55 |                 {"type":'java', "name":'guice', "owner":'Google'},
    56 |                 {"type":'php', "name":'django'}
    57 |             ];
    58 | 
    59 | 60 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /examples/recursion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 38 | 39 | 40 |

    Hello

    41 | 54 | 55 | -------------------------------------------------------------------------------- /examples/solr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tempo :: Solr Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 228 | 244 | 245 | 246 | 247 | 248 | 249 | 262 | 263 | 264 |
    265 | 271 |
    272 | 273 | 274 |
    275 |
    276 | 277 | 278 |
    279 |

    {{response.numFound|format}} results found

    280 | 281 |
      282 |
    1. 283 |
    284 |
    285 | 286 | 287 |
    288 |
      289 |
    • 290 |
    291 |
    292 |
    293 |
    294 | Sorry, but you sort of need JavaScript for this one! 295 |
    296 |
    297 | 298 | 299 | 300 | 305 | 310 | 311 | 312 | 313 | 314 | -------------------------------------------------------------------------------- /examples/solr/partials/facet.html: -------------------------------------------------------------------------------- 1 |

    {{key}}

    2 | -------------------------------------------------------------------------------- /examples/solr/partials/result.html: -------------------------------------------------------------------------------- 1 |

    {{title}}

    2 |

    {{description}}

    3 |

    by {{source}}

    -------------------------------------------------------------------------------- /examples/solr/solr.js: -------------------------------------------------------------------------------- 1 | function SolrJS(host) { 2 | this.h = host; 3 | this.params = {}; 4 | this.params.fq = []; 5 | 6 | return this; 7 | } 8 | 9 | SolrJS.prototype = { 10 | q : function(query) { 11 | if (query != undefined) { 12 | this.params.q = query; 13 | } else { 14 | return this.params.q; 15 | } 16 | }, 17 | 18 | add_fq : function(field, value) { 19 | this.params.fq.push({'field': field, 'value': value}); 20 | }, 21 | 22 | remove_fq : function(field, value) { 23 | for (var i = 0 ; i < this.params.fq.length; i++) { 24 | if (this.params.fq[i] != undefined && this.params.fq[i].field == field && this.params.fq[i].value == value) { 25 | this.params.fq[i] = undefined; 26 | } 27 | } 28 | }, 29 | 30 | url : function() { 31 | var req = this.h + '&q=' + this.params.q; 32 | for (var i = 0 ; i < this.params.fq.length; i++) { 33 | if (this.params.fq[i] !== undefined) { 34 | req += '&fq=' + this.params.fq[i].field + ':("' + this.params.fq[i].value + '")'; 35 | } 36 | } 37 | return req; 38 | }, 39 | 40 | reset : function() { 41 | this.params.q = ''; 42 | this.params = {}; 43 | this.params.fq = []; 44 | } 45 | } -------------------------------------------------------------------------------- /examples/table-nested-template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 96 |
    97 |

    {{title}}

    98 | 99 |
    100 |
    101 | {% if places.length > 0 %} 102 | boing 103 | {% endif %} 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | {% if places.length > 0 %} 115 | boing 116 | {% endif %} 117 | 118 | 119 | 120 | 121 |
    Country NameDate
    {{country}}{{date}}
    122 |
    123 |
    124 |
    -------------------------------------------------------------------------------- /examples/table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
    IdentifiantNom du clientDescription
    {{identifier}}{{clientName}}{{description}}
    43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
    {{identifier}}{{clientName}}{{description}}
    -------------------------------------------------------------------------------- /examples/template-attributes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 19 | 20 |
      21 |
    1. {{name}}
    2. 22 |
    -------------------------------------------------------------------------------- /examples/twitter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tempo :: Twitter Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 101 | 102 | 103 | 104 | 105 | 106 | 113 | 114 | 115 | 116 |
      117 | 126 |
    1. 127 | Sorry, but you sort of need JavaScript for this one! 128 |
    2. 129 |
    130 | 131 | 132 | 133 | 138 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tempo :: The Intuitive JavaScript Template Engine by Twigkit 6 | 7 | 8 | 9 | 12 | 43 | 52 | 65 | 66 | 67 | 68 | 77 | 78 | 79 |
    80 | 81 |
    82 |
    83 |

    Tempo 2.0

    84 |

    Tempo is an easy, intuitive JavaScript rendering engine that enables you to craft data templates in pure HTML.

    85 | 94 |

    Why use Tempo?

    95 | 100 |

    Key Features

    101 | 110 |
    111 | 143 |
    144 | 145 |
    146 |
    147 |
    148 |
    149 |

    Usage

    150 | 151 | 161 |
    162 |
    163 |
    164 |
    165 |
    166 | 167 | 168 |

    JSON

    169 |

    Tempo takes information encoded as JSON and renders it according to an HTML template. Below is a sample array of JSON data.

    170 |
    171 | var data = [
    172 |     {'name':{'first':'Leonard','last':'Marx'},'nickname':'Chico','born':'March 21, 1887','actor': true,'solo_endeavours':[{'title':'Papa Romani'}]},
    173 |     {'name':{'first':'Adolph','last':'Marx'},'nickname':'Harpo','born':'November 23, 1888','actor':true,'solo_endeavours':[{'title':'Too Many Kisses','rating':'favourite'},{'title':'Stage Door Canteen'}]},
    174 |     {'name':{'first':'Julius Henry','last':'Marx'},'nickname':'Groucho','born': 'October 2, 1890','actor':true,'solo_endeavours':[{'title':'Copacabana'},{'title':'Mr. Music','rating':'favourite'},{'title':'Double Dynamite'}]},
    175 |     {'name':{'first':'Milton','last':'Marx'},'nickname':'Gummo','born':'October 23, 1892'},
    176 |     {'name':{'first':'Herbert','last':'Marx'},'nickname':'Zeppo','born':'February 25, 1901','actor':true,'solo_endeavours':[{'title':'A Kiss in the Dark'}]}
    177 | ];
    178 | 
    179 | 180 | 181 |

    JavaScript

    182 |

    Include script

    183 |

    You only need to include one little script.

    184 |
    185 | <script src="js/tempo.js" type="text/javascript"></script>
    186 | 
    187 |

    Tempo.prepare(element)

    188 |

    To initialize Tempo, run the prepare() function to scan an HTML container for data templates, cache them in memory, and remove the data template HTML elements from the page. Tempo.prepare(element) returns an instance of a renderer that knows how to layout the data you provide to it.

    189 | 190 |
    element
    191 |

    The ID of the HTML element (or the element itself) containing your data template. If you're using jQuery, you may pass in a jQuery object instead.

    192 | 193 |

    If the container does not contain a default (that is without conditions and not nested) data-template the entire contents of the container will be considered to represent the template.

    194 | 195 |

    template.render()

    196 |

    The Tempo.prepare() function returns an instance of a template ready for rendering. Once the JSON data is available, run the render(data) function to add the data to the page.

    197 | 198 |
    data
    199 |

    The JSON data to be rendered. You'll first need to perform an AJAX call to the JSON data source (see below).

    200 |
    201 | Tempo.prepare( element ).render( data );
    202 | 
    203 |
    204 | Tempo.prepare('marx-brothers').render(data);
    205 | 
    206 | 207 |

    template.append()

    208 |

    Renderer methods all return an instance of the renderer (a la fluent) so you can chain calls to it. The append(data) function will render the data you pass in and append it to the container.

    209 | 210 |
    data
    211 |

    The JSON data to append.

    212 |
    213 | Tempo.prepare('marx-brothers').render( data ).append( more_brothers );
    214 | 
    215 | 216 |

    template.prepend()

    217 |

    The prepend(data) function will render the data you pass in and insert it before others in the container.

    218 | 219 |
    data
    220 |

    The JSON data to prepend.

    221 |
    222 | Tempo.prepare('marx-brothers').render( data ).prepend( brothers_we_didnt_know_about );
    223 | 
    224 | 225 |

    template.clear()

    226 |

    The clear() function will empty the container, allowing you to render the data again.

    227 |
    228 | Tempo.prepare('marx-brothers').render( data ).clear();
    229 | 
    230 | 231 |

    template.errors(errorHandler)

    232 |

    Tempo will attempt to deal with errors and failures silently but you can pass in your own handler for exceptions:

    233 |
    errorHandler
    234 |

    A function which will be called with the error object from the try/catch block.

    235 |
    236 | Tempo.prepare('list').errors(function (err) {
    237 |     console.log('Whoa! something happened!');
    238 |     console.log(err);
    239 | }).render(data);
    240 | 
    241 | 242 |

    template.into(container)

    243 |

    The into(element) function will allow you to render the original template to one or more different containers specified. The method will return a new template on which you can call the other template methods such as render() or append().

    244 | 245 |
    container
    246 |

    The container to render the template to.

    247 |
    Render to different container:
    248 |
    249 | Tempo.prepare('marx-brothers').into('alternative-container').render( data );
    250 | 
    251 |
    Reuse template for multiple different containers:
    252 |
    253 | var template = Tempo.prepare('marx-brothers');
    254 | template.into('alternative-container').render( data_1 );
    255 | template.into('yet-another-alternative-container').render( data_2 );
    256 | 
    257 | 258 | 259 | 260 |

    HTML

    261 |

    data-template

    262 |

    Any tag with the data-template attribute will be flagged as a data template.

    263 |

    For compliance the full (non-minimized) form is also supported: data-template="data-template".

    264 | 265 |

    {{fields}}

    266 |

    Any field represented in the JSON data may be retrieved by referencing the field name inside double brackets.

    267 |
    268 | <ol id="marx-brothers">
    269 |     <li data-template>{{nickname}} {{name.last}}</li>
    270 | </ol>
    271 | 
    272 |

    The above example would be rendered as:

    273 |
    274 |
      275 |
    1. Chico Marx
    2. 276 |
    3. Harpo Marx
    4. 277 |
    5. Groucho Marx
    6. 278 |
    7. Gummo Marx
    8. 279 |
    9. Zeppo Marx
    10. 280 |
    281 |
    282 | 283 |

    You can reference the object being iterated with {{.}}: 284 | 285 |

    286 | var data = [ 287 | 'Leonard Marx', 288 | 'Adolph Marx', 289 | 'Julius Henry Marx', 290 | 'Milton Marx', 291 | 'Herbert Marx' 292 | ]; 293 |
    294 | 295 |
    296 | <ol id="marx-brothers">
    297 |     <li data-template>{{.}}</li>
    298 | </ol>
    299 | 
    300 |

    If the JSON data represents an array of arrays (which can not be referenced by field/member name) for example:

    301 | 302 |
    303 | var data = [ 304 | ['Leonard','Marx'], 305 | ['Adolph','Marx'], 306 | ['Julius Henry','Marx'], 307 | ['Milton','Marx'], 308 | ['Herbert','Marx'] 309 | ]; 310 |
    311 | 312 |

    You can reference array elements with the following notation:

    313 | 314 |
    315 | <ol id="marx-brothers">
    316 |     <li data-template>{{[0]}} {{[1]}}</li>
    317 | </ol>
    318 | 
    319 | 320 |

    Both examples above would be rendered as:

    321 |
    322 |
      323 |
    1. Leonard Marx
    2. 324 |
    3. Adolph Marx
    4. 325 |
    5. Julius Henry Marx
    6. 326 |
    7. Milton Marx
    8. 327 |
    9. Herbert Marx
    10. 328 |
    329 |
    330 | 331 | 332 |

    Using data from associative arrays (objects)

    333 |

    Normally data being iterated is represented as an array of objects. In some cases however the data is a series of objects in a map:

    334 | 335 |
    336 | var data = {
    337 |     'leonard': 'Leonard Marx',
    338 |     'adolph': 'Adolph Marx',
    339 |     'julius': 'Julius Henry Marx',
    340 |     'milton': 'Milton Marx',
    341 |     'herbert': Herbert Marx'
    342 | };
    343 | 
    344 | 345 |

    In this case you can iterate all the elements using the data-from-map attribute where the key name can be accessed with {{key}} and the value object via {{value}}:

    346 | 347 |
    348 | <ol id="list">
    349 |     <li data-template data-from-map>{{value}} - {{key | append '@marx.com'}}</li>
    350 | </ol>
    351 | 
    352 | 353 |

    Values are escaped by default

    354 |

    All values are escaped by default. To disable automatic escaping pass in the 'escape': false parameter:

    355 |
    356 | Tempo.prepare('marx-brothers', {'escape': false}).render(data);
    357 | 
    358 |

    If you disable escaping you can control this at individual value level using the escape and encodeURI filters.

    359 | 360 | 361 |

    Nested data-templates

    362 |

    Data templates can even be nested within other data templates. Multiple nested templates are supported.

    363 |
    364 | <li data-template>
    365 |     {{nickname}} {{name.last}}
    366 |     <ul>
    367 |         <li data-template-for="solo_endeavours">{{title}}</li>
    368 |     </ul>
    369 | </li>
    370 | 
    371 |
    372 |
      373 |
    1. Chico Marx 374 |
        375 |
      • Papa Romani
      • 376 |
      377 |
    2. 378 |
    3. Harpo Marx 379 |
        380 |
      • Too Many Kisses
      • 381 |
      • Stage Door Canteen
      • 382 |
      383 |
    4. 384 |
    5. Groucho Marx 385 |
        386 |
      • Copacabana
      • 387 |
      • Mr. Music
      • 388 |
      • Double Dynamite
      • 389 |
      390 |
    6. 391 |
    7. Gummo Marx
    8. 392 |
    9. Zeppo Marx 393 |
        394 |
      • A Kiss in the Dark
      • 395 |
      396 |
    10. 397 |
    398 |
    399 | 400 |

    You can (recursively) refer to parent objects within a nested template using the _parent variable.

    401 |
    402 | <li data-template-for="solo_endeavours">{{_parent.name.first}} acted in {{title}}</li>
    403 | 
    404 | 405 | 406 |

    Nested Templates as Partial Template Files

    407 |

    Tempo supports separating more complex nested templates in to master and partial template files. Partials templates are loaded on demand from the server and do require you to use the alternative asynchronous pattern:

    408 |
    JavaScript:
    409 |
    410 | Tempo.prepare('marx-brothers', {}, function(template) {
    411 |    template.render(data);
    412 | });
    413 | 
    414 |
    Template:
    415 |
    416 | <li data-template>
    417 |     {{name.first}} {{name.last}}
    418 |     <ol>
    419 |         <li data-template-for="solo_endeavours" data-template-file="partials/movie.html"></li>
    420 |     </ol>
    421 | </li>
    422 | 
    423 |
    Partial ('partials/movie.html'):
    424 |
    425 | {{title}}
    426 | 
    427 |
    428 |
      429 |
    1. Chico Marx 430 |
        431 |
      • Papa Romani
      • 432 |
      433 |
    2. 434 |
    3. Harpo Marx 435 |
        436 |
      • Too Many Kisses
      • 437 |
      • Stage Door Canteen
      • 438 |
      439 |
    4. 440 |
    5. Groucho Marx 441 |
        442 |
      • Copacabana
      • 443 |
      • Mr. Music
      • 444 |
      • Double Dynamite
      • 445 |
      446 |
    6. 447 |
    7. Gummo Marx
    8. 448 |
    9. Zeppo Marx 449 |
        450 |
      • A Kiss in the Dark
      • 451 |
      452 |
    10. 453 |
    454 |
    455 | 456 | 457 |

    Conditional Templates

    458 |

    Tempo provides boolean and value-based conditionals, as well as the ability to define multiple data templates per container (the first matching template wins).

    459 |
    460 | <ul id="marx-brothers3">
    461 |     <li data-template data-if-nickname="Groucho"">{{nickname}} (aka {{name.first}}) was grumpy!</li>
    462 |     <li data-template data-if-actor>{{name.first}}, nicknamed '<i>{{nickname}} {{name.last}}</i>' was born on {{born}}</li>
    463 | 
    464 |     <!-- Default template -->
    465 |     <li data-template>{{name.first}} {{name.last}} was not in any movies!</li>
    466 | </ul>
    467 | 
    468 |
    469 |
      470 |
    • Leonard, nicknamed 'Chico Marx' was born on March 21, 1887
    • 471 |
    • Adolph, nicknamed 'Harpo Marx' was born on November 23, 1888
    • 472 |
    • Groucho (aka Julius Henry) was grumpy!
    • 473 |
    • Milton Marx was not in any movies!
    • 474 |
    • Herbert, nicknamed 'Zeppo Marx' was born on February 25, 1901
    • 475 |
    476 |
    477 | 478 |

    You can define templates based on data member existence as well:

    479 |
    480 | <li data-template data-has="friend">{{friend}}></li>
    481 | 
    482 | 483 | 484 |

    Fallback Template

    485 |

    Use the data-template-fallback attribute to identify HTML containers you would like to show if JavaScript is disabled.

    486 |

    To ensure that your data template is not visible before being rendered (either because of JavaScript being disabled or due to latency retrieving the data), it's best practice to hide your templates with CSS. If you add an inline rule of style="display: none;" Tempo will simply remove the inline rule once the data has been rendered.

    487 | 488 |
    489 | <ul id="marx-brothers">
    490 |     <li data-template style="display: none;">{{nickname}} {{name.last}}</li>
    491 |     <li data-template-fallback>Sorry, JavaScript required!</li>
    492 | </ul>
    493 | 
    494 | 495 |

    If JavaScript is disabled in the browser the above example would be rendered as:

    496 |
    497 |
      498 |
    • Sorry, JavaScript required!
    • 499 |
    500 |
    501 | 502 | 503 |

    Preserving other elements in the template container

    504 |

    If the template container contains other elements that should be preserved or ignored you can mark these with the data-before-template and data-after-template attributes:

    505 | 506 |
    507 | <ol id="marx-brothers">
    508 |     <li data-before-template>...</li>
    509 |     <li data-template>{{name.first}} {{name.last}}</li>
    510 |     <li data-after-template>...</li>
    511 | </ol>
    512 | 
    513 | 514 | 515 |

    Filter and Formatting Functions

    516 |

    Tempo supports a number of filter functions to modify individual values before they are rendered. Filters can be chained to apply multiple ones to the same value.

    517 | 518 |
    {{ field | escape }}
    519 |

    Escape HTML characters before rendering to page.

    520 | 521 |
    {{ field | encodeURI }}
    522 |

    Encodes a Uniform Resource Identifier (URI) by replacing certain characters with escape sequences representing the UTF-8 encoding of the character.

    523 | 524 |
    {{ field | decodeURI }}
    525 |

    Replaces each escape sequence in an encoded URI with the character that it represents.

    526 | 527 |
    {{ field | truncate 25[, 'optional_suffix'] }}
    528 |

    If the value of this field is longer than 25 characters, then truncate to the length provided. If a second argument is provided then it is used as the suffix instead of the default ellipsis (...).

    529 | 530 |
    {{ field | format[, numberOfDecimals] }}
    531 |

    Currently only formats numbers like 100000.25 by adding commas: 100,000.25. You can optionally specify the number of decimals to use.

    532 | 533 |
    {{ field | default 'default value' }}
    534 |

    If the field is undefined, or null, then show the default value specified.

    535 | 536 |
    {{ field | date 'preset or pattern like HH:mm, DD-MM-YYYY'[, 'UTC'] }}
    537 |

    Creates a date object from the field value, and formats according to presets, or using a date pattern. Available presets are localedate, localetime, date and time. The following pattern elements are supported:

    538 |
      539 |
    • YYYY or YY: year as 4 or 2 digits.
    • 540 |
    • MMMM, MMM, MM or M: month of the year as either full name (September), abbreviated (Sep), padded two digit number or simple integer.
    • 541 |
    • DD or D: day of the month.
    • 542 |
    • EEEE, EEE or E: day of the week as either full (Tuesday), abbreviated (Tue) or number.
    • 543 |
    • HH, H or h: hour of the day.
    • 544 |
    • mm or m: minutes of the hour.
    • 545 |
    • ss or s: seconds.
    • 546 |
    • SSS or S: milliseconds.
    • 547 |
    • a: AM/PM.
    • 548 |
    549 |

    If you would like to use any of the format characters verbatim in the pattern then use \ to escape: {{ some_date | date '\at HH:mm' }}. In this case the a is not replaced with AM/PM. Additionally you can specify whether dates should be handled as UTC.

    550 | 551 |
    {{ field | replace 'regex_pattern', 'replacement' }}
    552 |

    Replace all occurrences of the pattern (supports regular expressions) with the replacement. Replacement string can contain backreferences. See Twitter code sample for an example.

    553 | 554 |
    {{ field | trim }}
    555 |

    Trim any white space at the beginning or end of the value.

    556 | 557 |
    {{ field | upper }}
    558 |

    Change any lower case characters in the value to upper case.

    559 | 560 |
    {{ field | lower }}
    561 |

    Change any upper case characters in the value to lower case.

    562 | 563 |
    {{ field | titlecase[, 'and for the'] }}
    564 |

    Format strings to title case, with an optional blacklist of words that should not be titled (unless first in string e.g. 'the last of the mohicans' to 'The Last of the Mohicans'). See example.

    565 | 566 |
    {{ field | append '&nbsp;some suffix' }}
    567 |

    If the field value is not empty, then append the string to the value. Helpful if you don't want the suffix to show up in the template if the field is undefined or null. Use &nbsp; if you need before or after the suffix.

    568 | 569 |
    {{ field | prepend 'some prefix&nbsp;' }}
    570 |

    If the field value is not empty, then prepend the string to the value. Helpful if you don't want the prefix to show up in the template if the field is undefined or null. Use &nbsp; if you need before or after the prefix.

    571 | 572 |
    {{ field | join 'separator' }}
    573 |

    If the field is an array joins the elements into a string using the supplied separator.

    574 | 575 | 576 |

    Attribute Setters

    577 |

    If an HTML element attribute in a template is immediately followed by a second attribute with the same name, prefixed with data-, then as long as the second one is not empty will the value of the original be replaced with the value of the latter.

    578 |

    In the following example, will the reference to default_image.png be replaced by an actual field value if one exists. Notice here that the .png suffix is only added if the field is not empty.

    579 | 580 |
    581 | <img src="default_image.png" data-src="{{actual_image_if_exists | append '.png'}}" ... />
    582 | 
    583 | 584 |

    Template Tags

    585 |

    Tempo also supports tag blocks.

    586 | 587 |
    {% if javascript-expression %} ... {% else %} ... {% endif %}
    588 |

    The body of the tag will only be rendered if the JavaScript expression evaluates to true. The {% else %} is optional.

    589 | 590 | 591 |

    Variable injection for inline scripts

    592 |

    If you're using scripts inline in your template, you can access JSON object members. You reference these via the __ variable.

    593 | 594 |
    595 | <a href="#" onclick="alert(__.nickname); return false;">{{name.last}}</a>
    596 | 
    597 | 598 |

    Similarly you can reference array elements (shorthand for for __.this[0]):

    599 |
    600 | <a href="#" onclick="alert(__[0]); return false;">{{[0]}}</a>
    601 | 
    602 | 603 |

    You can refer to the object being iterated with __.this. You can use normal dot notation to access members of that object.:

    604 |
    605 | <a href="#" onclick="alert(__.this); return false;">{{.}}</a>
    606 | 
    607 | 608 |

    At render time the accessor variable will be replaced by the object it references. If the object is a string then its value will be wrapped in single quotes, otherwise type is preserved.

    609 | 610 | 611 |

    Pre-processing and Event Handling

    612 | 613 |

    template.when(type, handler)

    614 |

    After preparing a template you can register one or more event listeners by providing a callback function to be notified of events in the lifecycle.

    615 | 616 |
    type
    617 |

    The type of the event. Constant values are defined in TempoEvent.Types.

    618 | 619 |
      620 |
    • TempoEvent.Types.RENDER_STARTING: Indicates that rendering has started, or been manually triggered by calling starting() on the renderer object.
    • 621 |
    • TempoEvent.Types.ITEM_RENDER_STARTING: Indicates that the rendering of a given individual item is starting.
    • 622 |
    • TempoEvent.Types.ITEM_RENDER_COMPLETE: Indicates that the rendering of a given individual item has completed.
    • 623 |
    • TempoEvent.Types.RENDER_COMPLETE: Indicates that the rendering of all items is completed.
    • 624 |
    • TempoEvent.Types.BEFORE_CLEAR: Fires before the container is cleared of all elements.
    • 625 |
    • TempoEvent.Types.AFTER_CLEAR: Fires after the container is cleared of all elements.
    • 626 |
    627 | 628 |
    handler
    629 |

    The handler function to call when the specified event fires.

    630 | 631 |

    The event listener will be called with a single argument, a TempoEvent which has the following properties:

    632 | 633 |
    type
    634 |

    The type of the event. Constant values are defined in TempoEvent.Types.

    635 | 636 |
    item
    637 |

    The item being rendered. This is only available for the ITEM_RENDER_STARTING and ITEM_RENDER_COMPLETE events.

    638 | 639 |
    element
    640 |

    The HTML element or template being used for rendering. This is only available for the ITEM_RENDER_STARTING and ITEM_RENDER_COMPLETE events.

    641 | 642 |
    643 | Tempo.prepare('tweets').when(TempoEvent.Types.RENDER_STARTING, function (event) {
    644 |         $('#tweets').addClass('loading');
    645 |     }).when(TempoEvent.Types.RENDER_COMPLETE, function (event) {
    646 |         $('#tweets').removeClass('loading');
    647 |     }).render(data);
    648 | 
    649 | 650 |

    Even though it's possible to modify the DOM via event handlers it's worth keeping in mind that Tempo is built on the premise of keeping the templates as semantic and readable as possible. We would therefore advise that you limit the use or pre-processing to data cleansing and restructuring as opposed to template modifications.

    651 | 652 |

    starting()

    653 |

    In some cases you prepare the templates ahead of calling render. In those cases if you would like to e.g. set loader graphics, call the renderer's starting() method just prior to issuing e.g. a jQuery request. This will fire the ITEM_RENDER_STARTING event.

    654 | 655 |

    The following example demonstrates use of both methods above. In this case we prepare the templates, and register our event handler function. The event handler is then notified that the jQuery request is about to be issued (when we manually fire RENDER_STARTING with a call to starting()) adding a CSS class to the container. We are then notified that rendering is complete so we can remove the CSS class. 656 | 657 |

    658 | var twitter = Tempo.prepare('tweets').when(TempoEvent.Types.RENDER_STARTING, function (event) {
    659 |             $('#tweets').addClass('loading');
    660 |         }).when(TempoEvent.Types.RENDER_COMPLETE, function (event) {
    661 |             $('#tweets').removeClass('loading');
    662 |         });
    663 | twitter.starting();
    664 | $.getJSON(url, function(data) {
    665 |     twitter.render(data.results);
    666 | });
    667 | 
    668 | 669 | 670 |

    Using Tempo with jQuery

    671 |

    jQuery's getJSON() method provides an easy means of loading JSON-encoded data from the server using a GET HTTP request.

    672 |
    673 | var twitter = Tempo.prepare('tweets');
    674 | $.getJSON("http://search.twitter.com/search.json?q='marx brothers'&callback=?", function(data) {
    675 |     twitter.render(data.results);
    676 | });
    677 | 
    678 |

    Binding event handlers

    679 |

    680 | Note that if you've bound event listeners to elements in your template, these will not be cloned when the template is used. In order to do this you have two options.
    681 |

    682 |

    683 | You can either leave binding until the data has been rendered, e.g. by registering a TempoEvent.Types.RENDER_COMPLETE listener and doing the binding when that fires, or you can use jQuery live which attaches a handler to elements even though they haven't been created. 684 |

    685 |

    686 | The following example shows how to make a template clickable (assuming an 'ol li' selector provides a reference to a template element) and how to obtain a reference to the clicked element: 687 |

    688 |
    689 | $('ol li').live('click', function() {
    690 |     // Do something with the clicked element
    691 |     alert(this);
    692 | });
    693 | 
    694 | 695 | 696 |

    Advanced Topics

    697 | 698 | 699 |

    Configuring the surrounding brace syntax

    700 |

    701 | In order to make it easier to use Tempo with other frameworks such as Django, you can configure Tempo to use surrounding braces other than the default {{ ... }} and {% ... %}. 702 |

    703 |

    704 | To do this you pass the var_braces and tag_braces parameters to the Tempo.prepare function. These will be split down the middle to form the left and right braces. 705 |

    706 |
    707 | Tempo.prepare('marx-brothers', {'var_braces' : '\\[\\[\\]\\]', 'tag_braces' : '\\[\\?\\?\\]'});
    708 | 
    709 |

    You can now use this template syntax instead:

    710 |
    711 | <ol id="marx-brothers">
    712 |     <li data-template>[[nickname]] is [? if nickname == 'Groucho' ?]grouchy[? else ?]happy[? endif ?]!</li>
    713 | </ol>
    714 | 
    715 | 716 |

    Tempo Renderer Information - the _tempo variable

    717 |

    You can access information about the rendering process via the _tempo variable.

    718 | 719 |
    _tempo.index
    720 |

    The index variable tells you how many iterations of a given template have been carried out. This is a zero (0) based index. Nested templates have a separate counter. 721 |

    The following example adds the iteration count to the class attribute, prefixed with 'item-':

    722 |
    723 | <ol id="marx-brothers" class="item-{{_tempo.index}}">
    724 |     <li data-template>{{nickname}} {{name.last}}</li>
    725 | </ol>
    726 | 
    727 |

    This example shows how to access the iteration counter using inline JavaScript injection:

    728 |
    729 | <a href="#" onclick="alert(__._tempo.index); return false;">{{name.last}}</a>
    730 | 
    731 | 732 |
    _tempo.first
    733 |

    True if the item being iterated is the first one in the collection.

    734 | 735 |
    _tempo.last
    736 |

    True if the item being iterated is the last one in the collection.

    737 | 738 |
    739 | 747 |
    748 |
    749 | 750 | 758 | 759 | 764 | 765 | 766 | -------------------------------------------------------------------------------- /node/express.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | app.engine('tpl', require('tempo').__express); 5 | app.set('view engine', 'tpl'); 6 | 7 | app.get('/', function(req, res) { 8 | var beatles = [ 9 | {'name': 'John ' + new Date()}, 10 | {'name': 'Paul'}, 11 | {'name': 'George'}, 12 | {'name': 'Ringo'} 13 | ]; 14 | 15 | res.render('beatles', {'title': 'The Beatles!', 'beatles' : beatles}); 16 | }); 17 | 18 | console.log('Server running at http://127.0.0.1:3000/'); 19 | app.listen(3000); -------------------------------------------------------------------------------- /node/http.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var tempo = require('tempo') 3 | 4 | // Create an instance of an http server 5 | http.createServer( 6 | function(req, res) { 7 | 8 | // Load a template file and create an instance of Tempo 9 | // Load method supplies an instance of Tempo to the callback function 10 | tempo.load('views/beatles.tpl', function(tempo) { 11 | // Load some data 12 | var beatles = [ 13 | {'name': 'John'}, 14 | {'name': 'Paul'}, 15 | {'name': 'George'}, 16 | {'name': 'Ringo'} 17 | ]; 18 | 19 | // Prepare a part of the template and render with data 20 | tempo.prepare('*', {}, function(r) { 21 | r.render({'title': 'The Beatles!', 'beatles': beatles}); 22 | // Write the template to the response 23 | tempo.write(res); 24 | 25 | res.end(); 26 | }); 27 | }); 28 | 29 | }).listen(3000, "127.0.0.1"); 30 | 31 | console.log('Server running at http://127.0.0.1:3000/'); 32 | -------------------------------------------------------------------------------- /node/module/tempo/README.md: -------------------------------------------------------------------------------- 1 | Tempo for Node 2 | -------------------------------------------------------------------------------- /node/module/tempo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tempo", 3 | "version": "0.2.0", 4 | "description": "Tempo Templating Engine", 5 | "keywords": [ 6 | "template", 7 | "html" 8 | ], 9 | "maintainers": [ 10 | { 11 | "name": "Stefan Olafsson", 12 | "email": "stefan@twigkit.com", 13 | "web": "http://twigkit.com/" 14 | } 15 | ], 16 | "contributors": [ 17 | { 18 | "name" : "Twigkit (http://github.com/twigkit)" 19 | } 20 | ], 21 | "bugs": { 22 | "mail": "support@twigkit.com", 23 | "web": "http://github.com/twigkit/tempo/issues" 24 | }, 25 | "licenses": [ 26 | { 27 | "type": "MIT", 28 | "url": "http://github.com/tmpvar/jsdom/blob/master/LICENSE.txt" 29 | } 30 | ], 31 | "repositories": [ 32 | { 33 | "type": "git", 34 | "url": "http://github.com/twigkit/tempo.git" 35 | } 36 | ], 37 | "dependencies": { 38 | "jsdom": ">=0.5.0" 39 | }, 40 | "engines" : { "node" : ">=0.1.9" }, 41 | "main": "./tempo" 42 | } 43 | 44 | -------------------------------------------------------------------------------- /node/module/tempo/tempo.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var jsdom = require('jsdom').jsdom; 3 | navigator = { 4 | userAgent: 'node-js', appVersion: '0.2' 5 | }; 6 | var tempo = require('./lib/tempo').tempo 7 | 8 | var _window; 9 | 10 | /** 11 | * Overrides the template loading mechanism in Tempo to use local file system. 12 | * 13 | * @param file 14 | * @param callback 15 | * @return {*} 16 | */ 17 | tempo.exports.templates.prototype.load = function (file, callback) { 18 | return callback(fs.readFileSync(file, 'UTF-8')); 19 | }; 20 | 21 | tempo.exports.utils.clearContainer = function (el) { 22 | if (el !== null && el !== undefined && el.childNodes !== undefined) { 23 | el.innerHTML = ''; 24 | } 25 | }; 26 | 27 | /** 28 | * Load a given template from the file system. 29 | * 30 | * @param template 31 | * @param callback 32 | */ 33 | tempo.load = function (template, callback) { 34 | fs.readFile(template, 'UTF-8', function (err, data) { 35 | if (err) throw err; 36 | document = jsdom(data); 37 | _window = document.createWindow(); 38 | 39 | callback(tempo.init(_window)); 40 | }); 41 | }; 42 | 43 | /** 44 | * Write the rendered DOM to the response. 45 | * 46 | * @param res 47 | */ 48 | tempo.write = function (res) { 49 | res.write(_window.document.innerHTML); 50 | }; 51 | 52 | /** 53 | * Express 2.0 support. 54 | * 55 | * @param markup 56 | * @param options 57 | * @return {Function} 58 | */ 59 | tempo.compile = function (markup, options) { 60 | var data = markup; 61 | document = jsdom(data); 62 | window = document.createWindow(); 63 | 64 | var renderer = tempo.init(window).prepare(document.getElementsByTagName('html')[0]); 65 | 66 | return function render (locals) { 67 | renderer.render(locals); 68 | return window.document.innerHTML; 69 | }; 70 | }; 71 | 72 | /** 73 | * Express 3.0 support. 74 | * 75 | * @param filename 76 | * @param options 77 | * @param callback 78 | * @private 79 | */ 80 | tempo.__express = function (filename, options, callback) { 81 | options = options || {}; 82 | fs.readFile(filename, 'UTF-8', function (err, data) { 83 | document = jsdom(data); 84 | window = document.createWindow(); 85 | 86 | var renderer = tempo.init(window).prepare(document.getElementsByTagName('html')[0]); 87 | renderer.render(options); 88 | callback(err, window.document.innerHTML); 89 | }); 90 | }; 91 | 92 | module.exports = tempo; -------------------------------------------------------------------------------- /node/views/beatle.tpl: -------------------------------------------------------------------------------- 1 | {{name}}! -------------------------------------------------------------------------------- /node/views/beatles.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 |

    {{title}}

    9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tempo.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Tempo Template Engine 2.1 3 | * 4 | * http://tempojs.com/ 5 | */ 6 | function TempoEvent(type, item, element) { 7 | 'use strict'; 8 | this.type = type; 9 | this.item = item; 10 | this.element = element; 11 | 12 | return this; 13 | } 14 | 15 | TempoEvent.Types = { 16 | RENDER_STARTING: 'render_starting', 17 | ITEM_RENDER_STARTING: 'item_render_starting', 18 | ITEM_RENDER_COMPLETE: 'item_render_complete', 19 | RENDER_COMPLETE: 'render_complete', 20 | BEFORE_CLEAR: 'before_clear', 21 | AFTER_CLEAR: 'after_clear' 22 | }; 23 | 24 | 25 | var Tempo = (function (tempo) { 26 | 'use strict'; 27 | 28 | /*! 29 | * Constants 30 | */ 31 | var NUMBER_FORMAT_REGEX = /(\d+)(\d{3})/; 32 | 33 | 34 | var _window; 35 | 36 | 37 | /*! 38 | * Helpers 39 | */ 40 | var utils = { 41 | memberRegex: function (obj) { 42 | var member_regex = '('; 43 | for (var member in obj) { 44 | if (obj.hasOwnProperty(member)) { 45 | if (member_regex.length > 1) { 46 | member_regex += '|'; 47 | } 48 | member_regex += member; 49 | } 50 | } 51 | return member_regex + ')[\\.]?' + '(?!\\w)'; 52 | }, 53 | 54 | pad: function (val, pad, size) { 55 | while (val.length < size) { 56 | val = pad + val; 57 | } 58 | return val; 59 | }, 60 | 61 | trim: function (str) { 62 | return str.replace(/^\s*([\S\s]*?)\s*$/, '$1'); 63 | }, 64 | 65 | startsWith: function (str, prefix) { 66 | return (str.indexOf(prefix) === 0); 67 | }, 68 | 69 | clearContainer: function (el) { 70 | if (el !== null && el !== undefined && el.childNodes !== undefined) { 71 | for (var i = el.childNodes.length - 1; i >= 0; i--) { 72 | if (el.childNodes[i] !== undefined && el.childNodes[i].getAttribute !== undefined && (el.childNodes[i].getAttribute('data-template') !== null || el.childNodes[i].getAttribute('data-template-for') !== null)) { 73 | el.childNodes[i].parentNode.removeChild(el.childNodes[i]); 74 | } 75 | } 76 | } 77 | }, 78 | 79 | isNested: function (el) { 80 | var p = el.parentNode; 81 | while (p) { 82 | if (this.hasAttr(p, 'data-template') || this.hasAttr(p, 'data-template-for')) { 83 | return true; 84 | } 85 | p = p.parentNode; 86 | } 87 | return false; 88 | }, 89 | 90 | equalsIgnoreCase: function (str1, str2) { 91 | return str1.toLowerCase() === str2.toLowerCase(); 92 | }, 93 | 94 | getElement: function (template, html) { 95 | if (navigator.appVersion.indexOf("MSIE") > -1 && utils.equalsIgnoreCase(template.tagName, 'tr')) { 96 | // Wrapping to get around read-only innerHTML 97 | var el = _window.document.createElement('div'); 98 | el.innerHTML = '' + html + '
    '; 99 | var depth = 3; 100 | while (depth--) { 101 | el = el.lastChild; 102 | } 103 | el.setAttribute('data-template', ''); 104 | return el; 105 | } else { 106 | // No need to wrap 107 | template.innerHTML = html; 108 | return template; 109 | } 110 | }, 111 | 112 | typeOf: function (obj) { 113 | if (typeof(obj) === "object") { 114 | if (obj === null) { 115 | return "null"; 116 | } 117 | if (obj.constructor === ([]).constructor) { 118 | return "array"; 119 | } 120 | if (obj.constructor === (new Date()).constructor) { 121 | return "date"; 122 | } 123 | if (obj.constructor === (new RegExp()).constructor) { 124 | return "regex"; 125 | } 126 | if (typeof HTMLElement === "object" ? obj instanceof HTMLElement : obj && typeof obj === "object" && obj.nodeType === 1 && typeof obj.nodeName === "string") { 127 | return 'element'; 128 | } 129 | if (typeof jQuery !== 'undefined' && obj instanceof jQuery) { 130 | return 'jquery'; 131 | } 132 | return "object"; 133 | } 134 | return typeof(obj); 135 | }, 136 | 137 | hasAttr: function (el, name) { 138 | if (el !== undefined) { 139 | if (el.hasAttribute !== undefined) { 140 | return el.hasAttribute(name); 141 | } else if (el.getAttribute !== undefined) { 142 | return el.getAttribute(name) !== null; 143 | } 144 | } 145 | 146 | return false; 147 | }, 148 | 149 | removeAttr: function (el, name) { 150 | if (el !== undefined) { 151 | el.setAttribute(name, ''); 152 | // if (el.removeAttribute) { 153 | // el.removeAttribute(name); 154 | // } 155 | } 156 | }, 157 | 158 | merge: function (obj1, obj2) { 159 | var obj3 = {}; 160 | 161 | for (var attr1 in obj1) { 162 | if (obj1.hasOwnProperty(attr1)) { 163 | obj3[attr1] = obj1[attr1]; 164 | } 165 | } 166 | 167 | for (var attr2 in obj2) { 168 | if (obj2.hasOwnProperty(attr2)) { 169 | obj3[attr2] = obj2[attr2]; 170 | } 171 | } 172 | return obj3; 173 | }, 174 | notify: function (listener, event) { 175 | if (listener !== undefined && listener.length > 0) { 176 | for (var i = 0; i < listener.length; i++) { 177 | if (event.type === listener[i].type) { 178 | listener[i].listener(event); 179 | } 180 | } 181 | } 182 | }, 183 | container: function (container) { 184 | if (utils.typeOf(container) === 'string') { 185 | if (container === '*') { 186 | container = _window.document.getElementsByTagName('html')[0]; 187 | } else { 188 | container = _window.document.getElementById(container); 189 | } 190 | } else if (utils.typeOf(container) === 'jquery' && container.length > 0) { 191 | container = container[0]; 192 | } 193 | 194 | return container; 195 | }, 196 | arrayContains: function (array, obj) { 197 | if (!Array.prototype.indexOf) { 198 | for (var i = 0; i < this.length; i++) { 199 | if (this[i] === obj) { 200 | return true; 201 | } 202 | } 203 | return false; 204 | } else { 205 | return array.indexOf(obj) > -1; 206 | } 207 | } 208 | }; 209 | 210 | function Templates(params, nestedItem) { 211 | this.params = params; 212 | this.defaultTemplate = null; 213 | this.namedTemplates = {}; 214 | this.container = null; 215 | 216 | this.nestedItem = nestedItem !== undefined ? nestedItem : null; 217 | 218 | this.escape = true; 219 | this.var_brace_left = '\\{\\{'; 220 | this.var_brace_right = '\\}\\}'; 221 | this.tag_brace_left = '\\{%'; 222 | this.tag_brace_right = '%\\}'; 223 | 224 | this.dataIsMap = false; 225 | 226 | this.attributes = {}; 227 | 228 | if (typeof params !== 'undefined') { 229 | for (var prop in params) { 230 | if (prop === 'var_braces') { 231 | this.var_brace_left = params[prop].substring(0, params[prop].length / 2); 232 | this.var_brace_right = params[prop].substring(params[prop].length / 2); 233 | } else if (prop === 'tag_braces') { 234 | this.tag_brace_left = params[prop].substring(0, params[prop].length / 2); 235 | this.tag_brace_right = params[prop].substring(params[prop].length / 2); 236 | } else if (typeof this[prop] !== 'undefined') { 237 | this[prop] = params[prop]; 238 | } 239 | } 240 | } 241 | 242 | return this; 243 | } 244 | 245 | Templates.prototype = { 246 | load: function (file, callback) { 247 | function contents(iframe) { 248 | return iframe.contentWindow ? iframe.contentWindow.document.documentElement.innerHTML : iframe.contentDocument ? iframe.contentDocument.body.innerHTML : iframe.document.body.innerHTML; 249 | } 250 | 251 | if (_window.document.getElementById(file) !== null) { 252 | callback(contents(_window.document.getElementById(file))); 253 | } else { 254 | var el = _window.document.createElement('iframe'); 255 | el.id = file; 256 | el.name = file; 257 | el.style.height = 0; 258 | el.style.width = 0; 259 | el.src = file; 260 | 261 | if (el.attachEvent) { 262 | el.attachEvent('onload', function () { 263 | callback(contents(el)); 264 | }); 265 | } else { 266 | el.onload = function () { 267 | callback(contents(el)); 268 | }; 269 | } 270 | 271 | _window.document.body.appendChild(el); 272 | } 273 | }, 274 | _insertTemplate: function (child, templates, container, callback) { 275 | return function (el) { 276 | utils.removeAttr(child, 'data-template-file'); 277 | child.innerHTML = el; 278 | templates.parse(container, callback); 279 | }; 280 | }, 281 | 282 | parse: function (container, callback) { 283 | this.container = container; 284 | var children = container.getElementsByTagName('*'); 285 | 286 | var ready = true; 287 | 288 | // Preprocessing for referenced templates 289 | for (var i = 0; i < children.length; i++) { 290 | if (ready === true && callback !== undefined && utils.hasAttr(children[i], 'data-template-file')) { 291 | var child = children[i]; 292 | if (child.getAttribute('data-template-file').length > 0) { 293 | var templates = this; 294 | ready = false; 295 | 296 | this.load(child.getAttribute('data-template-file'), this._insertTemplate(child, templates, container, callback)); 297 | } 298 | } else if (utils.hasAttr(children[i], 'data-template-fallback')) { 299 | // Hiding the fallback template 300 | children[i].style.display = 'none'; 301 | } 302 | } 303 | 304 | // Parsing 305 | if (ready) { 306 | var foundTemplates = {}; 307 | for (var s = 0; s < children.length; s++) { 308 | if (children[s].getAttribute !== undefined) { 309 | if (utils.hasAttr(children[s], 'data-template-for') && children[s].getAttribute('data-template-for').length > 0 && this.nestedItem === children[s].getAttribute('data-template-for') && !foundTemplates[this.nestedItem]) { 310 | // Nested template 311 | this.createTemplate(children[s]); 312 | // Guards against recursion when child template has same name! 313 | foundTemplates[this.nestedItem] = true; 314 | } else if (utils.hasAttr(children[s], 'data-template') && !utils.isNested(children[s])) { 315 | // Normal template 316 | this.createTemplate(children[s]); 317 | } 318 | } 319 | } 320 | 321 | // If there is no default template (data-template) then create one from container 322 | // if (this.defaultTemplate === null) { 323 | // this.createTemplate(container); 324 | // } 325 | 326 | utils.clearContainer(this.container); 327 | if (callback !== undefined) { 328 | callback(this); 329 | } 330 | } 331 | }, 332 | 333 | createTemplate: function (node) { 334 | var element = node.cloneNode(true); 335 | 336 | // Clear display: none; 337 | if (element.style.removeAttribute) { 338 | element.style.removeAttribute('display'); 339 | } else if (element.style.removeProperty) { 340 | element.style.removeProperty('display'); 341 | } else { 342 | element.style.display = 'block'; 343 | } 344 | 345 | // Remapping container element in case template 346 | // is deep in container 347 | this.container = node.parentNode; 348 | 349 | // Element is a template 350 | var nonDefault = false; 351 | for (var a = 0; a < element.attributes.length; a++) { 352 | var attr = element.attributes[a]; 353 | // If attribute 354 | if (utils.startsWith(attr.name, 'data-if-')) { 355 | var val; 356 | if (attr.value === '') { 357 | val = true; 358 | } else if (attr.value === 'null') { 359 | val = null; 360 | } else { 361 | val = '\'' + attr.value + '\''; 362 | } 363 | this.namedTemplates[attr.name.substring(8, attr.name.length) + '==' + val] = element; 364 | utils.removeAttr(element, attr.name); 365 | nonDefault = true; 366 | } else if (attr.name === 'data-has') { 367 | this.namedTemplates[attr.value + '!==undefined'] = element; 368 | utils.removeAttr(element, attr.name); 369 | nonDefault = true; 370 | } else if (attr.name === 'data-from-map') { 371 | this.dataIsMap = true; 372 | } else if (!utils.startsWith(attr.name, 'data-template') && utils.startsWith(attr.name, 'data-')) { 373 | // Treat as an attribute for template 374 | this.attributes[attr.name.substring(5, attr.name.length)] = attr.value; 375 | } 376 | } 377 | // Setting as default template, last one wins 378 | if (!nonDefault) { 379 | this.defaultTemplate = element; 380 | } 381 | }, 382 | 383 | templateFor: function (i) { 384 | for (var templateName in this.namedTemplates) { 385 | if (eval('i.' + templateName)) { 386 | return this.namedTemplates[templateName].cloneNode(true); 387 | } 388 | } 389 | if (this.defaultTemplate) { 390 | return this.defaultTemplate.cloneNode(true); 391 | } 392 | } 393 | }; 394 | 395 | 396 | /*! 397 | * Renderer for populating containers with data using templates. 398 | */ 399 | function Renderer(templates) { 400 | this.templates = templates; 401 | this.listener = []; 402 | this.started = false; 403 | this.varRegex = new RegExp(this.templates.var_brace_left + '[ ]?([A-Za-z0-9$\\._\\[\\]]*?)([ ]?\\|[ ]?.*?)?[ ]?' + this.templates.var_brace_right, 'g'); 404 | this.tagRegex = new RegExp(this.templates.tag_brace_left + '[ ]?([\\s\\S]*?)( [\\s\\S]*?)?[ ]?' + this.templates.tag_brace_right + '(([\\s\\S]*?)(?=' + this.templates.tag_brace_left + '[ ]?end\\1[ ]?' + this.templates.tag_brace_right + '))?', 'g'); 405 | this.filterSplitter = new RegExp('\\|[ ]?(?=' + utils.memberRegex(this.filters) + ')', 'g'); 406 | this.errorHandler = null; 407 | return this; 408 | } 409 | 410 | Renderer.prototype = { 411 | when: function (type, listener) { 412 | this.listener.push({'type': type, 'listener': listener}); 413 | 414 | return this; 415 | }, 416 | 417 | _getValue: function (renderer, variable, i, t) { 418 | var val = null; 419 | // Handling tempo_info variable 420 | if (utils.startsWith(variable, '_tempo.')) { 421 | return eval('t.' + variable.substring(7, variable.length)); 422 | } 423 | 424 | if (variable === '.') { 425 | val = eval('i'); 426 | } else if (variable === 'this' || variable.match(/this[\\[\\.]/) !== null) { 427 | val = eval('i' + variable.substring(4, variable.length)); 428 | } else if (utils.typeOf(i) === 'array') { 429 | val = eval('i' + variable); 430 | } else { 431 | val = eval('i.' + variable); 432 | } 433 | 434 | return val; 435 | }, 436 | 437 | _replaceVariables: function (renderer, _tempo, i, str) { 438 | var self = this; 439 | return str.replace(this.varRegex, function (match, variable, args) { 440 | 441 | try { 442 | var val = renderer._getValue(renderer, variable, i, _tempo); 443 | // Handle filters 444 | if (args !== undefined && args !== '') { 445 | var filters = utils.trim(utils.trim(args).substring(1)).split(self.filterSplitter); 446 | for (var p = 0; p < filters.length; p++) { 447 | var filter = utils.trim(filters[p]), filter_args, j = filter.indexOf(' '); 448 | // If there is a space, there must be arguments 449 | if (~j) { 450 | filter_args = filter.substr(j).replace(/(^ *['"])|(['"] *$)/g, '').split(/['"] *, *['"]/); 451 | filter = filter.substr(0, j); 452 | } else { 453 | filter_args = []; 454 | } 455 | val = renderer.filters[filter](val, filter_args); 456 | } 457 | } 458 | 459 | if (val !== undefined) { 460 | if (self.templates.escape) { 461 | val = self.filters.escape(val, {}); 462 | } 463 | return val; 464 | } 465 | } catch (err) { 466 | self._onError.call(self, err); 467 | } 468 | 469 | return ''; 470 | }); 471 | }, 472 | 473 | _replaceObjects: function (renderer, _tempo, i, str, regex) { 474 | return str.replace(regex, function (match, variable, args) { 475 | try { 476 | var val = renderer._getValue(renderer, variable, i, _tempo); 477 | 478 | if (val !== undefined) { 479 | if (utils.typeOf(val) === 'string') { 480 | return '\'' + val + '\''; 481 | } else { 482 | return val; 483 | } 484 | } 485 | } catch (err) { 486 | self._onError.call(self, err); 487 | } 488 | 489 | return undefined; 490 | }); 491 | }, 492 | 493 | _applyAttributeSetters: function (renderer, item, str) { 494 | // Adding a space in front of first part to make sure I don't get partial matches 495 | return str.replace(/(\b[A-z0-9]+?)(?:="[^"']*?"[^>]*?)data-\1="(.*?)"/g, function (match, attr, data_value) { 496 | if (data_value !== '') { 497 | return attr + '="' + data_value + '"'; 498 | } 499 | return match; 500 | }); 501 | }, 502 | 503 | _applyTags: function (renderer, item, str) { 504 | return str.replace(this.tagRegex, function (match, tag, args, body) { 505 | if (renderer.tags.hasOwnProperty(tag)) { 506 | args = args.substring(args.indexOf(' ')).replace(/^[ ]*|[ ]*$/g, ''); 507 | var filter_args = args.split(/(?:['"])[ ]?,[ ]?(?:['"])/); 508 | return renderer.tags[tag](renderer, item, match, filter_args, body); 509 | } else { 510 | return ''; 511 | } 512 | }); 513 | }, 514 | 515 | starting: function (event) { 516 | // Use this to manually fire the RENDER_STARTING event e.g. just before you issue an AJAX request 517 | // Useful if you're not calling prepare immediately before render 518 | this.started = true; 519 | if (event === undefined) { 520 | event = new TempoEvent(TempoEvent.Types.RENDER_STARTING, undefined, undefined); 521 | } 522 | utils.notify(this.listener, event); 523 | 524 | return this; 525 | }, 526 | 527 | _renderNestedItem: function (i, nested) { 528 | var self = this; 529 | return function (templates) { 530 | var r = new Renderer(templates); 531 | var data = null; 532 | 533 | if (nested === '*' || i.hasOwnProperty(nested.split('.')[0])) { 534 | if (nested === '*') { 535 | data = i; 536 | } else { 537 | data = eval('i.' + nested); 538 | } 539 | 540 | if (data) { 541 | try { 542 | if (utils.typeOf(data) === 'array') { 543 | for (var s = 0; s < data.length; s++) { 544 | if (utils.typeOf(data[s]) === 'object') { 545 | data[s]._parent = function () { 546 | return i; 547 | }() 548 | } 549 | } 550 | } else { 551 | data._parent = function () { 552 | return i; 553 | }(); 554 | } 555 | } catch (err) { 556 | self._onError.call(self, err); 557 | } 558 | } 559 | } 560 | r.render(data); 561 | }; 562 | }, 563 | 564 | renderItem: function (renderer, _tempo_info, i, fragment) { 565 | var memberRegex = new RegExp('(?:__[\\.]?)((_tempo|\\[|' + utils.memberRegex(i) + '|this)([A-Za-z0-9$\\._\\[\\]]+)?)', 'g'); 566 | var template = renderer.templates.templateFor(i); 567 | var tempo_info = utils.merge(_tempo_info, renderer.templates.attributes); 568 | 569 | // Clear attributes in case of recursive nesting (TODO: Probably need to clear more) 570 | if (utils.hasAttr(template, 'data-template-for')) { 571 | utils.removeAttr(template, 'data-template-for'); 572 | } 573 | if (utils.hasAttr(template, 'data-template-file')) { 574 | utils.removeAttr(template, 'data-template-file'); 575 | } 576 | 577 | if (template && i) { 578 | utils.notify(this.listener, new TempoEvent(TempoEvent.Types.ITEM_RENDER_STARTING, i, template)); 579 | var nestedDeclaration = template.innerHTML.match(/data-template-for="([^"]+?)"/g); 580 | if (nestedDeclaration) { 581 | for (var p = 0; p < nestedDeclaration.length; p++) { 582 | var nested = nestedDeclaration[p].match(/data-template-for="([^"]+?)"/); 583 | if (nested && nested[1]) { 584 | var t = new Templates(renderer.templates.params, nested[1]); 585 | try { 586 | t.parse(template, this._renderNestedItem(i, nested[1])); 587 | } catch (err) { 588 | this._onError.call(this, err); 589 | } 590 | } 591 | } 592 | } 593 | 594 | // Processing template element attributes 595 | for (var a = 0; a < template.attributes.length; a++) { 596 | var attr = template.attributes[a]; 597 | if (attr !== null && attr.specified && attr.value !== null && attr.value.length > 0 && attr.name.match(/style|data-template.*/) === null) { 598 | attr.value = this._applyTags(this, i, attr.value); 599 | attr.value = this._replaceVariables(this, tempo_info, i, attr.value); 600 | } 601 | } 602 | 603 | // Dealing with HTML as a String from now on (to be reviewed) 604 | // Attribute values are escaped in FireFox so making sure there are no escaped tags 605 | var html = template.innerHTML.replace(/%7B%7B/g, '{{').replace(/%7D%7D/g, '}}'); 606 | 607 | // Tags 608 | html = this._applyTags(this, i, html); 609 | 610 | // Content 611 | html = this._replaceVariables(this, tempo_info, i, html); 612 | 613 | // JavaScript objects 614 | html = this._replaceObjects(this, tempo_info, i, html, memberRegex); 615 | 616 | html = this._applyAttributeSetters(this, i, html); 617 | 618 | fragment.appendChild(utils.getElement(template, html)); 619 | 620 | utils.notify(this.listener, new TempoEvent(TempoEvent.Types.ITEM_RENDER_COMPLETE, i, template)); 621 | } 622 | }, 623 | 624 | _createFragment: function (data) { 625 | if (data) { 626 | var tempo_info = {}; 627 | var fragment = _window.document.createDocumentFragment(); 628 | 629 | // If object then wrapping in an array 630 | if (utils.typeOf(data) === 'object') { 631 | if (this.templates.dataIsMap) { 632 | var mapped = []; 633 | for (var member in data) { 634 | if (data.hasOwnProperty(member) && member !== '_parent') { 635 | var pair = {}; 636 | pair.key = member; 637 | pair.value = data[member]; 638 | mapped.push(pair); 639 | } 640 | } 641 | data = mapped; 642 | } else { 643 | data = [data]; 644 | } 645 | } 646 | 647 | for (var i = 0; i < data.length; i++) { 648 | tempo_info.index = i; 649 | tempo_info.first = i < 1; 650 | tempo_info.last = i == data.length - 1; 651 | this.renderItem(this, tempo_info, data[i], fragment); 652 | } 653 | 654 | return fragment; 655 | } 656 | 657 | return null; 658 | }, 659 | 660 | into: function (target) { 661 | if (target !== undefined) { 662 | this.templates.container = utils.container(target); 663 | } 664 | 665 | return this; 666 | }, 667 | 668 | render: function (data) { 669 | // Check if starting event was manually fired 670 | if (!this.started) { 671 | this.starting(new TempoEvent(TempoEvent.Types.RENDER_STARTING, data, this.templates.container)); 672 | } 673 | 674 | this.clear(); 675 | this.append(data); 676 | 677 | return this; 678 | }, 679 | 680 | append: function (data) { 681 | // Check if starting event was manually fired 682 | if (!this.started) { 683 | this.starting(new TempoEvent(TempoEvent.Types.RENDER_STARTING, data, this.templates.container)); 684 | } 685 | 686 | var fragment = this._createFragment(data); 687 | if (fragment !== null && this.templates.container !== null) { 688 | if (fragment !== null) { 689 | var ref = null; 690 | for (var i = this.templates.container.childNodes.length; i >= 0; i--) { 691 | 692 | if (this.templates.container.childNodes[i] !== undefined && this.templates.container.childNodes[i].getAttribute !== undefined && this.templates.container.childNodes[i].getAttribute('data-after-template') !== null) { 693 | ref = this.templates.container.childNodes[i]; 694 | } 695 | } 696 | if (ref === null) { 697 | ref = this.templates.container.lastChild; 698 | } 699 | if (ref !== null) { 700 | this.templates.container.insertBefore(fragment, ref); 701 | } else { 702 | this.templates.container.appendChild(fragment); 703 | } 704 | } 705 | } 706 | 707 | utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_COMPLETE, data, this.templates.container)); 708 | 709 | return this; 710 | }, 711 | 712 | prepend: function (data) { 713 | // Check if starting event was manually fired 714 | if (!this.started) { 715 | this.starting(new TempoEvent(TempoEvent.Types.RENDER_STARTING, data, this.templates.container)); 716 | } 717 | 718 | var fragment = this._createFragment(data); 719 | if (fragment !== null) { 720 | var ref = null; 721 | for (var i = 0; i < this.templates.container.childNodes.length; i++) { 722 | if (this.templates.container.childNodes[i] !== undefined && this.templates.container.childNodes[i].getAttribute !== undefined && this.templates.container.childNodes[i].getAttribute('data-before-template') !== null) { 723 | ref = this.templates.container.childNodes[i]; 724 | } 725 | } 726 | if (ref === null) { 727 | ref = this.templates.container.firstChild; 728 | } 729 | if (ref !== null) { 730 | if (ref.nextSibling !== null && ref.getAttribute && ref.getAttribute('data-before-template') !== null) { 731 | ref = ref.nextSibling; 732 | } 733 | this.templates.container.insertBefore(fragment, ref); 734 | } else { 735 | this.templates.container.appendChild(fragment); 736 | } 737 | } 738 | 739 | utils.notify(this.listener, new TempoEvent(TempoEvent.Types.RENDER_COMPLETE, data, this.templates.container)); 740 | 741 | return this; 742 | }, 743 | 744 | errors: function (errorHandler) { 745 | this.errorHandler = errorHandler; 746 | return this; 747 | }, 748 | 749 | _onError: function (err) { 750 | if (this.errorHandler !== null) { 751 | this.errorHandler.call(this, err); 752 | } 753 | }, 754 | 755 | clear: function () { 756 | utils.notify(this.listener, new TempoEvent(TempoEvent.Types.BEFORE_CLEAR, {}, this.templates.container)); 757 | utils.clearContainer(this.templates.container); 758 | utils.notify(this.listener, new TempoEvent(TempoEvent.Types.AFTER_CLEAR, {}, this.templates.container)); 759 | }, 760 | 761 | tags: { 762 | 'if': function (renderer, i, match, args, body) { 763 | var member_regex = utils.memberRegex(i); 764 | 765 | var expr = args[0].replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<'); 766 | expr = expr.replace(new RegExp(member_regex, 'gi'), function (match) { 767 | return 'i.' + match; 768 | }); 769 | 770 | var blockRegex = new RegExp(renderer.templates.tag_brace_left + '[ ]?else[ ]?' + renderer.templates.tag_brace_right, 'g'); 771 | var blocks = body.split(blockRegex); 772 | 773 | if (eval(expr)) { 774 | return blocks[0]; 775 | } else if (blocks.length > 1) { 776 | return blocks[1]; 777 | } 778 | 779 | return ''; 780 | } 781 | }, 782 | 783 | filters: { 784 | 'escape': function (value, args) { 785 | return value.toString().replace(/[&<>]/g, function (c) { 786 | return { 787 | '&': '&', 788 | '<': '<', 789 | '>': '>' 790 | }[c] || c; 791 | }); 792 | }, 793 | 'encodeURI': function (value, args) { 794 | return encodeURI(value.toString()); 795 | }, 796 | 'decodeURI': function (value, args) { 797 | return decodeURI(value.toString()); 798 | }, 799 | 'truncate': function (value, args) { 800 | if (value !== undefined) { 801 | var len = 0; 802 | var rep = '...'; 803 | if (args.length > 0) { 804 | len = parseInt(args[0], 10); 805 | } 806 | if (args.length > 1) { 807 | rep = args[1]; 808 | } 809 | if (value.length > len - 3) { 810 | return value.substr(0, len - 3) + rep; 811 | } 812 | return value; 813 | } 814 | }, 815 | 'format': function (value, args) { 816 | if (value !== undefined) { 817 | if (args.length === 1) { 818 | value = parseFloat(value + '').toFixed(parseInt(args[0], 10)); 819 | } 820 | var x = (value + '').split('.'); 821 | var x1 = x[0]; 822 | var x2 = x.length > 1 ? '.' + x[1] : ''; 823 | 824 | while (NUMBER_FORMAT_REGEX.test(x1)) { 825 | x1 = x1.replace(NUMBER_FORMAT_REGEX, '$1' + ',' + '$2'); 826 | } 827 | 828 | return x1 + x2; 829 | } 830 | }, 831 | 'upper': function (value, args) { 832 | return value.toUpperCase(); 833 | }, 834 | 'lower': function (value, args) { 835 | return value.toLowerCase(); 836 | }, 837 | 'titlecase': function (value, args) { 838 | var blacklist = []; 839 | if (args !== undefined && args.length === 1) { 840 | blacklist = args[0].split(' '); 841 | } 842 | return value.replace(/\w[a-z]\S*/g, function (m, i) { 843 | if (blacklist.length === 0 || !(utils.arrayContains(blacklist, m) && i > 0)) { 844 | return m.charAt(0).toUpperCase() + m.substr(1).toLowerCase(); 845 | } 846 | return m; 847 | }); 848 | }, 849 | 'trim': function (value, args) { 850 | return utils.trim(value); 851 | }, 852 | 'replace': function (value, args) { 853 | if (value !== undefined && args.length === 2) { 854 | return value.replace(new RegExp(args[0], 'g'), args[1]); 855 | } 856 | return value; 857 | }, 858 | 'append': function (value, args) { 859 | if (value !== undefined && args.length === 1) { 860 | return value + '' + args[0]; 861 | } 862 | return value; 863 | }, 864 | 'prepend': function (value, args) { 865 | if (value !== undefined && args.length === 1) { 866 | return args[0] + '' + value; 867 | } 868 | return value; 869 | }, 870 | 'join': function (value, args) { 871 | if (args.length === 1 && value !== undefined && utils.typeOf(value) === 'array') { 872 | return value.join(args[0]); 873 | } 874 | return value; 875 | }, 876 | 'default': function (value, args) { 877 | if (value !== undefined && value !== null) { 878 | return value; 879 | } 880 | if (args.length === 1) { 881 | return args[0]; 882 | } 883 | return value; 884 | }, 885 | 'date': function (value, args) { 886 | if (value !== undefined && args.length >= 1 && args.length <= 2) { 887 | var date = new Date(value); 888 | var format = args[0]; 889 | var isUTC = (args.length === 2 && args[1] === 'UTC'); 890 | if (format === 'localedate') { 891 | return date.toLocaleDateString(); 892 | } else if (format === 'localetime') { 893 | return date.toLocaleTimeString(); 894 | } else if (format === 'date') { 895 | return date.toDateString(); 896 | } else if (format === 'time') { 897 | return date.toTimeString(); 898 | } else { 899 | var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 900 | var DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 901 | var DATE_PATTERNS = { 902 | 'YYYY': function (date) { 903 | return (isUTC ? date.getUTCFullYear() : date.getFullYear()); 904 | }, 905 | 'YY': function (date) { 906 | return (isUTC ? date.getUTCFullYear() : date.getFullYear()).toFixed().substring(2); 907 | }, 908 | 'MMMM': function (date) { 909 | return MONTHS[(isUTC ? date.getUTCMonth() : date.getMonth())]; 910 | }, 911 | 'MMM': function (date) { 912 | return MONTHS[(isUTC ? date.getUTCMonth() : date.getMonth())].substring(0, 3); 913 | }, 914 | 'MM': function (date) { 915 | return utils.pad(((isUTC ? date.getUTCMonth() : date.getMonth()) + 1).toFixed(), '0', 2); 916 | }, 917 | 'M': function (date) { 918 | return (isUTC ? date.getUTCMonth() : date.getMonth()) + 1; 919 | }, 920 | 'DD': function (date) { 921 | return utils.pad((isUTC ? date.getUTCDate() : date.getDate()).toFixed(), '0', 2); 922 | }, 923 | 'D': function (date) { 924 | return (isUTC ? date.getUTCDate() : date.getDate()); 925 | }, 926 | 'EEEE': function (date) { 927 | return DAYS[(isUTC ? date.getUTCDay() : date.getDay())]; 928 | }, 929 | 'EEE': function (date) { 930 | return DAYS[(isUTC ? date.getUTCDay() : date.getDay())].substring(0, 3); 931 | }, 932 | 'E': function (date) { 933 | return (isUTC ? date.getUTCDay() : date.getDay()); 934 | }, 935 | 'HH': function (date) { 936 | return utils.pad((isUTC ? date.getUTCHours() : date.getHours()).toFixed(), '0', 2); 937 | }, 938 | 'H': function (date) { 939 | return (isUTC ? date.getUTCHours() : date.getHours()); 940 | }, 941 | 'h': function (date) { 942 | var hours = (isUTC ? date.getUTCHours() : date.getHours()); 943 | return hours < 13 ? (hours === 0 ? 12 : hours) : hours - 12; 944 | }, 945 | 'mm': function (date) { 946 | return utils.pad((isUTC ? date.getUTCMinutes() : date.getMinutes()).toFixed(), '0', 2); 947 | }, 948 | 'm': function (date) { 949 | return (isUTC ? date.getUTCMinutes() : date.getMinutes()); 950 | }, 951 | 'ss': function (date) { 952 | return utils.pad((isUTC ? date.getUTCSeconds() : date.getSeconds()).toFixed(), '0', 2); 953 | }, 954 | 's': function (date) { 955 | return (isUTC ? date.getUTCSeconds() : date.getSeconds()); 956 | }, 957 | 'SSS': function (date) { 958 | return utils.pad((isUTC ? date.getUTCMilliseconds() : date.getMilliseconds()).toFixed(), '0', 3); 959 | }, 960 | 'S': function (date) { 961 | return (isUTC ? date.getUTCMilliseconds() : date.getMilliseconds()); 962 | }, 963 | 'a': function (date) { 964 | return (isUTC ? date.getUTCHours() : date.getHours()) < 12 ? 'AM' : 'PM'; 965 | } 966 | }; 967 | format = format.replace(/(\\)?(Y{2,4}|M{1,4}|D{1,2}|E{1,4}|H{1,2}|h|m{1,2}|s{1,2}|S{1,3}|a)/g, 968 | function (match, escape, pattern) { 969 | if (!escape) { 970 | if (DATE_PATTERNS.hasOwnProperty(pattern)) { 971 | return DATE_PATTERNS[pattern](date); 972 | } 973 | } 974 | return pattern; 975 | }); 976 | 977 | return format; 978 | } 979 | } 980 | 981 | return ''; 982 | } 983 | } 984 | }; 985 | 986 | /*! 987 | * Initialising Tempo with a Window object in case running inside Node. 988 | */ 989 | tempo.init = function (window) { 990 | _window = window; 991 | 992 | return this; 993 | }; 994 | 995 | /*! 996 | * Prepare a container for rendering, gathering templates and 997 | * clearing afterwards. 998 | */ 999 | tempo.prepare = function (container, params, callback) { 1000 | container = utils.container(container); 1001 | 1002 | var templates = new Templates(params); 1003 | if (callback !== undefined) { 1004 | templates.parse(container, function (templates) { 1005 | callback(new Renderer(templates)); 1006 | }); 1007 | } else { 1008 | templates.parse(container); 1009 | return new Renderer(templates); 1010 | } 1011 | }; 1012 | 1013 | tempo.exports = { 1014 | 'templates': Templates, 1015 | 'utils': utils 1016 | }; 1017 | 1018 | tempo.test = { 1019 | 'utils': utils, 1020 | 'templates': new Templates({}), 1021 | 'renderer': new Renderer(new Templates({})) 1022 | }; 1023 | 1024 | 1025 | // Default initialisation 1026 | try { 1027 | tempo.init(window); 1028 | } catch (e) { 1029 | exports.tempo = tempo; 1030 | } 1031 | 1032 | return tempo; 1033 | 1034 | })(Tempo || {}); 1035 | -------------------------------------------------------------------------------- /tempo.min.js: -------------------------------------------------------------------------------- 1 | eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('B R(a,b,c){\'2p 2q\';6.1G=a;6.3E=b;6.2r=c;y 6}R.14={1v:\'3F\',2s:\'3G\',2t:\'3H\',22:\'3I\',2u:\'3J\',2v:\'3K\'};C 2w=(B(n){\'2p 2q\';C o=/(\\d+)(\\d{3})/;C q;C u={1H:B(a){C b=\'(\';O(C c 1n a){v(a.1h(c)){v(b.F>1){b+=\'|\'}b+=c}}y b+\')[\\\\.]?\'+\'(?!\\\\w)\'},1i:B(a,b,c){1I(a.F=0;i--){v(a.U[i]!==I&&a.U[i].V!==I&&(a.U[i].V(\'N-Q\')!==J||a.U[i].V(\'N-Q-O\')!==J)){a.U[i].1J.3L(a.U[i])}}}},2x:B(a){C p=a.1J;1I(p){v(6.17(p,\'N-Q\')||6.17(p,\'N-Q-O\')){y W}p=p.1J}y 1j},2y:B(a,b){y a.1K()===b.1K()},2z:B(a,b){v(3M.3N.1p("3O")>-1&&u.2y(a.3P,\'3Q\')){C c=q.16.2A(\'3R\');c.1a=\'<2B><2C>\'+b+\'\';C d=3;1I(d--){c=c.2D}c.2E(\'N-Q\',\'\');y c}K{a.1a=b;y a}},18:B(a){v(1b(a)==="1q"){v(a===J){y"J"}v(a.1r===([]).1r){y"1L"}v(a.1r===(P 2F()).1r){y"24"}v(a.1r===(P 1c()).1r){y"3S"}v(1b 2G==="1q"?a 2H 2G:a&&1b a==="1q"&&a.3T===1&&1b a.3U==="25"){y\'2r\'}v(1b 2I!==\'I\'&&a 2H 2I){y\'2J\'}y"1q"}y 1b(a)},17:B(a,b){v(a!==I){v(a.2K!==I){y a.2K(b)}K v(a.V!==I){y a.V(b)!==J}}y 1j},1s:B(a,b){v(a!==I){a.2E(b,\'\')}},2L:B(a,b){C c={};O(C d 1n a){v(a.1h(d)){c[d]=a[d]}}O(C e 1n b){v(b.1h(e)){c[e]=b[e]}}y c},1d:B(a,b){v(a!==I&&a.F>0){O(C i=0;i0){a=a[0]}y a},2N:B(a,b){v(!3W.27.1p){O(C i=0;i<6.F;i++){v(6[i]===b){y W}}y 1j}K{y a.1p(b)>-1}}};B 1k(a,b){6.2O=a;6.1M=J;6.1x={};6.L=J;6.1N=b!==I?b:J;6.1O=W;6.28=\'\\\\{\\\\{\';6.29=\'\\\\}\\\\}\';6.1y=\'\\\\{%\';6.1z=\'%\\\\}\';6.2a=1j;6.1l={};v(1b a!==\'I\'){O(C c 1n a){v(c===\'3X\'){6.28=a[c].X(0,a[c].F/2);6.29=a[c].X(a[c].F/2)}K v(c===\'3Y\'){6.1y=a[c].X(0,a[c].F/2);6.1z=a[c].X(a[c].F/2)}K v(1b 6[c]!==\'I\'){6[c]=a[c]}}}y 6}1k.27={2P:B(b,c){B 1P(a){y a.2Q?a.2Q.16.3Z.1a:a.2R?a.2R.2b.1a:a.16.2b.1a}v(q.16.26(b)!==J){c(1P(q.16.26(b)))}K{C d=q.16.2A(\'40\');d.41=b;d.Z=b;d.19.42=0;d.19.43=0;d.44=b;v(d.2S){d.2S(\'2T\',B(){c(1P(d))})}K{d.2T=B(){c(1P(d))}}q.16.2b.1Q(d)}},2U:B(b,c,d,e){y B(a){u.1s(b,\'N-Q-1t\');b.1a=a;c.1A(d,e)}},1A:B(a,b){6.L=a;C c=a.2M(\'*\');C d=W;O(C i=0;i0){C f=6;d=1j;6.2P(e.V(\'N-Q-1t\'),6.2U(e,f,a,b))}}K v(u.17(c[i],\'N-Q-45\')){c[i].19.1R=\'46\'}}v(d){C g={};O(C s=0;s0&&6.1N===c[s].V(\'N-Q-O\')&&!g[6.1N]){6.2c(c[s]);g[6.1N]=W}K v(u.17(c[s],\'N-Q\')&&!u.2x(c[s])){6.2c(c[s])}}}u.23(6.L);v(b!==I){b(6)}}},2c:B(b){C c=b.2d(W);v(c.19.2V){c.19.2V(\'1R\')}K v(c.19.2W){c.19.2W(\'1R\')}K{c.19.1R=\'47\'}6.L=b.1J;C d=1j;O(C a=0;a]*?)N-\\1="(.*?)"/g,B(a,b,c){v(c!==\'\'){y b+\'="\'+c+\'"\'}y a})},2h:B(f,g,h){y h.T(6.31,B(a,b,c,d){v(f.2i.1h(b)){c=c.X(c.1p(\' \')).T(/^[ ]*|[ ]*$/g,\'\');C e=c.1m(/(?:[\'"])[ ]?,[ ]?(?:[\'"])/);y f.2i[b](f,g,a,e,d)}K{y\'\'}})},1X:B(a){6.1B=W;v(a===I){a=P R(R.14.1v,I,I)}u.1d(6.15,a);y 6},38:B(i,c){C d=6;y B(a){C r=P 1u(a);C b=J;v(c===\'*\'||i.1h(c.1m(\'.\')[0])){v(c===\'*\'){b=i}K{b=1e(\'i.\'+c)}v(b){1C{v(u.18(b)===\'1L\'){O(C s=0;s0&&k.Z.1U(/19|N-Q.*/)===J){k.11=6.2h(6,i,k.11);k.11=6.2g(6,g,i,k.11)}}C l=f.1a.T(/%3b%3b/g,\'{{\').T(/%3c%3c/g,\'}}\');l=6.2h(6,i,l);l=6.2g(6,g,i,l);l=6.35(6,g,i,l,e);l=6.37(6,i,l);d.1Q(u.2z(f,l));u.1d(6.15,P R(R.14.2t,i,f))}},2k:B(a){v(a){C b={};C c=q.16.4f();v(u.18(a)===\'1q\'){v(6.G.2a){C d=[];O(C e 1n a){v(a.1h(e)&&e!==\'2j\'){C f={};f.4g=e;f.11=a[e];d.33(f)}}a=d}K{a=[a]}}O(C i=0;i=0;i--){v(6.G.L.U[i]!==I&&6.G.L.U[i].V!==I&&6.G.L.U[i].V(\'N-4l-Q\')!==J){c=6.G.L.U[i]}}v(c===J){c=6.G.L.2D}v(c!==J){6.G.L.3e(b,c)}K{6.G.L.1Q(b)}}}u.1d(6.15,P R(R.14.22,a,6.G.L));y 6},3f:B(a){v(!6.1B){6.1X(P R(R.14.1v,a,6.G.L))}C b=6.2k(a);v(b!==J){C c=J;O(C i=0;i<6.G.L.U.F;i++){v(6.G.L.U[i]!==I&&6.G.L.U[i].V!==I&&6.G.L.U[i].V(\'N-3g-Q\')!==J){c=6.G.L.U[i]}}v(c===J){c=6.G.L.4m}v(c!==J){v(c.3h!==J&&c.V&&c.V(\'N-3g-Q\')!==J){c=c.3h}6.G.L.3e(b,c)}K{6.G.L.1Q(b)}}u.1d(6.15,P R(R.14.22,a,6.G.L));y 6},4n:B(a){6.1T=a;y 6},1E:B(a){v(6.1T!==J){6.1T.1F(6,a)}},3d:B(){u.1d(6.15,P R(R.14.2u,{},6.G.L));u.23(6.G.L);u.1d(6.15,P R(R.14.2v,{},6.G.L))},2i:{\'v\':B(b,i,c,d,e){C f=u.1H(i);C g=d[0].T(/&3i;/g,\'&\').T(/&3j;/g,\'>\').T(/&3k;/g,\'<\');g=g.T(P 1c(f,\'4o\'),B(a){y\'i.\'+a});C h=P 1c(b.G.1y+\'[ ]?K[ ]?\'+b.G.1z,\'g\');C j=e.1m(h);v(1e(g)){y j[0]}K v(j.F>1){y j[1]}y\'\'}},1S:{\'1O\':B(a,b){y a.2m().T(/[&<>]/g,B(c){y{\'&\':\'&3i;\',\'<\':\'&3k;\',\'>\':\'&3j;\'}[c]||c})},\'3l\':B(a,b){y 3l(a.2m())},\'3m\':B(a,b){y 3m(a.2m())},\'4p\':B(a,b){v(a!==I){C c=0;C d=\'...\';v(b.F>0){c=3n(b[0],10)}v(b.F>1){d=b[1]}v(a.F>c-3){y a.1W(0,c-3)+d}y a}},\'4q\':B(a,b){v(a!==I){v(b.F===1){a=4r(a+\'\').1g(3n(b[0],10))}C x=(a+\'\').1m(\'.\');C c=x[0];C d=x.F>1?\'.\'+x[1]:\'\';1I(o.3o(c)){c=c.T(o,\'$1\'+\',\'+\'$2\')}y c+d}},\'4s\':B(a,b){y a.3p()},\'4t\':B(a,b){y a.1K()},\'4u\':B(a,b){C c=[];v(b!==I&&b.F===1){c=b[0].1m(\' \')}y a.T(/\\w[a-z]\\S*/g,B(m,i){v(c.F===0||!(u.2N(c,m)&&i>0)){y m.4v(0).3p()+m.1W(1).1K()}y m})},\'1o\':B(a,b){y u.1o(a)},\'T\':B(a,b){v(a!==I&&b.F===2){y a.T(P 1c(b[0],\'g\'),b[1])}y a},\'2l\':B(a,b){v(a!==I&&b.F===1){y a+\'\'+b[0]}y a},\'3f\':B(a,b){v(a!==I&&b.F===1){y b[0]+\'\'+a}y a},\'3q\':B(a,b){v(b.F===1&&a!==I&&u.18(a)===\'1L\'){y a.3q(b[0])}y a},\'4w\':B(a,b){v(a!==I&&a!==J){y a}v(b.F===1){y b[0]}y a},\'24\':B(d,e){v(d!==I&&e.F>=1&&e.F<=2){C f=P 2F(d);C g=e[0];C h=(e.F===2&&e[1]===\'4x\');v(g===\'4y\'){y f.4z()}K v(g===\'4A\'){y f.4B()}K v(g===\'24\'){y f.4C()}K v(g===\'4D\'){y f.4E()}K{C i=[\'4F\',\'4G\',\'4H\',\'4I\',\'4J\',\'4K\',\'4L\',\'4M\',\'4N\',\'4O\',\'4P\',\'4Q\'];C j=[\'4R\',\'4S\',\'4T\',\'4U\',\'4V\',\'4W\',\'4X\'];C k={\'4Y\':B(a){y(h?a.3r():a.3s())},\'4Z\':B(a){y(h?a.3r():a.3s()).1g().X(2)},\'50\':B(a){y i[(h?a.1Y():a.1Z())]},\'51\':B(a){y i[(h?a.1Y():a.1Z())].X(0,3)},\'52\':B(a){y u.1i(((h?a.1Y():a.1Z())+1).1g(),\'0\',2)},\'M\':B(a){y(h?a.1Y():a.1Z())+1},\'53\':B(a){y u.1i((h?a.3t():a.3u()).1g(),\'0\',2)},\'D\':B(a){y(h?a.3t():a.3u())},\'54\':B(a){y j[(h?a.2n():a.2o())]},\'55\':B(a){y j[(h?a.2n():a.2o())].X(0,3)},\'E\':B(a){y(h?a.2n():a.2o())},\'56\':B(a){y u.1i((h?a.20():a.21()).1g(),\'0\',2)},\'H\':B(a){y(h?a.20():a.21())},\'h\':B(a){C b=(h?a.20():a.21());y b<13?(b===0?12:b):b-12},\'57\':B(a){y u.1i((h?a.3v():a.3w()).1g(),\'0\',2)},\'m\':B(a){y(h?a.3v():a.3w())},\'58\':B(a){y u.1i((h?a.3x():a.3y()).1g(),\'0\',2)},\'s\':B(a){y(h?a.3x():a.3y())},\'59\':B(a){y u.1i((h?a.3z():a.3A()).1g(),\'0\',3)},\'S\':B(a){y(h?a.3z():a.3A())},\'a\':B(a){y(h?a.20():a.21())<12?\'5a\':\'5b\'}};g=g.T(/(\\\\)?(Y{2,4}|M{1,4}|D{1,2}|E{1,4}|H{1,2}|h|m{1,2}|s{1,2}|S{1,3}|a)/g,B(a,b,c){v(!b){v(k.1h(c)){y k[c](f)}}y c});y g}}y\'\'}}};n.3B=B(a){q=a;y 6};n.5c=B(b,c,d){b=u.L(b);C e=P 1k(c);v(d!==I){e.1A(b,B(a){d(P 1u(a))})}K{e.1A(b);y P 1u(e)}};n.3C={\'G\':1k,\'3D\':u};n.3o={\'3D\':u,\'G\':P 1k({}),\'5d\':P 1u(P 1k({}))};1C{n.3B(5e)}1D(e){3C.5f=n}y n})(2w||{});',62,326,'||||||this|||||||||||||||||||||||||if|||return|||function|var|||length|templates||undefined|null|else|container||data|for|new|template|TempoEvent||replace|childNodes|getAttribute|true|substring||name||value|||Types|listener|document|hasAttr|typeOf|style|innerHTML|typeof|RegExp|notify|eval|err|toFixed|hasOwnProperty|pad|false|Templates|attributes|split|in|trim|indexOf|object|constructor|removeAttr|file|Renderer|RENDER_STARTING|startsWith|namedTemplates|tag_brace_left|tag_brace_right|parse|started|try|catch|_onError|call|type|memberRegex|while|parentNode|toLowerCase|array|defaultTemplate|nestedItem|escape|contents|appendChild|display|filters|errorHandler|match|filter_args|substr|starting|getUTCMonth|getMonth|getUTCHours|getHours|RENDER_COMPLETE|clearContainer|date|string|getElementById|prototype|var_brace_left|var_brace_right|dataIsMap|body|createTemplate|cloneNode|z0|_getValue|_replaceVariables|_applyTags|tags|_parent|_createFragment|append|toString|getUTCDay|getDay|use|strict|element|ITEM_RENDER_STARTING|ITEM_RENDER_COMPLETE|BEFORE_CLEAR|AFTER_CLEAR|Tempo|isNested|equalsIgnoreCase|getElement|createElement|table|tbody|lastChild|setAttribute|Date|HTMLElement|instanceof|jQuery|jquery|hasAttribute|merge|getElementsByTagName|arrayContains|params|load|contentWindow|contentDocument|attachEvent|onload|_insertTemplate|removeAttribute|removeProperty|templateFor|varRegex|Za|_|tagRegex|filterSplitter|push|_tempo|_replaceObjects|self|_applyAttributeSetters|_renderNestedItem|render|renderItem|7B|7D|clear|insertBefore|prepend|before|nextSibling|amp|gt|lt|encodeURI|decodeURI|parseInt|test|toUpperCase|join|getUTCFullYear|getFullYear|getUTCDate|getDate|getUTCMinutes|getMinutes|getUTCSeconds|getSeconds|getUTCMilliseconds|getMilliseconds|init|exports|utils|item|render_starting|item_render_starting|item_render_complete|render_complete|before_clear|after_clear|removeChild|navigator|appVersion|MSIE|tagName|tr|div|regex|nodeType|nodeName|html|Array|var_braces|tag_braces|documentElement|iframe|id|height|width|src|fallback|none|block|has|from|map|end|when|__|specified|createDocumentFragment|key|index|first|last|into|after|firstChild|errors|gi|truncate|format|parseFloat|upper|lower|titlecase|charAt|default|UTC|localedate|toLocaleDateString|localetime|toLocaleTimeString|toDateString|time|toTimeString|January|February|March|April|May|June|July|August|September|October|November|December|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|YYYY|YY|MMMM|MMM|MM|DD|EEEE|EEE|HH|mm|ss|SSS|AM|PM|prepare|renderer|window|tempo'.split('|'),0,{})) -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tempo Test Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

    Tempo Test Suite

    13 |

    14 |
    15 |

    16 |
      17 |
      18 |
        19 |
      1. Hello
      2. 20 |
      3. Fallback Template
      4. 21 |
      5. Dummy element
      6. 22 |
      23 |
      24 | 25 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Utils tests 3 | */ 4 | module('Utils'); 5 | var utils = Tempo.test.utils; 6 | 7 | test('memberRegex', function () { 8 | equal(utils.memberRegex({ 'foo' : 'bar', 'zoo' : 'doo' }), '(foo|zoo)[\\.]?(?!\\w)', 'Created RegEx testing for object members'); 9 | }); 10 | 11 | test('pad', function () { 12 | deepEqual(utils.pad('3', '0', 3), '003', 'Padding string with zeros'); 13 | }); 14 | 15 | test('trim', function () { 16 | equal(utils.trim(' hello '), 'hello', 'Trimmed whitespace'); 17 | equal(utils.trim('hello'), 'hello', 'No whitespace to trim'); 18 | }); 19 | 20 | test('startsWith', function () { 21 | ok(utils.startsWith('hello', 'he'), 'Checking if string starts with correctly returns true'); 22 | ok(!utils.startsWith('hello', 'lo'), 'Checking if string starts with correctly returns false'); 23 | }); 24 | 25 | test('equalsIgnoreCase', function () { 26 | ok(utils.equalsIgnoreCase('HeLlO', 'hElLo'), 'Equals ignore case with different case strings'); 27 | }); 28 | 29 | test('clearContainer', function () { 30 | var el = document.getElementById('container'); 31 | ok($(el).children('li').length === 3, 'Container has three elements (2 templates, one regular)'); 32 | utils.clearContainer(el); 33 | ok($(el).children('li').length === 2, 'All child elements removed'); 34 | }); 35 | 36 | 37 | /*! 38 | * Tags tests 39 | */ 40 | module('Tags'); 41 | 42 | 43 | /*! 44 | * Filters tests 45 | */ 46 | module('Filters'); 47 | var filters = Tempo.test.renderer.filters; 48 | 49 | test('truncate', function () { 50 | equal(filters['truncate'](undefined, []), undefined, 'No value'); 51 | equal(filters['truncate']('Hello world!', [8]), 'Hello...', 'Truncating'); 52 | equal(filters['truncate']('Hello world!', [20]), 'Hello world!', 'No truncation'); 53 | }); 54 | 55 | test('format', function () { 56 | equal(filters['format'](undefined, []), undefined, 'No value'); 57 | equal(filters['format'](100, []), '100', 'No formatting required'); 58 | equal(filters['format'](1000, []), '1,000', 'No formatting required'); 59 | equal(filters['format'](1000000.10, []), '1,000,000.1', 'No formatting required'); 60 | }); 61 | 62 | test('upper', function () { 63 | equal(filters.upper('Hello'), 'HELLO', 'Uppercase filter'); 64 | }); 65 | 66 | test('lower', function () { 67 | equal(filters.lower('hELLO'), 'hello', 'Lowercase filter'); 68 | }); 69 | 70 | test('titlecase', function () { 71 | equal(filters.titlecase('snow white and the seven dwarfs', ['and the']), 'Snow White and the Seven Dwarfs', 'Capitalize filter with blacklist values'); 72 | equal(filters.titlecase('the last of the mohicans', ['the of']), 'The Last of the Mohicans', 'Capitalize filter with first word in blacklist'); 73 | equal(filters.titlecase('FIRE bug'), 'FIRE Bug', 'Ignoring capitalized words'); 74 | }); 75 | 76 | 77 | test('trim', function () { 78 | equal(filters.trim(' hello '), 'hello', 'Trimmed whitespace'); 79 | equal(filters.trim('hello'), 'hello', 'No whitespace to trim'); 80 | }); 81 | 82 | test('replace', function () { 83 | equal(filters.replace('foot simpson', ['foo', 'bar']), 'bart simpson', 'Replacing simple value'); 84 | equal(filters.replace('foo 123 bar', ['([0-9]+)', '|$1|']), 'foo |123| bar', 'Replacing value with backreference'); 85 | equal(filters.replace('http://github.io', ['^http://', '']), 'github.io', 'Replacing with an empty string'); 86 | equal(filters.replace(undefined, ['([0-9]+)', '|$1|']), undefined, 'Trying to replace in undefined value'); 87 | }); 88 | 89 | test('append', function() { 90 | equal(filters.append('foo', [' bar']), 'foo bar', 'Appending value with space'); 91 | equal(filters.append('foo', []), 'foo', 'Append with no arguments'); 92 | }); 93 | 94 | test('prepend', function () { 95 | equal(filters.prepend('bar', ['foo ']), 'foo bar', 'Prepending value with space'); 96 | equal(filters.prepend('foo', []), 'foo', 'Prepend with no arguments'); 97 | }); 98 | 99 | test('default', function () { 100 | equal(filters['default'](undefined, ['foo']), 'foo', 'Default value for undefined'); 101 | equal(filters['default'](null, ['foo']), 'foo', 'Default value for null'); 102 | equal(filters['default']('bar', ['foo']), 'bar', 'Default value should not be used'); 103 | deepEqual(filters['default'](undefined, []), undefined, 'No default value, returns original even if empty'); 104 | }); 105 | 106 | test('date', function () { 107 | var date = new Date(1283359805000); 108 | equal(filters.date(undefined, []), '', 'Undefined date'); 109 | equal(filters.date(date, ['localedate']), '1 September 2010', 'Date to localedate'); 110 | equal(filters.date(date, ['localetime']), '17:50:05 BST', 'Date to localetime'); 111 | equal(filters.date(date, ['date']), 'Wed Sep 01 2010', 'Date to date'); 112 | equal(filters.date(date, ['time']), '17:50:05 GMT+0100 (BST)', 'Date to localetime'); 113 | equal(filters.date(date, ['YYYY YY MMMM MMM MM M EEEE EEE E DD D HH H mm m ss s SSS S a']), '2010 10 September Sep 09 9 Wednesday Wed 3 01 1 17 17 50 50 05 5 000 0 PM', 'Date to formatted with pattern'); 114 | equal(filters.date(date, ['EEE \\at HH:mm']), 'Wed at 17:50', 'Date to string with escaping'); 115 | }); 116 | 117 | test('filters member regex', function () { 118 | equal(utils.memberRegex(filters), '(truncate|format|upper|lower|titlecase|trim|replace|append|prepend|join|default|date)[\\.]?(?!\\w)', 'Regex of all filter names'); 119 | }); 120 | 121 | /*! 122 | * Templates tests 123 | */ 124 | module('Templates'); 125 | test('prepare', function() { 126 | var template = Tempo.prepare('container'); 127 | ok(template !== undefined, 'Template was created from element ID'); 128 | }); 129 | 130 | /*! 131 | * Renderer tests 132 | */ 133 | module('Renderer'); 134 | var renderer = Tempo.test.renderer; 135 | var item = {'$foo': 'bar'}; 136 | var array = ['foo', 'bar']; 137 | 138 | var str = 'Sample {{ $foo }} string.'; 139 | var str2 = 'Sample {{.}} string.'; 140 | var str3 = 'Sample {{$foo | replace "a+" , \'\' }} string.'; 141 | 142 | test('_replaceVariables', function () { 143 | equal(renderer._replaceVariables(renderer, {}, item, str), 'Sample bar string.'); 144 | equal(renderer._replaceVariables(renderer, {}, item, str2), 'Sample [object Object] string.'); 145 | equal(renderer._replaceVariables(renderer, {}, array, str2), 'Sample foo,bar string.'); 146 | equal(renderer._replaceVariables(renderer, {}, item, str3), 'Sample br string.'); 147 | }); 148 | 149 | test('_replaceObjects', function () { 150 | var regex = new RegExp('(?:__[\\.]?)((_tempo|\\[|' + utils.memberRegex(['foo', 'bar']) + '|this)([A-Za-z0-9$\\._\\[\\]]+)?)', 'g') 151 | equal(renderer._replaceObjects(renderer, {}, ['foo', 'bar'], '{{[0]}}', regex), '{{[0]}}'); 152 | }); 153 | --------------------------------------------------------------------------------