UNPKG

8.9 kBMarkdownView Raw
1rincewind
2===
3
4An HTML based template engine with a few ever-so-slightly magic attributes.
5
6[![NPM](https://nodei.co/npm/rincewind.png?compact=true)](https://nodei.co/npm/rincewind/)
7
8## Example
9
10Here's some data we want to display to the user. It's a basic blog:
11
12```js
13var data = {
14
15 post: {
16 title: "Rincewind! The not really but sort of magic templating engine.",
17 date: "1 April 2013",
18 body: "A lot of text with clever puns etc."
19 },
20
21 comments: [
22 { name: 'Joe Blogs',
23 date: '2 April 2013',
24 body: "Get it? My name is Joe and a blog ;)"
25 },
26 { name: 'Anonymous Coward',
27 date: '2 April 2013',
28 body: "I am a coward."
29 }
30 ]
31
32}
33```
34
35And here's a view to make it browsery:
36
37```html
38<!-- post.html -->
39<? require './markdown.js' as markdown ?>
40<? require './widget.html' as widget ?>
41
42<article class='Post'>
43 <header>
44 <h1 t:bind='post.title' />
45 <p t:bind='post.date' />
46 </header>
47 <div t:bind='post.body' t:view='markdown' />
48
49 <section class='comments'>
50 <header>Comments</header>
51
52 <div t:repeat='comments'>
53 <header>
54 <strong t:bind='.name' /> on
55 <span t:bind='.date' />
56 </header>
57 <div t:bind='.body' t:view='markdown' />
58 </div>
59
60 </section>
61
62 <aside t:view='widget' />
63</article>
64```
65
66```html
67<!-- widget.html -->
68<h1>Cool Links!</h1>
69<ul>
70 <li><a href='https://github.com'>Github</a></li>
71 <li><a href='http://nodejs.org'>Node.js</a></li>
72 <li><a href='https://npmjs.org'>npm</a></li>
73</ul>
74```
75
76```js
77// markdown.js
78var marked = require('marked')
79module.exports = function(context){
80 return marked(context.source)
81}
82```
83
84And a master/layout to hold it:
85
86```html
87<!-- master.html -->
88<html>
89 <head>
90 <title>Matt's Blog</title>
91 <link rel='stylesheet' href='/styles.css' />
92 </head>
93 <body>
94
95 <header>
96 <h1><a href='/'>Matt's Blog</a></h1>
97 <p>Not just another wordpress</p>
98 </header>
99
100 <div>
101 <t:placeholder t:content />
102 </div>
103
104 <footer>Powered by Node, NPM and You</footer>
105 </body>
106
107</html>
108```
109
110### Now let's hook it all up!
111
112Create our views and do some databinding with json-query.
113
114```js
115var fs = require('fs')
116var View = require('rincewind')
117var jsonQuery = require('json-query')
118
119var master = View(__dirname + '/master.html')
120var renderView = View(__dirname + '/post.html')
121
122function respond(req, res, data){
123 var queryHandler = function(query){
124 return jsonQuery(query, {
125 rootContext: data,
126 context: this.source,
127 override: this.override
128 }).value
129 }
130
131 var html = master({
132 get: queryHandler
133 content: renderView({get: queryHandler})
134 })
135
136 res.end(html)
137}
138
139var server = http.createServer(function(req,res){
140 respond(req, res, data)
141})
142```
143
144### Inline views
145
146```js
147var View = require('rincewind')
148var renderView = View({parse: '<div><div t:bind="value" /></div>'})
149```
150
151## Preloading views
152
153### Manually
154
155```js
156var View = require('rincewind')
157var precompiled = View(__dirname + '/view.html').getCompiledView()
158```
159
160Then send the precompiled value to the browser.
161
162```js
163// recreate in the browser
164var View = require('rincewind')
165var renderView = View(precompiled)
166```
167
168### Browserify transform
169
170Or you can use [rincewind-precompile-transform](https://github.com/mmckegg/rincewind-precompile-transform) which will automatically compile and inline any rincewind views in your source for the browser bundle.
171
172```bash
173$ browserify entry.js -t rincewind-precompile-transform > output.js
174```
175
176### Watch script
177
178For more control use [rincewind-watch](https://github.com/mmckegg/rincewind-watch) to and trigger callbacks on changes and manually compile to javascript (and call `view.stringify(relativePath)`). The compiled file can then be required by your code.
179
180
181## The magic t:attributes
182
183
184### Attribute: `t:bind`
185
186Any time the system hits a `t:bind` attribute while rendering the view, it calls the `queryHandler` function with the value and additional context info (`parent`, `source` etc).
187
188### Attribute: `t:bind:<attribute-name>`
189
190We can bind arbitrary attributes using the same method by using `t:bind:<attribute-name>`.
191
192For example, if we wanted to bind an element's ID to the query `element_id`:
193
194```html
195<span t:bind:id='element_id'>content unchanged</span>
196```
197
198Which would output:
199
200```html
201<span id='value'>content unchanged</span>
202```
203
204### Attribute: `t:if`
205
206The element will only be rendered if the specified query **returns a truthy value**.
207
208```html
209<span t:if='show_content'>Only shows if show_content returns true</span>
210```
211
212### Attribute: `t:unless`
213
214The inverse of `t:if`. The element will only be rendered if the specified query **returns a falsy value**.
215
216### Attribute: `t:by` and `t:when`
217
218An extension of the if system. Much like a `switch` or `case` statement. Specify the source query using `t:by` then any sub-elements can use `t:when` to choose what value the `t:by` query must return in order for them to show. Multiple values may be specified by separating with the pipe symbol (e.g. `value1|value2|value3`).
219
220```html
221<div t:by='type'>
222 <div t:when='example'>
223 This div is only rendered if the query "type" returns the value "example".
224 </div>
225 <div t:when='production'>
226 This div is only rendered if the query "type" returns the value "production".
227 </div>
228 <div t:when='trick|treat'>
229 This div is rendered when the query "type" returns the value "trick" or "treat".
230 </div>
231</div>
232```
233
234### Attribute: `t:repeat`
235
236For binding to arrays and creating repeating content. The attribute value is queried and the element is duplicated for every item in the returned array.
237
238For this [JSON Context](http://github.com/mmckegg/json-context) datasource:
239
240```js
241var datasource = JsonContext({
242 posts: [
243 {id: 1, title: "Post 1", body: "Here is the body content"},
244 {id: 2, title: "Post 2", body: "Here is some more body content"},
245 {id: 3, title: "Post 3", body: "We're done."},
246 ]
247})
248```
249
250And this template:
251
252```html
253<div class='post' t:repeat='posts' t:bind:data-id='.id'>
254 <h1 t:bind='.title'>Will replaced with the value of title</h1>
255 <div t:bind='.body'>Will replaced with the value of body</div>
256</div>
257```
258
259We would get:
260
261```html
262<div class='post' data-id='1'>
263 <h1>Post 1</h1>
264 <div>Here is the body content</div>
265</div>
266<div class='post' data-id='2'>
267 <h1>Post 2</h1>
268 <div>Here is some more body content</div>
269</div>
270<div class='post' data-id='3'>
271 <h1>Post 3</h1>
272 <div>We're done.</div>
273</div>
274```
275
276If required (e.g. nesting repeaters) you can use `t:as` to assign the context a name and reference it by that instead of '.' - this will only work if `templateContext.override` is handled by the query engine.
277
278```html
279<div class='post' t:repeat='posts' t:as='post' t:bind:data-id='.id'>
280 <div t:repeat='something_else'>
281 Can still access the post!
282 <span t:bind='post.name' />
283 </div>
284</div>
285```
286### Attribute: `t:view`
287
288Specify a sub-view to render as the content of the element.
289
290```html
291<!-- view.html -->
292<? require './subview.html' as subview ?>
293<div>
294 <div t:view='subview' />
295</div>
296```
297
298```html
299<!--- subview.html -->
300<div>Sub-view content</div>
301```
302
303Format content in specific way:
304
305```html
306<? require './format.html' as format ?>
307<div>
308 <div t:bind='contact' t:view='format' />
309</div>
310```
311
312```html
313<!--- format.html -->
314<strong><span t:bind='.name' />:</strong> <span t:bind='.address' />
315```
316
317Or require javascript view:
318
319```html
320<? require './markdown.html' as markdown ?>
321<div>
322 <div t:bind='body' t:view='markdown' />
323</div>
324```
325
326```js
327// markdown.js
328var marked = require('marked')
329module.exports = function(context){
330 return marked(context.source)
331}
332```
333
334Wrap content using javascript:
335
336```html
337<? require './wrapper.html' as wrap ?>
338<div>
339 <t:placeholder t:view='wrap' />
340</div>
341```
342
343```js
344// wrapper.html
345var marked = require('marked')
346module.exports = function(context){
347 return '<strong>' + context.content + '</strong>'
348}
349```
350
351If the element had content specified, it will be overrided with the content of the subview, but if the subview contains an element with the attribute `t:content`, the removed content will be inserted here. This allows creating views that act like wrappers.
352
353### Attribute: `t:content`
354
355This attribute accepts no value and is used to denote where to insert inner content.
356
357Say we have this master layout:
358
359```html
360<!--/views/layout.master.html-->
361<html>
362 <head>
363 <title>My Blog</title>
364 </head>
365 <body>
366 <h1>My Blog</h1>
367 <div t:content id='content'></div>
368 </body>
369</html>
370```
371
372And this view:
373
374```html
375<!--/views/content.html-->
376<h2>Page title</h2>
377<div>I am the page content</div>
378```
379
380We would get:
381
382```html
383<html>
384 <head>
385 <title>My Blog</title>
386 </head>
387 <body>
388 <h1>My Blog</h1>
389 <div id='content'> <!--inner view is inserted here-->
390 <h2>Page title</h2>
391 <div>I am the page content</div>
392 </div>
393 </body>
394</html>
395```