-
Notifications
You must be signed in to change notification settings - Fork 60
/
README.vash
1183 lines (747 loc) · 41.7 KB
/
README.vash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
@{
// shortcuts
var fn = html.mdFn.bind(html)
,toc = html.mdToc.bind(html)
,lvl1 = html.mdTocLvlX.bind(html, 1)
,lvl2 = html.mdTocLvlX.bind(html, 2)
// Shortcut some of the html parsing.
function echo(input) {
return html.raw(input)
}
}
Vash
====
_"... the 60 billion double-dollar template-maker!"_ ~ The previous README, and no one else, ever.
Vash is a template engine that offers a swift flow between code and content using [Razor Syntax][] @fn('razor-ms'). This document @fn('this-doc') is intended for users of Vash, and also serves as a reference for Vash's implementation of Razor Syntax.
[Razor Syntax]: http://www.asp.net/web-pages/tutorials/basics/2-introduction-to-asp-net-web-programming-using-the-razor-syntax
[![Build Status](https://secure.travis-ci.org/kirbysayshi/vash.png?branch=master)](https://travis-ci.org/kirbysayshi/vash)
[![NPM](https://nodei.co/npm/vash.png?downloads=true&stars=true)](https://nodei.co/npm/vash/) [![NPM](https://nodei.co/npm-dl/vash.png)](https://nodei.co/npm/vash/)
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/kirbysayshi/vash/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
@toc()
Features @lvl1()
================
* Mix code and content without ugly delineators, like `<?`, `<%`, or `{{`.
* No new language to learn: Vash is just HTML-aware JavaScript.
* Great with markup, but can be used with nearly any other language as well (even Markdown!).
* Helpers API allows for extensibility and meta programming.
* Works in the browser or in node.
* Comes with a Jade-inspired layout engine (block, include, extend, append/prepend), which even works in the browser.
Syntax Example @lvl1()
======================
<p>How are you @@model.name? Today is a sunny day on the planet Gunsmoke.</p>
<ul class="@@(model.active ? 'highlight' : '')">
@@model.forEach(function(m){
<li>@@m.name</li>
})
</ul>
Quick Start @lvl1()
===================
nodejs @lvl2()
--------------
var vash = require('vash');
var tpl = vash.compile('<p>I am a @@model.t!</p>');
var out = tpl({ t: 'template' });
// <p>I am a template!</p>
express @lvl2()
--------------------
Check out [vash-express-example][] for a full example of hooking up vash as a view engine for express 3. But it's basically as simple as:
var express = require('express');
var app = express();
app.set('view engine', 'vash');
More information is also available in the [Layout Helpers][] sections.
[vash-express-example]: https://github.com/kirbysayshi/vash-express-example
Browser - Vanilla @lvl2()
-------------------------
<script type="text/javascript" src="build/vash.js"></script>
var tpl = vash.compile( '<p>I am a @@model.t!</p>' );
document.querySelector('#content').innerHTML = tpl({ t: 'template' });
But you should probably be precompiling your templates. See [Precompiling Templates][] for more info. Then you can just include the Vash runtime instead of the entire compiler.
Browser - Browserify et al @lvl2()
----------------------------------
Just `require` Vash, and compile. If you want something fancier, try [vashify](https://www.npmjs.com/package/vashify)! Then you can directly require any `.vash` file and it will be resolved as compiled template:
var tpl = require('my-awesome-template.vash');
document.querySelector('#content').innerHTML = tpl({ t: 'template' });
Browser - RequireJS @lvl2()
---------------------------
RequireJS support has been recently dropped. However Vash does support CJS environments, so as long as you configure RequireJS to consume Vash as a CJS project (including `node_modules` resolution), everything should work.
Playground @lvl1()
==================
Vash now has [a playground][] of sorts at [CodePen.io][]. It uses the current version of `vash.js` from the `build` folder. Fork it to test your own template ideas!
[a playground]: http://codepen.io/kirbysayshi/full/IjrFw
[CodePen.io]: http://codepen.io
Syntax @lvl1()
==============
For the following examples, assume a model is passed into the compiled function. If a model is explicitly defined, it will appear as:
// model = { what: 'hello!' }
The Transition Character: @@ @lvl2()
------------------------------------
Vash uses the `@@` symbol to transition between code and markup. To escape and print a literal `@@`, use a double `@@`, like this: `@@@@`.
Expressions @lvl2()
-------------------
The most basic usage of Vash is an implicit expression. Vash is smart enough to know what's valid JS and what's not, and can usually do what you want it to do. An expression is an @@ followed by a valid JS identifier. This is then interpolated automatically.
input:
// model = { what: 'hello!' }
<p>@@what</p>
output:
<p>hello!</p>
The `model` comment is just to show that the object passed into the compiled template contains a key that matches the expression.
To allow for the fastest render time possible, Vash by default requires the model to be addressed explicitly. This is to avoid using a `with` statment in the compiled template, which is approximately 25 times slower. The above example then becomes:
input:
<p>@@model.what</p>
output:
<p>hello!</p>
As you can see, the output is exactly the same. The name used to reference the model is configurable via [vash.config.modelName][]. Typical values are `model` and `it`.
Advanced Expressions @lvl2()
----------------------------
Vash typically knows when an expression ends, even when the expression is complex. For example:
input:
<p>@@model.what().who[2]('are you sure')('yes, it\'s ok')( model.complex ? 'FULL POWER' : '' )</p>
This will work just fine, assuming you have a model that actually contains that complexity! I hope you don't, and if so, I feel bad.
Callbacks work as well:
input:
// model = ['a', 'b']
@@model.forEach(function(item){
<li>@@item</li>
})
outputs:
<li>a</li><li>b</li>
Vash also knows the difference between JS dot notation and a period.
input:
// model = { description: 'living' }
<p>Plants are @@model.description.</p>
output:
<p>Plants are living.</p>
And empty brackets, because they're not valid JS:
input:
// model = { formName: 'addresses' }
<input type="text" name="@@model.formName[]" />
output:
<input type="text" name="addresses[]" />
Email addresses, to an extent, are fine as well. Vash makes a trade-off. It uses the following regex to validate an email address:
/^([a-zA-Z0-9.%]+@@[a-zA-Z0-9.\-]+\.(?:ca|co\.uk|com|edu|net|org))\b/
Email addresses can actually contain many more valid characters, and are [really hard to validate][]. Vash can handle a typical email address with ease:
input:
<a href="mailto:[email protected]">Email Me</a>
output:
<a href="mailto:[email protected]">Email Me</a>
If you have a complex email address that confuses Vash, then you should use an [explicit expression](#explicit-expressions) instead.
[really hard to validate]: http://www.regular-expressions.info/email.html
Explicit Expressions @lvl2()
----------------------------
An explicit expression is simply an expression that, instead of being composed of `@@` and a valid JS identifier, is surrounded by parenthesis.
input:
<p>@@(model.what)</p>
output:
<p>hello!</p>
Why would you ever need this? Perhaps you want to do something like:
input:
// model = { hasIceCream: true }
<p class="@@( model.hasIceCream ? 'ice-cream' : '')">Ice Cream</p>
output:
<p class="ice-cream">Ice Cream</p>
You could even create an anonymous function.
input:
@@(function(type){ return type + ' cream'; }('banana'))
output:
banana cream
As you can see, Vash does not require a model to be referenced, or even passed in.
Code Blocks @lvl2()
-------------------
Sometimes, AGAINST ALL ODDS, a template may need some quick computation of values to avoid repeating yourself. Unlike expressions and explicit expressions, a code block does not directly output. To compare to PHP, expressions are like `<?= $what ?>`, while a code block is like `<? $what = 'what' ?>`.
A code block is simply `@@{ }`.
input:
@@{ var rideOn = 'shooting star'; }
output:
That's right, _nothing_! Here's a better example:
input:
@@{
var total = model.price + model.tax;
}
<p>Your total is: $@@total</p>
output:
<p>Your total is: $2.70</p>
Anything is valid within a code block, such as function declarations or even something as complex as defining a prototype. You can also use markup within a code block, and it will behave as expected:
input:
@@{ <p>This works!</p> }
output:
<p>This works!</p>
A code block just tells Vash, "expect the next stuff to be code until otherwise".
Keyword Blocks @lvl2()
----------------------
Vash is aware of keywords, and will open a code block automatically for you.
input:
// model = { type: 'banana' }
@@if(model.type){
<p>I'm a @@model.type!</p>
} else if(model.name){
<p>My name is @@model.name.</p>
} else {
<p>I DON'T KNOW WHO OR WHAT I AM...</p>
}
output:
<p>I'm a banana!</p>
This also works for `while`, `for`, `do`, `try/catch`, `with`, `switch`, `function`, and other keywords.
You don't even need to worry about whitespace or newlines:
input:
// model = 1
@@switch(model){case 1:<p></p>break;case 2:<b></b>break;}
output:
<p></p>
Comments @lvl2()
----------------
Vash also supports comments that are not compiled into the template. These are delineated with `@@*` and `*@@`
input:
@@* I am a comment that extends
over multiple lines *@@
<p>BANANA!</p>
output:
<p>BANANA!</p>
HTML Escaping @lvl2()
---------------------
By default, Vash escapes any HTML-like values before outputting them.
input:
// model = { what: '<img />' }
<p>@@model.what</p>
output:
<p><img /></p>
If you are sure that you trust the content and/or need to display HTML-like values, you can escape the HTML escaping via a call to Vash's [helper system][]: `html.raw`.
input:
// model = { what: '<img />' }
<p>@@html.raw(model.what)</p>
output:
<p><img /></p>
This behavior can be disabled using [vash.config.htmlEscape][].
Explicit Markup @lvl2()
-----------------------
Sometimes you may wish to tell Vash that what you're typing is markup or content, as opposed to code. Take the following example:
input:
// model = ['a']
@@model.forEach(function(item){
this should be content @@item
})
output:
(Error when compiling)
In this situation, you have two options. The first is the `@@:` (at colon) escape. It tells Vash that until it sees a newline, treat the input as content, not code.
input:
// model = ['a']
@@model.forEach(function(item){
@@: this should be content @@item
})
output:
this should be content a
The other option, in the event that more than one line is needed, is by using an imaginary HTML tag named `@html.raw('<text>')`. When Vash sees this tag, it switches to content mode, and discards the tag. This means that the tag will never be output.
input:
// model = ['Indeed!']
@@model.forEach(function(item){
@html.raw('<text>')
This is some longer content that you
apparently wanted on multiple lines,
multiple times! @@item
@html.raw('</text>')
})
output:
This is some longer content that you
apparently wanted on multiple lines,
multiple times! Indeed!
Configuration @lvl1()
=====================
Vash has a few compilation options that are configurable either by setting the relevant value in `vash.config` or by passing in an object with that key/value to [vash.compile][], [vash.compileBatch][], or [vash.compileHelper][].
For example:
vash.config.debug = true;
Is the global version of:
vash.compile('<p>My tpl</p>', { debug: true });
vash.config.useWith @lvl2()
---------------------------
Default: false
If `useWith` is set to `true`, then Vash will wrap a `with` block around the contents of the compiled function.
// vash.config.useWith == true
<li>@@description</li>
vs
// vash.config.useWith == false
<li>@@model.description</li>
Rendering is the same regardless:
tpl( { description: 'I am a banana!' } );
// outputs:
// <li>I'm a banana!</li>
_Tech note: using a `with` block comes at a severe performance penalty (at least 25x slower!)._
vash.config.modelName @lvl2()
-----------------------------
Default: 'model'
If [vash.config.useWith][] is `false` (default), then this property is used to determine what the name of the default free variable will be. Example:
// vash.config.useWith == false
<li>@@model.description</li>
vs
// vash.config.useWith == false
// vash.config.modelName == 'whatwhat'
<li>@@whatwhat.description</li>
Again, rendering is the same regardless:
tpl( { description: 'I am a banana!' } );
// outputs:
// <li>I'm a banana!</li>
A common alternative to `model` is `it`.
vash.config.helpersName @lvl2()
-------------------------------
Default: 'html'
Determines the name of the free variable through which registered helper methods can be reached. Example:
<li>@@html.raw(model.description)</li>
vs
// vash.config.helpersName == "help";
<li>@@help.raw(model.description)</li>
Again, rendering is the same regardless:
tpl( { description : '<strong>Raw</strong> content!' } );
// outputs:
// <li><strong>Raw</strong> content!</li>
vash.config.htmlEscape @lvl2()
------------------------------
Default: true
As of version 0.4x, Vash automatically HTML encodes values generated by an explicit or implicit expression. To disable this behavior, set `htmlEscape` to `false`. For an more in depth example, see [HTML Escaping][].
If a value _should not_ be escaped, simply wrap it in a call to [vash.helpers.raw][].
vash.config.debug @lvl2()
-------------------------
Default: true
By default, templates are compiled with extensive debugging information, so if an error is thrown while rendering a template (not compiling), exact location (line, character) information can be given.
Using the following template:
<p></p>
A template with `debug` set to `true` (default):
function anonymous(model,html,__vopts,vash) {
try {
var __vbuffer = html.buffer;
html.options = __vopts;
model = model || {};
html.vl = 1, html.vc = 0;
__vbuffer.push('<p>');
html.vl = 1, html.vc = 3;
__vbuffer.push('</p>');
html.vl = 1, html.vc = 7;
__vbuffer.push('\n');
(__vopts && __vopts.onRenderEnd && __vopts.onRenderEnd(null, html));
return (__vopts && __vopts.asContext)
? html
: html.toString();
} catch( e ){
html.reportError( e, html.vl, html.vc, "<p></p>[email protected]('L')@html.raw('B')!" );
}
}
And that same template with `debug` set to `false`:
function anonymous(model,html,__vopts,vash) {
var __vbuffer = html.buffer;
html.options = __vopts;
model = model || {};
__vbuffer.push('<p></p>\n');
(__vopts && __vopts.onRenderEnd && __vopts.onRenderEnd(null, html));
return (__vopts && __vopts.asContext)
? html
: html.toString();
}
As you can see, the difference, especially in code size and instruction size is significant. For production apps, templates should be precompiled with `debug` as `false`.
vash.config.debugParser @lvl2()
-------------------------------
Default: false
Vash's parser will output useful debugging infomation if `debugParser` is `true`:
* Tokens and what mode they were processed as
* A textual representation of the fully parsed AST
vash.config.debugCompiler @lvl2()
---------------------------------
Default: false
Vash's compiler will output useful debugging information if `debugCompiler` is `true`:
* The text content of the template function before it is passed into [vash.link][] for actual evaluation
* The options passed into the compiler. This is useful for debugging [vash.compileBatch][] and [vash.compileHelper][].
vash.config.simple @lvl2()
--------------------------
Default: false
If `true`, the template is compiled in "fast path" mode. This disables several advanced features for the sake of speed:
* [onRenderEnd][] callbacks are completely ignored.
* The [Helper System][] instance normally available within a running template as `html` is no longer an instance of `vash.helpers.constructor`, and thus the entire buffer API and helpers are missing. Instead it is a plain object with the following properties:
* `buffer`: a plain array
* `escape`: [vash.helpers.escape][]
* `raw`: [vash.helpers.raw][]
* The [asContext][] runtime option is completely ignored.
* [vash.config.htmlEscape][], [vash.config.useWith][], and [vash.config.debug][] still behave as expected.
While standard Vash templates are definitely not slow, using `true` for this option decreases render time by 15% - 25% depending on the size of the template.
[vash-benchgraph](https://github.com/kirbysayshi/vash-benchgraph) can be used to show the speed increase:
node benches.js --tinclude 004.vash,007.vash --vinclude '0.6.2-2482' --chart vashv,ops
vash.config.favorText @lvl2()
-----------------------------
Default: false
When Vash encounters text that directly follows an opening brace of a block, it assumes that unless it encounters an HTML tag, the text is JS code. For example:
@@it.forEach(function(a){
var b = a; // vash assumes this line is code
})
When `favorText` is set to `true`, Vash will instead assume that most things are content (not code) unless it's very explicit.
@@it.forEach(function(a){
var b = a; // vash.config.favorText assumes this line is content
})
This option is __EXPERIMENTAL__, and should be treated as such. It allows Vash to be used in a context like [Markdown](http://daringfireball.net/projects/markdown/syntax), where HTML tags, which typically help Vash tell the difference between code and content, are rare.
Template Options @lvl1()
========================
These options concern rendering a template, after it has already been compiled. For options related to compiling templates, see [Configuration][].
The compiled templates themselves have three signatures.
tpl(model) -> string
The most basic form accepts a single argument, `model`, that can be any value: Number, Boolean, Object, Array, undefined, null, etc. It returns the rendered template as a string.
tpl(model, function(){}) -> string
The second form accepts a function callback as its second parameter, which is called [onRenderEnd][] (see below).
tpl(model, options) -> string
The third form allows for options in addition to [onRenderEnd][]. There are two options that can affect a template while rendering:
asContext @lvl2()
-----------------
tpl(model, { asContext: true }) -> vash.helpers.constructor
This option tells the template that instead of returning a string, it should return the "render context", otherwise known as an instance of `vash.helpers.constructor` ([Helper System][]).
onRenderEnd @lvl2()
-------------------
tpl(model, { onRenderEnd: function(){} }) -> string
This option is effectively a callback for once primary execution of the rendering template has finished. The arguments passed to the callback are: `( err, html )`, where `err` is always `null` (for now), and `html` is the render context (instance of `vash.helpers.constructor`). This callback is not required, and is only called if defined (and has no default definition). The [Layout Helpers][] use this to know when all includes, prepends, appends, blocks, and extend calls have finished.
[onRenderEnd][] can also be defined as a property of the model:
var model = { hey: 'what', onRenderEnd: function(err, ctx){ ... } }
Helper System @lvl1()
===============
Vash's primary point of expandability lies in its Helper API. When a template is rendering, there is a free variable avaiable. This variable is, by default, named `html`. This name can be changed with the [vash.config.helpersName][] option. `html` is an instance of the prototype that is attached to `vash.helpers`. It's a bit confusing, but this is how it kind of works:
var Helpers = function(){}
vash.helpers = Helpers.prototype;
vash.helpers.constructor = Helpers;
What this means is that any function that is attached to `vash.helpers` is available within a rendering template via `html`. For example:
// defined in a JS file or script tag somewhere
vash.helpers.echo = function(arg){ return arg; }
input:
<p>@@html.echo('hello!')</p>
output:
<p>hello!</p>
Here is a simple helper that converts text like "This is a holdup!" to "this-is-a-holdup":
vash.helpers.mdHref = function(text){
return text
.toLowerCase()
.replace(/[^a-zA-Z0-9-_]+/g, '-')
.replace(/^-+|\n|-+$/g, '');
}
Notice how it's just JavaScript. Within a template, it could be accessed via `html.mdHref("This is a holdup!")`.
Built-in Helpers @lvl1()
========================
vash.helpers.raw @lvl2()
------------------------
Available as `html.raw` within an executing template. By default, all content that passes from a model to a template is HTML encoded. In the event that the text is trusted (or is already encoded), wrap the text in this function. For an example, see [HTML Escaping][];
vash.helpers.escape @lvl2()
---------------------------
Available as `html.escape` within an executing template, this is the method Vash uses to HTML encode model values. It can also be used manually.
vash.helpers.tplcache @lvl2()
-----------------------------
The `tplcache` is just that, a place to put a global index of templates. This is used primarily for the more "view engine" aspects that Vash provides, as well as a default location for [precompiled templates][--target-namespace] using [vash(1)][].
Layout Helpers @lvl1()
======================
Vash provides a relatively simple but powerful view engine whose API is borrowed directly from [Jade][]. Below is the API, but an example can be found at [vash-express-example][].
Callbacks are used to maintain compatibility with typical JS syntax.
When running in [nodejs][] and using [express][], Vash will automatically resolve and load templates using the same conventions as express itself, specifically [app.engine][]. When in the browser, Vash uses the same rules, but looks in [vash.helpers.tplcache][] instead.
[nodejs]: http://nodejs.org
[express]: http://expressjs.com
[app.engine]: http://expressjs.com/api.html#app.engine
[Jade]: http://jade-lang.com
[vash-express-example]: https://github.com/kirbysayshi/vash-express-example/tree/master/views
[layout.vash]: https://github.com/kirbysayshi/vash-express-example/blob/master/views/layout.vash
[Layout.vash]: https://github.com/kirbysayshi/vash-express-example/blob/master/views/layout.vash
@function helpersDisclaimer(){
@:_Tech note: due to the way JS scoping works, the `model` parameter of the `cb` function must be explicitely defined as above if it is referenced in the content. This may change in a future version of Vash._
}
vash.helpers.extend @lvl2()
---------------------------
vash.helpers.extend(parent_path, cb)
This is Vash's main form of inheritance for view templates. `parent_path` is the location or name of the template to be extended.
A template can define various locations in itself that can be [overridden](#vash-helpers-block) or [added to](#vash-helpers-append). In addition, a template that calls `extend` can even be extended itself!
In the following example, this template extends another named [layout.vash][]. [Layout.vash][] defines an empty [block](#vash-helpers-block) named 'content', which is overrided in this example.
@@html.extend('layout', function(model){
@@html.block('content', function(model){
<h1 class="name">Welcome to @model.title</h1>
})
})
@helpersDisclaimer()
vash.helpers.block @lvl2()
--------------------------
vash.helpers.block(name)
A block is essentially a placeholder within a template that can be overridden via another call to [vash.helpers.block][], or modified using [vash.helpers.append][] and [vash.helpers.prepend][].
vash.helpers.block(name, cb)
If `cb` is defined, then it becomes default content for the block. The eventual contents of the block can still be overridden by a subsequent call to [vash.helpers.block][] using the same `name` value, either within the current template (silly) or in a template that extends this one using [vash.helpers.extend][]. If [vash.helpers.append][] or [vash.helpers.prepend][] are later called, their content is _added_ to the content defined in `cb`.
@@html.block('main', function(model){
<p>Hello, I'm default content. It's nice to meet you.</p>
})
@helpersDisclaimer()
vash.helpers.append @lvl2()
---------------------------
vash.helpers.append(name, cb)
[vash.helpers.append][] is a way to control the content of a block from within an extending template. In this way, it allows templates to invert control over content "above" them.
An example is a navigation area. Perhaps there is a default navigation list that templates can add to:
// layout.vash
<ul>
@@html.block('main-nav', function(model){
<li><a href="/">Home</a></li>
})
</ul>
// another.vash
@@html.extend('layout', function(model){
@@html.append('main-nav', function(){
<li><a href="/another">Another Link</a></li>
})
})
This would output when fully rendered:
<li><a href="/">Home</a></li>
<li><a href="/another">Another Link</a></li>
@helpersDisclaimer()
vash.helpers.prepend @lvl2()
----------------------------
vash.helpers.prepend(name, cb)
[vash.helpers.prepend][] behaves nearly the same as [vash.helpers.append][] except that it places content at the beginning of a block instead of at the end. The previous example, if `prepend` were substituted for `append`, would render as:
<li><a href="/another">Another Link</a></li>
<li><a href="/">Home</a></li>
@helpersDisclaimer()
vash.helpers.include @lvl2()
----------------------------
vash.helpers.include(name, model)
This grabs the template `name` and executes it using `model` as the... model. [vash.helpers.include][] is used to literally include the contents of another template. It is analogous to a "partial" in other view engines. Except that there is a hidden power here... as included templates share the same "view engine scope" as other templates, and can thus call all of the layout helper functions, and it will _just work_. Thus, a block within an included template can append to a block defined in a parent. It can even use [vash.helpers.extend][]!
Compiled Helpers @lvl1()
========================
A relatively new feature in Vash (added in 0.6), compiled helpers are a bit meta. They allow a developer to write a helper using Vash syntax instead of the manual buffer API. The below buffer API example `imgfigure` could be rewritten:
vash.helpers.imgfigure = function(path, caption){
vash.helpers.imgfigure.figcount = vash.helpers.imgfigure.figcount || 0;
var figcount = vash.helpers.imgfigure.figcount;
<figure id="fig-@@(figcount++)">
<img src="@@path" alt="@@caption" />
<figcaption>Fig. @@figcount: @@caption</figcaption>
</figure>
}
There are two ways to compile a helper. The first is using [vash.compileHelper][], the second is using [vash(1)][]'s [--helper][] option.
Buffer API @lvl1()
==================
Within a helper (not a template), `this` refers to the current `Helpers` instance. Every instance has a `Buffer` that has methods to help easily add, subtract, or mark content put there by the rendering template.
Adding to the buffer:
vash.helpers.imgfigure = function(path, caption){
vash.helpers.imgfigure.figcount = vash.helpers.imgfigure.figcount || 0;
var figcount = vash.helpers.imgfigure.figcount++;
this.buffer.push('<figure id="fig-' + figcount + '">';
this.buffer.push('<img src="' + path + '" alt="' + caption + '" />';
this.buffer.push('<figcaption>Fig.' + figcount + ':' + caption + '</figcaption>';
this.buffer.push('</figure>');
}
Here is a more advanced example, which is [contained within Vash](https://github.com/kirbysayshi/vash/blob/master/src/vhelpers.js):
vash.helpers.highlight = function(lang, cb){
// context (this) is an instance of Helpers, aka a rendering context
// mark() returns an internal `Mark` object
// Use it to easily capture output...
var startMark = this.buffer.mark();
// cb() is simply a user-defined function. It could (and should) contain
// buffer additions, so we call it...
cb();
// ... and then use fromMark() to grab the output added by cb().
var cbOutLines = this.buffer.fromMark(startMark);
// The internal buffer should now be back to where it was before this
// helper started, and the output is completely contained within cbOutLines.
this.buffer.push( '<pre><code>' );
if( helpers.config.highlighter ){
this.buffer.push( helpers.config.highlighter(lang, cbOutLines.join('')).value );
} else {
this.buffer.push( cbOutLines );
}
this.buffer.push( '</code></pre>' );
// returning is allowed, but could cause surprising effects. A return
// value will be directly added to the output directly following the above.
}
A `Mark` is effectively a placeholder that can be used to literally mark the rendered content, and later do something with that mark. Possibilities include inserting content at the mark, deleting content that follows a mark, and more. It is an internal constructor that is only ever created through the `Buffer#mark` method within a helper. Examples of `Mark` usage can be found in the [layout helpers code][].
[layout helpers code]: https://github.com/kirbysayshi/vash/blob/master/src/vhelpers.layout.js
TODO: Explain the Buffer methods:
* mark
* fromMark
* spliceMark
* empty
* push
* pushConcat
* indexOf
* lastIndexOf
* splice
* index
* flush
* toString
* toHtmlString
Precompiling Templates @lvl1()
==============================
To save both processing time (compiling templates is not trivial) as well as bandwidth (no need to send the whole compiler to the client), Vash supports precompilation of templates. Any template that Vash compiles is given a method called `toClientString`. This method returns a string that can either be `eval`ed or sent to a remote client. For example:
<p>Hello</p>
Compiles to a function:
function anonymous(model,html,__vopts,vash) {
var __vbuffer = html.buffer;
html.options = __vopts;
model = model || {};
__vbuffer.push('<p></p>\n');
(__vopts && __vopts.onRenderEnd && __vopts.onRenderEnd(null, html));
return (__vopts && __vopts.asContext)
? html
: html.toString();
}
If `toClientString` is called on that function, the following is returned:
vash.link( function anonymous(model,html,__vopts,vash) {
var __vbuffer = html.buffer;
html.options = __vopts;
model = model || {};
__vbuffer.push('<p></p>\n');
(__vopts && __vopts.onRenderEnd && __vopts.onRenderEnd(null, html));
return (__vopts && __vopts.asContext)
? html
: html.toString();
}, {"simple":false,"modelName":"model","helpersName":"html"} )
This string could then be sent to the client (probably prefixed with something like `TPLCACHE["name-of-template"] = `). [vash(1)][] helps to automate this easily.
Note: this assumes that `vash` is available globally. A future version of Vash will hopefully remove this assumption.
Vash Runtime (Browser) @lvl1()
====================
The Vash runtime is a set of functions that every executing template expects to be available. The runtime is automatically packaged with full Vash builds. However, if only precompiled templates are sent to the browser, then only the runtime must be sent. The runtime includes all helpers and a few standard functions, such as [HTML Escaping][].
There are two runtime builds:
* [vash-runtime.js][]: This is the basic runtime. It contains everything a standard Vash template needs to execute.
* [vash-runtime-all.js][]: This also includes the [Layout Helpers][]. It is roughly twice as large as `vash-runtime.js`. Unless you're using the Vash view system in the browser, this is probably not necessary.
If you're in a Browserify-like environemnt, you should be able to:
```js
var vashruntime = require('vash/runtime');
```
..and have access to the [Runtime API][].
[vash-runtime.js]: https://github.com/kirbysayshi/vash/blob/master/build/vash-runtime.js
[vash-runtime-all.js]: https://github.com/kirbysayshi/vash/blob/master/build/vash-runtime-all.js
Compile-time API @lvl1()
=============
vash.compile @lvl2()
-----------------------------------------------
vash.compile(str_template, opt_options) -> Function
At its core, Vash has a `compile` function that accepts a string and options, and returns a function, otherwise known as a compiled template. That function, when called with a parameter (otherwise known as a _model_), will use that parameter to fill in the template. A model can be any value, including `undefined`, objects, arrays, strings, and booleans.
vash.compileHelper @lvl2()
-----------------------------------------------------
vash.compileHelper(str_template, opt_options) -> Object
See [Compiled Helpers][] for more detail.
vash.compileBatch @lvl2()
----------------------------------------------------
vash.compileBatch(str_template, opt_options) -> Object
This function can take a single string containing many named templates, and output an object containing the compiled versions of those templates. A "named template" is of the form (similar to a `sourceURL`):
//@@batch = div
<div>@@model</div>\n'
//@@batch = a
<a>@@model</a>'
This example contains two named templates, "div" and "a". If this example were passed as a single string to `compileBatch`:
var tpls = vash.compileBatch(theTplString);
One could be called:
tpls.div('yes!');
// returns: <div>yes!</div>
This is meant as a convenience function for developers. Putting each template in a separate file can get old, especially if a template is small. Instead, templates can be grouped together. The object returned also has a custom `toClientString` function, which serializes each template in the object automatically.
Aside from the newline following the "name" of the template, whitespace is ignored:
//@@ batch = div
//@@batch=div
// @@batch =div
Each is treated the same.
Runtime API @lvl1()
===================
vash.link @lvl2()