miki-template 2.2.3 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/docs.yml +3 -1
- package/.github/workflows/release.yml +0 -5
- package/README.md +17 -5
- package/benchmarks/ejs-results.json +6 -6
- package/benchmarks/ejs.js +5 -3
- package/benchmarks/handlebars-results.json +6 -6
- package/benchmarks/handlebars.js +5 -8
- package/benchmarks/miki-results.json +6 -6
- package/benchmarks/miki.js +6 -3
- package/benchmarks/pug-results.json +6 -6
- package/benchmarks/pug.js +5 -3
- package/docs/api/async-render.md +88 -3
- package/docs/api/cache.md +90 -3
- package/docs/api/compile.md +131 -3
- package/docs/api/context-processors.md +80 -3
- package/docs/api/filters.md +223 -3
- package/docs/api/finder.md +97 -3
- package/docs/api/helpers.md +56 -3
- package/docs/api/i18n.md +160 -3
- package/docs/api/index.md +82 -28
- package/docs/api/libraries.md +210 -3
- package/docs/api/render-partial.md +84 -3
- package/docs/api/render.md +95 -3
- package/docs/api/security.md +148 -3
- package/docs/api/setup-express.md +78 -2
- package/docs/api/tags.md +138 -4
- package/docs/filter.md +0 -0
- package/docs/guide/advanced-usage.md +403 -6
- package/docs/guide/async-rendering.md +312 -4
- package/docs/guide/context-processors.md +261 -4
- package/docs/guide/custom-filters.md +315 -4
- package/docs/guide/custom-tags.md +275 -4
- package/docs/guide/filters.md +675 -3
- package/docs/guide/getting-started.md +109 -7
- package/docs/guide/installation.md +99 -4
- package/docs/guide/partial-templates.md +371 -4
- package/docs/guide/quick-start.md +228 -6
- package/docs/guide/security.md +348 -3
- package/docs/guide/tags.md +789 -6
- package/docs/guide/template-discovery.md +174 -4
- package/docs/guide/template-inheritance.md +277 -4
- package/docs/index.md +24 -42
- package/docs/integrations/elysia.md +4 -2
- package/docs/integrations/express.md +219 -219
- package/docs/integrations/fastify.md +4 -2
- package/docs/integrations/hono.md +4 -2
- package/docs/integrations/index.md +68 -68
- package/docs/integrations/koa.md +4 -2
- package/docs/integrations/nestjs.md +4 -2
- package/docs/integrations/tsed.md +4 -2
- package/docs/performance.md +45 -8
- package/ex.mjs +1 -1
- package/mkdocs.yml +0 -22
- package/overrides/main.html +1 -1
- package/package.json +1 -1
- package/requirements-docs.txt +2 -1
- package/src/codegen.js +905 -0
- package/src/context.js +42 -30
- package/src/filters.js +16 -0
- package/src/index.js +66 -61
- package/src/tags/control.js +15 -12
- package/src/utils.js +60 -0
- package/tests/filters.test.js +9 -0
- package/docs/javascripts/extra.js +0 -174
- package/docs/stylesheets/extra.css +0 -819
- package/overrides/partials/footer.html +0 -9
package/docs/guide/tags.md
CHANGED
|
@@ -1,783 +1,1566 @@
|
|
|
1
|
-
# Tags
|
|
1
|
+
# Tags
|
|
2
|
+
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
Tags control template logic and structure. They use `{% %}` syntax.
|
|
4
6
|
|
|
7
|
+
|
|
8
|
+
|
|
5
9
|
## Table of Contents
|
|
6
10
|
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
- [Control Flow](#control-flow)
|
|
14
|
+
|
|
8
15
|
- [Variable Assignment](#variable-assignment)
|
|
16
|
+
|
|
9
17
|
- [Change Detection](#change-detection)
|
|
18
|
+
|
|
10
19
|
- [Date and Time](#date-and-time)
|
|
20
|
+
|
|
11
21
|
- [Utility Tags](#utility-tags)
|
|
22
|
+
|
|
12
23
|
- [Security Tags](#security-tags)
|
|
24
|
+
|
|
13
25
|
- [Comments and Raw Output](#comments-and-raw-output)
|
|
26
|
+
|
|
14
27
|
- [Autoescape](#autoescape)
|
|
28
|
+
|
|
15
29
|
- [Library Loading](#library-loading)
|
|
30
|
+
|
|
16
31
|
- [Template Tags](#template-tags)
|
|
32
|
+
|
|
17
33
|
- [Inheritance Tags](#inheritance-tags)
|
|
34
|
+
|
|
18
35
|
- [Partial Tags](#partial-tags)
|
|
36
|
+
|
|
19
37
|
- [i18n Tags](#i18n-tags)
|
|
20
38
|
|
|
39
|
+
|
|
40
|
+
|
|
21
41
|
---
|
|
22
42
|
|
|
43
|
+
|
|
44
|
+
|
|
23
45
|
## Control Flow
|
|
24
46
|
|
|
47
|
+
|
|
48
|
+
|
|
25
49
|
### if / elif / else / endif
|
|
26
50
|
|
|
51
|
+
|
|
52
|
+
|
|
27
53
|
Conditional rendering with a wide range of operators.
|
|
28
54
|
|
|
55
|
+
|
|
56
|
+
|
|
29
57
|
=== "Basic if"
|
|
30
58
|
|
|
59
|
+
|
|
60
|
+
|
|
31
61
|
```html
|
|
62
|
+
|
|
32
63
|
{% if user.is_authenticated %}
|
|
64
|
+
|
|
33
65
|
<p>Welcome back, {{ user.name }}!</p>
|
|
66
|
+
|
|
34
67
|
{% else %}
|
|
68
|
+
|
|
35
69
|
<p>Please <a href="/login">log in</a>.</p>
|
|
70
|
+
|
|
36
71
|
{% endif %}
|
|
72
|
+
|
|
37
73
|
```
|
|
38
74
|
|
|
75
|
+
|
|
76
|
+
|
|
39
77
|
=== "Multiple conditions with elif"
|
|
40
78
|
|
|
79
|
+
|
|
80
|
+
|
|
41
81
|
```html
|
|
82
|
+
|
|
42
83
|
{% if user.role == 'admin' %}
|
|
84
|
+
|
|
43
85
|
<p>Admin panel</p>
|
|
86
|
+
|
|
44
87
|
{% elif user.is_staff %}
|
|
88
|
+
|
|
45
89
|
<p>Staff dashboard</p>
|
|
90
|
+
|
|
46
91
|
{% else %}
|
|
92
|
+
|
|
47
93
|
<p>Guest view</p>
|
|
94
|
+
|
|
48
95
|
{% endif %}
|
|
96
|
+
|
|
49
97
|
```
|
|
50
98
|
|
|
99
|
+
|
|
100
|
+
|
|
51
101
|
=== "Combined conditions with parentheses"
|
|
52
102
|
|
|
103
|
+
|
|
104
|
+
|
|
53
105
|
```html
|
|
106
|
+
|
|
54
107
|
{% if (user.role == 'admin' or user.is_staff) and user.is_active %}
|
|
108
|
+
|
|
55
109
|
<p>Active staff member</p>
|
|
110
|
+
|
|
56
111
|
{% endif %}
|
|
57
112
|
|
|
113
|
+
|
|
114
|
+
|
|
58
115
|
{% if item not in cart_items %}
|
|
116
|
+
|
|
59
117
|
<button>Add to cart</button>
|
|
118
|
+
|
|
60
119
|
{% endif %}
|
|
120
|
+
|
|
61
121
|
```
|
|
62
122
|
|
|
123
|
+
|
|
124
|
+
|
|
63
125
|
**Supported operators:** `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `and`, `or`, `not`
|
|
64
126
|
|
|
127
|
+
|
|
128
|
+
|
|
65
129
|
**Operator precedence** (highest to lowest): comparison → `and` → `or`
|
|
66
130
|
|
|
131
|
+
|
|
132
|
+
|
|
67
133
|
### for / empty / endfor
|
|
68
134
|
|
|
135
|
+
|
|
136
|
+
|
|
69
137
|
Loop over arrays and objects. Injects `forloop` meta tracking.
|
|
70
138
|
|
|
139
|
+
|
|
140
|
+
|
|
71
141
|
=== "Basic loop"
|
|
72
142
|
|
|
143
|
+
|
|
144
|
+
|
|
73
145
|
```html
|
|
146
|
+
|
|
74
147
|
<ul>
|
|
148
|
+
|
|
75
149
|
{% for item in items %}
|
|
150
|
+
|
|
76
151
|
<li>{{ forloop.counter }}: {{ item }}</li>
|
|
152
|
+
|
|
77
153
|
{% empty %}
|
|
154
|
+
|
|
78
155
|
<li>No items found</li>
|
|
156
|
+
|
|
79
157
|
{% endfor %}
|
|
158
|
+
|
|
80
159
|
</ul>
|
|
160
|
+
|
|
81
161
|
```
|
|
82
162
|
|
|
163
|
+
|
|
164
|
+
|
|
83
165
|
=== "Loop with filters"
|
|
84
166
|
|
|
167
|
+
|
|
168
|
+
|
|
85
169
|
```html
|
|
170
|
+
|
|
86
171
|
{% for group in items|regroup:"category" %}
|
|
172
|
+
|
|
87
173
|
<h3>{{ group.grouper }}</h3>
|
|
174
|
+
|
|
88
175
|
{% for item in group.list %}- {{ item.name }}
|
|
176
|
+
|
|
89
177
|
{% endfor %}
|
|
178
|
+
|
|
90
179
|
{% endfor %}
|
|
180
|
+
|
|
91
181
|
```
|
|
92
182
|
|
|
183
|
+
|
|
184
|
+
|
|
93
185
|
=== "Dictionary iteration"
|
|
94
186
|
|
|
187
|
+
|
|
188
|
+
|
|
95
189
|
```html
|
|
190
|
+
|
|
96
191
|
{% for key, value in config %}
|
|
192
|
+
|
|
97
193
|
<dt>{{ key }}</dt>
|
|
194
|
+
|
|
98
195
|
<dd>{{ value }}</dd>
|
|
196
|
+
|
|
99
197
|
{% endfor %}
|
|
198
|
+
|
|
100
199
|
```
|
|
101
200
|
|
|
201
|
+
|
|
202
|
+
|
|
102
203
|
=== "Nested loops"
|
|
103
204
|
|
|
205
|
+
|
|
206
|
+
|
|
104
207
|
```html
|
|
208
|
+
|
|
105
209
|
{% for department in departments %}
|
|
210
|
+
|
|
106
211
|
<h2>{{ department.name }}</h2>
|
|
212
|
+
|
|
107
213
|
{% for employee in department.employees %}
|
|
214
|
+
|
|
108
215
|
<span>#{{ forloop.parentloop.counter }}.{{ forloop.counter }} {{ employee.name }}</span>
|
|
216
|
+
|
|
109
217
|
{% endfor %}
|
|
218
|
+
|
|
110
219
|
{% endfor %}
|
|
220
|
+
|
|
111
221
|
```
|
|
112
222
|
|
|
223
|
+
|
|
224
|
+
|
|
113
225
|
#### forloop Metadata
|
|
114
226
|
|
|
227
|
+
|
|
228
|
+
|
|
115
229
|
| Variable | Description |
|
|
230
|
+
|
|
116
231
|
|----------|-------------|
|
|
232
|
+
|
|
117
233
|
| `forloop.counter` | 1-based index |
|
|
234
|
+
|
|
118
235
|
| `forloop.counter0` | 0-based index |
|
|
236
|
+
|
|
119
237
|
| `forloop.revcounter` | Reverse 1-based index |
|
|
238
|
+
|
|
120
239
|
| `forloop.revcounter0` | Reverse 0-based index |
|
|
240
|
+
|
|
121
241
|
| `forloop.first` | `true` on first iteration |
|
|
242
|
+
|
|
122
243
|
| `forloop.last` | `true` on last iteration |
|
|
244
|
+
|
|
123
245
|
| `forloop.parentloop` | Parent loop context (nested loops) |
|
|
124
246
|
|
|
247
|
+
|
|
248
|
+
|
|
125
249
|
**Real-world table with alternating row classes:**
|
|
126
250
|
|
|
251
|
+
|
|
252
|
+
|
|
127
253
|
```html
|
|
254
|
+
|
|
128
255
|
<table>
|
|
256
|
+
|
|
129
257
|
{% for row in rows %}
|
|
258
|
+
|
|
130
259
|
<tr class="{% cycle 'row-odd' 'row-even' %}">
|
|
260
|
+
|
|
131
261
|
<td>{{ row.name }}</td>
|
|
262
|
+
|
|
132
263
|
<td>{{ row.value }}</td>
|
|
264
|
+
|
|
133
265
|
</tr>
|
|
266
|
+
|
|
134
267
|
{% endfor %}
|
|
268
|
+
|
|
135
269
|
</table>
|
|
270
|
+
|
|
136
271
|
```
|
|
137
272
|
|
|
273
|
+
|
|
274
|
+
|
|
138
275
|
### with / endwith
|
|
139
276
|
|
|
277
|
+
|
|
278
|
+
|
|
140
279
|
Scope localized variables.
|
|
141
280
|
|
|
281
|
+
|
|
282
|
+
|
|
142
283
|
=== "Alias a variable"
|
|
143
284
|
|
|
285
|
+
|
|
286
|
+
|
|
144
287
|
```html
|
|
288
|
+
|
|
145
289
|
{% with user.profile.address as addr %}
|
|
290
|
+
|
|
146
291
|
<p>{{ addr.city }}, {{ addr.zip }}</p>
|
|
292
|
+
|
|
147
293
|
{% endwith %}
|
|
294
|
+
|
|
148
295
|
```
|
|
149
296
|
|
|
297
|
+
|
|
298
|
+
|
|
150
299
|
=== "Multiple assignments"
|
|
151
300
|
|
|
301
|
+
|
|
302
|
+
|
|
152
303
|
```html
|
|
304
|
+
|
|
153
305
|
{% with greeting="Hello", who="World" %}
|
|
306
|
+
|
|
154
307
|
<p>{{ greeting }} {{ who }}</p>
|
|
308
|
+
|
|
155
309
|
{% endwith %}
|
|
310
|
+
|
|
156
311
|
```
|
|
157
312
|
|
|
313
|
+
|
|
314
|
+
|
|
158
315
|
=== "Combined alias"
|
|
159
316
|
|
|
317
|
+
|
|
318
|
+
|
|
160
319
|
```html
|
|
320
|
+
|
|
161
321
|
{% with a=5, b=10 as total %}
|
|
322
|
+
|
|
162
323
|
<p>Total: {{ total }}</p>
|
|
324
|
+
|
|
163
325
|
{% endwith %}
|
|
326
|
+
|
|
164
327
|
```
|
|
165
328
|
|
|
329
|
+
|
|
330
|
+
|
|
166
331
|
### cycle
|
|
167
332
|
|
|
333
|
+
|
|
334
|
+
|
|
168
335
|
Cycle through values sequentially.
|
|
169
336
|
|
|
337
|
+
|
|
338
|
+
|
|
170
339
|
=== "Alternating CSS classes"
|
|
171
340
|
|
|
341
|
+
|
|
342
|
+
|
|
172
343
|
```html
|
|
344
|
+
|
|
173
345
|
{% for row in rows %}
|
|
346
|
+
|
|
174
347
|
<tr class="{% cycle 'row-odd' 'row-even' %}">...</tr>
|
|
348
|
+
|
|
175
349
|
{% endfor %}
|
|
350
|
+
|
|
176
351
|
```
|
|
177
352
|
|
|
353
|
+
|
|
354
|
+
|
|
178
355
|
=== "Store without output (as)"
|
|
179
356
|
|
|
357
|
+
|
|
358
|
+
|
|
180
359
|
```html
|
|
360
|
+
|
|
181
361
|
{% cycle 'row-odd' 'row-even' as row_class %}
|
|
362
|
+
|
|
182
363
|
<tr class="{{ row_class }}">
|
|
364
|
+
|
|
183
365
|
```
|
|
184
366
|
|
|
367
|
+
|
|
368
|
+
|
|
185
369
|
=== "Named cycle for resumable state"
|
|
186
370
|
|
|
371
|
+
|
|
372
|
+
|
|
187
373
|
```html
|
|
374
|
+
|
|
188
375
|
{% for item in items %}
|
|
376
|
+
|
|
189
377
|
{% cycle 'a' 'b' 'c' as marker silent %}
|
|
378
|
+
|
|
190
379
|
{% if marker == 'b' %}
|
|
380
|
+
|
|
191
381
|
<strong>{{ item }}</strong>
|
|
382
|
+
|
|
192
383
|
{% else %}
|
|
384
|
+
|
|
193
385
|
{{ item }}
|
|
386
|
+
|
|
194
387
|
{% endif %}
|
|
388
|
+
|
|
195
389
|
{% endfor %}
|
|
390
|
+
|
|
196
391
|
```
|
|
197
392
|
|
|
393
|
+
|
|
394
|
+
|
|
198
395
|
### firstof
|
|
199
396
|
|
|
397
|
+
|
|
398
|
+
|
|
200
399
|
Return the first truthy value.
|
|
201
400
|
|
|
401
|
+
|
|
402
|
+
|
|
202
403
|
```html
|
|
404
|
+
|
|
203
405
|
{% firstof user.display_name user.username "Anonymous" %}
|
|
406
|
+
|
|
204
407
|
```
|
|
205
408
|
|
|
409
|
+
|
|
410
|
+
|
|
206
411
|
---
|
|
207
412
|
|
|
413
|
+
|
|
414
|
+
|
|
208
415
|
## Variable Assignment
|
|
209
416
|
|
|
417
|
+
|
|
418
|
+
|
|
210
419
|
### set
|
|
211
420
|
|
|
421
|
+
|
|
422
|
+
|
|
212
423
|
Assign a value to a variable.
|
|
213
424
|
|
|
425
|
+
|
|
426
|
+
|
|
214
427
|
=== "Inline assignment"
|
|
215
428
|
|
|
429
|
+
|
|
430
|
+
|
|
216
431
|
```html
|
|
432
|
+
|
|
217
433
|
{% set total = price * quantity %}
|
|
434
|
+
|
|
218
435
|
<p>Total: ${{ total|floatformat:2 }}</p>
|
|
436
|
+
|
|
219
437
|
```
|
|
220
438
|
|
|
439
|
+
|
|
440
|
+
|
|
221
441
|
=== "Block form (captures rendered output)"
|
|
222
442
|
|
|
443
|
+
|
|
444
|
+
|
|
223
445
|
```html
|
|
446
|
+
|
|
224
447
|
{% set greeting %}
|
|
448
|
+
|
|
225
449
|
Hello {{ user.name|title }}, welcome to {{ site.name }}!
|
|
450
|
+
|
|
226
451
|
{% endset %}
|
|
227
452
|
|
|
453
|
+
|
|
454
|
+
|
|
228
455
|
<h1>{{ greeting|safe }}</h1>
|
|
456
|
+
|
|
229
457
|
```
|
|
230
458
|
|
|
459
|
+
|
|
460
|
+
|
|
231
461
|
=== "Multiple variables"
|
|
232
462
|
|
|
463
|
+
|
|
464
|
+
|
|
233
465
|
```html
|
|
466
|
+
|
|
234
467
|
{% set tax_rate = 0.08, tax = subtotal|mult:tax_rate %}
|
|
468
|
+
|
|
235
469
|
```
|
|
236
470
|
|
|
471
|
+
|
|
472
|
+
|
|
237
473
|
Variables set with `{% set %}` persist in the current scope and can be used after the tag.
|
|
238
474
|
|
|
475
|
+
|
|
476
|
+
|
|
239
477
|
---
|
|
240
478
|
|
|
479
|
+
|
|
480
|
+
|
|
241
481
|
## Change Detection
|
|
242
482
|
|
|
483
|
+
|
|
484
|
+
|
|
243
485
|
### ifchanged / endifchanged
|
|
244
486
|
|
|
487
|
+
|
|
488
|
+
|
|
245
489
|
Render the body only when a value changes.
|
|
246
490
|
|
|
491
|
+
|
|
492
|
+
|
|
247
493
|
=== "Basic (no argument)"
|
|
248
494
|
|
|
495
|
+
|
|
496
|
+
|
|
249
497
|
```html
|
|
498
|
+
|
|
250
499
|
{% for item in changelog %}
|
|
500
|
+
|
|
251
501
|
{% ifchanged item.timestamp %}
|
|
502
|
+
|
|
252
503
|
<h3>{{ item.timestamp|date:"Y-m-d" }}</h3>
|
|
504
|
+
|
|
253
505
|
{% endifchanged %}
|
|
506
|
+
|
|
254
507
|
<p>{{ item.change }}</p>
|
|
508
|
+
|
|
255
509
|
{% endfor %}
|
|
510
|
+
|
|
256
511
|
```
|
|
257
512
|
|
|
258
|
-
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
=== "With else"
|
|
516
|
+
|
|
517
|
+
|
|
259
518
|
|
|
260
519
|
```html
|
|
520
|
+
|
|
261
521
|
{% for item in items %}
|
|
522
|
+
|
|
262
523
|
{% ifchanged item.category %}
|
|
524
|
+
|
|
263
525
|
<h2>{{ item.category }}</h2>
|
|
526
|
+
|
|
264
527
|
{% else %}
|
|
528
|
+
|
|
265
529
|
<p>Same category as above</p>
|
|
530
|
+
|
|
266
531
|
{% endifchanged %}
|
|
532
|
+
|
|
267
533
|
{% endfor %}
|
|
534
|
+
|
|
268
535
|
```
|
|
269
536
|
|
|
537
|
+
|
|
538
|
+
|
|
270
539
|
---
|
|
271
540
|
|
|
541
|
+
|
|
542
|
+
|
|
272
543
|
## Date and Time
|
|
273
544
|
|
|
545
|
+
|
|
546
|
+
|
|
274
547
|
### now
|
|
275
548
|
|
|
549
|
+
|
|
550
|
+
|
|
276
551
|
Output the current date/time.
|
|
277
552
|
|
|
553
|
+
|
|
554
|
+
|
|
278
555
|
```html
|
|
556
|
+
|
|
279
557
|
<p>Current time: {% now "Y-m-d H:i:s" %}</p>
|
|
558
|
+
|
|
280
559
|
<p>Pretty date: {% now "F j, Y" %}</p>
|
|
560
|
+
|
|
281
561
|
```
|
|
282
562
|
|
|
563
|
+
|
|
564
|
+
|
|
283
565
|
Uses the same format codes as the `date` filter (Django-style tokens like `Y`, `m`, `d`, `H`, `i`, `s`, `F`).
|
|
284
566
|
|
|
567
|
+
|
|
568
|
+
|
|
285
569
|
**Real-world copyright footer:**
|
|
286
570
|
|
|
571
|
+
|
|
572
|
+
|
|
287
573
|
```html
|
|
574
|
+
|
|
288
575
|
<footer>
|
|
576
|
+
|
|
289
577
|
© {{ "now"|date:"Y" }} {{ site.name }}. All rights reserved.
|
|
578
|
+
|
|
290
579
|
</footer>
|
|
580
|
+
|
|
291
581
|
```
|
|
292
582
|
|
|
583
|
+
|
|
584
|
+
|
|
293
585
|
---
|
|
294
586
|
|
|
587
|
+
|
|
588
|
+
|
|
295
589
|
## Utility Tags
|
|
296
590
|
|
|
591
|
+
|
|
592
|
+
|
|
297
593
|
### static
|
|
298
594
|
|
|
595
|
+
|
|
596
|
+
|
|
299
597
|
Generate a static file URL.
|
|
300
598
|
|
|
599
|
+
|
|
600
|
+
|
|
301
601
|
```html
|
|
602
|
+
|
|
302
603
|
<link rel="stylesheet" href="{% static "css/main.css" %}">
|
|
604
|
+
|
|
303
605
|
<script src="{% static "js/app.js" %}"></script>
|
|
606
|
+
|
|
304
607
|
<img src="{% static "images/logo.svg" }}" alt="{{ site.name }}">
|
|
608
|
+
|
|
305
609
|
```
|
|
306
610
|
|
|
611
|
+
|
|
612
|
+
|
|
307
613
|
Configure the prefix at compile time:
|
|
308
614
|
|
|
615
|
+
|
|
616
|
+
|
|
309
617
|
=== "CommonJS"
|
|
310
618
|
|
|
619
|
+
|
|
620
|
+
|
|
311
621
|
```javascript
|
|
622
|
+
|
|
312
623
|
const { compile } = require('miki-template');
|
|
624
|
+
|
|
313
625
|
const template = compile(source, { staticUrl: '/assets/' });
|
|
626
|
+
|
|
314
627
|
```
|
|
315
628
|
|
|
629
|
+
|
|
630
|
+
|
|
316
631
|
=== "ES Modules"
|
|
317
632
|
|
|
633
|
+
|
|
634
|
+
|
|
318
635
|
```javascript
|
|
636
|
+
|
|
319
637
|
import { compile } from 'miki-template';
|
|
638
|
+
|
|
320
639
|
const template = compile(source, { staticUrl: '/assets/' });
|
|
640
|
+
|
|
321
641
|
```
|
|
322
642
|
|
|
643
|
+
|
|
644
|
+
|
|
323
645
|
### url
|
|
324
646
|
|
|
647
|
+
|
|
648
|
+
|
|
325
649
|
Build a URL from a route name.
|
|
326
650
|
|
|
651
|
+
|
|
652
|
+
|
|
327
653
|
=== "Basic"
|
|
328
654
|
|
|
655
|
+
|
|
656
|
+
|
|
329
657
|
```html
|
|
658
|
+
|
|
330
659
|
<a href="{% url 'user.profile' user.id %}">Profile</a>
|
|
660
|
+
|
|
331
661
|
```
|
|
332
662
|
|
|
663
|
+
|
|
664
|
+
|
|
333
665
|
=== "With keyword arguments"
|
|
334
666
|
|
|
667
|
+
|
|
668
|
+
|
|
335
669
|
```html
|
|
670
|
+
|
|
336
671
|
{% url 'posts.show' post.id tab='comments' %}
|
|
672
|
+
|
|
337
673
|
```
|
|
338
674
|
|
|
675
|
+
|
|
676
|
+
|
|
339
677
|
=== "Deep routing with dots"
|
|
340
678
|
|
|
679
|
+
|
|
680
|
+
|
|
341
681
|
```html
|
|
682
|
+
|
|
342
683
|
{% url 'user.profile.posts.show' user.id post.id %}
|
|
684
|
+
|
|
343
685
|
```
|
|
344
686
|
|
|
687
|
+
|
|
688
|
+
|
|
345
689
|
Configure with a custom resolver:
|
|
346
690
|
|
|
691
|
+
|
|
692
|
+
|
|
347
693
|
=== "CommonJS"
|
|
348
694
|
|
|
695
|
+
|
|
696
|
+
|
|
349
697
|
```javascript
|
|
698
|
+
|
|
350
699
|
const { compile } = require('miki-template');
|
|
700
|
+
|
|
351
701
|
const template = compile(source, {
|
|
702
|
+
|
|
352
703
|
urlHelper: (routeName, ...args) => {
|
|
704
|
+
|
|
353
705
|
// Convert "user.profile" + [42] → "/user/profile/42"
|
|
706
|
+
|
|
354
707
|
return '/' + routeName.split('.').join('/') + '/' + args.join('/');
|
|
708
|
+
|
|
355
709
|
}
|
|
710
|
+
|
|
356
711
|
});
|
|
712
|
+
|
|
357
713
|
```
|
|
358
714
|
|
|
715
|
+
|
|
716
|
+
|
|
359
717
|
=== "ES Modules"
|
|
360
718
|
|
|
719
|
+
|
|
720
|
+
|
|
361
721
|
```javascript
|
|
722
|
+
|
|
362
723
|
import { compile } from 'miki-template';
|
|
724
|
+
|
|
363
725
|
const template = compile(source, {
|
|
726
|
+
|
|
364
727
|
urlHelper: (routeName, ...args) => {
|
|
728
|
+
|
|
365
729
|
return '/' + routeName.split('.').join('/') + '/' + args.join('/');
|
|
730
|
+
|
|
366
731
|
}
|
|
732
|
+
|
|
367
733
|
});
|
|
734
|
+
|
|
368
735
|
```
|
|
369
736
|
|
|
737
|
+
|
|
738
|
+
|
|
370
739
|
### regroup
|
|
371
740
|
|
|
741
|
+
|
|
742
|
+
|
|
372
743
|
Group a list by a common attribute.
|
|
373
744
|
|
|
745
|
+
|
|
746
|
+
|
|
374
747
|
```html
|
|
748
|
+
|
|
375
749
|
{% regroup people by gender as departments %}
|
|
750
|
+
|
|
376
751
|
{% for dept in departments %}
|
|
752
|
+
|
|
377
753
|
<h3>{{ dept.grouper }}</h3>
|
|
754
|
+
|
|
378
755
|
{% for person in dept.list %}
|
|
756
|
+
|
|
379
757
|
<p>{{ person.name }}</p>
|
|
758
|
+
|
|
380
759
|
{% endfor %}
|
|
760
|
+
|
|
381
761
|
{% endfor %}
|
|
762
|
+
|
|
382
763
|
```
|
|
383
764
|
|
|
765
|
+
|
|
766
|
+
|
|
384
767
|
You can also use `regroup` as a filter inside a `{% for %}` loop:
|
|
385
768
|
|
|
769
|
+
|
|
770
|
+
|
|
386
771
|
```html
|
|
772
|
+
|
|
387
773
|
{% for group in items|regroup:"category" %}
|
|
774
|
+
|
|
388
775
|
<h3>{{ group.grouper }}</h3>
|
|
776
|
+
|
|
389
777
|
{% for item in group.list %}
|
|
778
|
+
|
|
390
779
|
<p>{{ item.name }}</p>
|
|
780
|
+
|
|
391
781
|
{% endfor %}
|
|
782
|
+
|
|
392
783
|
{% endfor %}
|
|
784
|
+
|
|
393
785
|
```
|
|
394
786
|
|
|
787
|
+
|
|
788
|
+
|
|
395
789
|
### spaceless
|
|
396
790
|
|
|
791
|
+
|
|
792
|
+
|
|
397
793
|
Remove whitespace between HTML tags.
|
|
398
794
|
|
|
795
|
+
|
|
796
|
+
|
|
399
797
|
```html
|
|
798
|
+
|
|
400
799
|
{% spaceless %}
|
|
800
|
+
|
|
401
801
|
<div>
|
|
802
|
+
|
|
402
803
|
<span> hello </span>
|
|
804
|
+
|
|
403
805
|
</div>
|
|
806
|
+
|
|
404
807
|
{% endspaceless %}
|
|
808
|
+
|
|
405
809
|
```
|
|
406
810
|
|
|
811
|
+
|
|
812
|
+
|
|
407
813
|
Output: `<div><span> hello </span></div>`
|
|
408
814
|
|
|
815
|
+
|
|
816
|
+
|
|
409
817
|
### widthratio
|
|
410
818
|
|
|
819
|
+
|
|
820
|
+
|
|
411
821
|
Calculate ratios for progress bars or scaling.
|
|
412
822
|
|
|
823
|
+
|
|
824
|
+
|
|
413
825
|
```html
|
|
826
|
+
|
|
414
827
|
<!-- Calculate 25 out of 100 scaled to max-width 150 -->
|
|
828
|
+
|
|
415
829
|
{% widthratio score 100 150 %}
|
|
830
|
+
|
|
416
831
|
<!-- → 37 (floor of 25/100*150) -->
|
|
417
832
|
|
|
833
|
+
|
|
834
|
+
|
|
418
835
|
<!-- Progress bar width -->
|
|
836
|
+
|
|
419
837
|
<div class="bar" style="width: {% widthratio value max_value 100 %}px;"></div>
|
|
838
|
+
|
|
420
839
|
```
|
|
421
840
|
|
|
841
|
+
|
|
842
|
+
|
|
422
843
|
### debug
|
|
423
844
|
|
|
845
|
+
|
|
846
|
+
|
|
424
847
|
Dump the current template context for debugging.
|
|
425
848
|
|
|
849
|
+
|
|
850
|
+
|
|
426
851
|
```html
|
|
852
|
+
|
|
427
853
|
{% debug %}
|
|
854
|
+
|
|
428
855
|
```
|
|
429
856
|
|
|
857
|
+
|
|
858
|
+
|
|
430
859
|
Outputs a `<pre>` block with all context variables.
|
|
431
860
|
|
|
861
|
+
|
|
862
|
+
|
|
432
863
|
---
|
|
433
864
|
|
|
865
|
+
|
|
866
|
+
|
|
434
867
|
## Security Tags
|
|
435
868
|
|
|
869
|
+
|
|
870
|
+
|
|
436
871
|
### csrf_token
|
|
437
872
|
|
|
873
|
+
|
|
874
|
+
|
|
438
875
|
Output a hidden CSRF token input.
|
|
439
876
|
|
|
877
|
+
|
|
878
|
+
|
|
440
879
|
```html
|
|
880
|
+
|
|
441
881
|
<form method="post">
|
|
882
|
+
|
|
442
883
|
{% csrf_token %}
|
|
884
|
+
|
|
443
885
|
<button type="submit">Submit</button>
|
|
886
|
+
|
|
444
887
|
</form>
|
|
888
|
+
|
|
445
889
|
```
|
|
446
890
|
|
|
891
|
+
|
|
892
|
+
|
|
447
893
|
The token value is HTML-escaped to prevent attribute injection. Requires `csrf_token` to be present in the template context.
|
|
448
894
|
|
|
895
|
+
|
|
896
|
+
|
|
449
897
|
=== "CommonJS (Express middleware)"
|
|
450
898
|
|
|
899
|
+
|
|
900
|
+
|
|
451
901
|
```javascript
|
|
902
|
+
|
|
452
903
|
app.use((req, res, next) => {
|
|
904
|
+
|
|
453
905
|
res.locals.csrf_token = req.csrfToken();
|
|
906
|
+
|
|
454
907
|
next();
|
|
908
|
+
|
|
455
909
|
});
|
|
910
|
+
|
|
456
911
|
```
|
|
457
912
|
|
|
913
|
+
|
|
914
|
+
|
|
458
915
|
=== "ES Modules"
|
|
459
916
|
|
|
917
|
+
|
|
918
|
+
|
|
460
919
|
```javascript
|
|
920
|
+
|
|
461
921
|
app.use((req, res, next) => {
|
|
922
|
+
|
|
462
923
|
res.locals.csrf_token = req.csrfToken();
|
|
924
|
+
|
|
463
925
|
next();
|
|
926
|
+
|
|
464
927
|
});
|
|
928
|
+
|
|
465
929
|
```
|
|
466
930
|
|
|
931
|
+
|
|
932
|
+
|
|
467
933
|
### csp_nonce_attr
|
|
468
934
|
|
|
935
|
+
|
|
936
|
+
|
|
469
937
|
Output a `nonce` attribute when `csp_nonce` is in the context.
|
|
470
938
|
|
|
939
|
+
|
|
940
|
+
|
|
471
941
|
```html
|
|
942
|
+
|
|
472
943
|
<script {% csp_nonce_attr %} src="/js/app.js"></script>
|
|
944
|
+
|
|
473
945
|
```
|
|
474
946
|
|
|
947
|
+
|
|
948
|
+
|
|
475
949
|
If `csp_nonce` is present in context, the output is:
|
|
476
950
|
|
|
951
|
+
|
|
952
|
+
|
|
477
953
|
```html
|
|
954
|
+
|
|
478
955
|
<script nonce="abc123" src="/js/app.js"></script>
|
|
956
|
+
|
|
479
957
|
```
|
|
480
958
|
|
|
959
|
+
|
|
960
|
+
|
|
481
961
|
If `csp_nonce` is not present, the tag outputs nothing.
|
|
482
962
|
|
|
963
|
+
|
|
964
|
+
|
|
483
965
|
---
|
|
484
966
|
|
|
967
|
+
|
|
968
|
+
|
|
485
969
|
## Comments and Raw Output
|
|
486
970
|
|
|
971
|
+
|
|
972
|
+
|
|
487
973
|
### comment / endcomment
|
|
488
974
|
|
|
975
|
+
|
|
976
|
+
|
|
489
977
|
Block comments ignored during parsing.
|
|
490
978
|
|
|
979
|
+
|
|
980
|
+
|
|
491
981
|
```html
|
|
982
|
+
|
|
492
983
|
{% comment %}
|
|
984
|
+
|
|
493
985
|
This is a comment.
|
|
986
|
+
|
|
494
987
|
It can span multiple lines.
|
|
988
|
+
|
|
495
989
|
{% endcomment %}
|
|
990
|
+
|
|
496
991
|
```
|
|
497
992
|
|
|
993
|
+
|
|
994
|
+
|
|
498
995
|
### verbatim / endverbatim
|
|
499
996
|
|
|
997
|
+
|
|
998
|
+
|
|
500
999
|
Treat content as raw text — template syntax is not parsed.
|
|
501
1000
|
|
|
1001
|
+
|
|
1002
|
+
|
|
502
1003
|
```html
|
|
1004
|
+
|
|
503
1005
|
{% verbatim %}
|
|
1006
|
+
|
|
504
1007
|
This will NOT be parsed: {{ user.name }}
|
|
1008
|
+
|
|
505
1009
|
And this won't either: {% if x %}
|
|
1010
|
+
|
|
506
1011
|
{% endverbatim %}
|
|
1012
|
+
|
|
507
1013
|
```
|
|
508
1014
|
|
|
1015
|
+
|
|
1016
|
+
|
|
509
1017
|
You can also name a verbatim block:
|
|
510
1018
|
|
|
1019
|
+
|
|
1020
|
+
|
|
511
1021
|
```html
|
|
1022
|
+
|
|
512
1023
|
{% verbatim myscript %}
|
|
1024
|
+
|
|
513
1025
|
{{ angularExpression }}
|
|
1026
|
+
|
|
514
1027
|
{% endverbatim %}
|
|
1028
|
+
|
|
515
1029
|
```
|
|
516
1030
|
|
|
1031
|
+
|
|
1032
|
+
|
|
517
1033
|
---
|
|
518
1034
|
|
|
1035
|
+
|
|
1036
|
+
|
|
519
1037
|
## Autoescape
|
|
520
1038
|
|
|
1039
|
+
|
|
1040
|
+
|
|
521
1041
|
Control HTML escaping for a block.
|
|
522
1042
|
|
|
1043
|
+
|
|
1044
|
+
|
|
523
1045
|
```html
|
|
1046
|
+
|
|
524
1047
|
{% autoescape on %}
|
|
1048
|
+
|
|
525
1049
|
{{ user_input }} {# escaped → <script>... #}
|
|
1050
|
+
|
|
526
1051
|
{% endautoescape %}
|
|
527
1052
|
|
|
1053
|
+
|
|
1054
|
+
|
|
528
1055
|
{% autoescape off %}
|
|
1056
|
+
|
|
529
1057
|
{{ trusted_html }} {# not escaped → raw HTML #}
|
|
1058
|
+
|
|
530
1059
|
{% endautoescape %}
|
|
1060
|
+
|
|
531
1061
|
```
|
|
532
1062
|
|
|
1063
|
+
|
|
1064
|
+
|
|
533
1065
|
---
|
|
534
1066
|
|
|
1067
|
+
|
|
1068
|
+
|
|
535
1069
|
## Library Loading
|
|
536
1070
|
|
|
1071
|
+
|
|
1072
|
+
|
|
537
1073
|
### load
|
|
538
1074
|
|
|
1075
|
+
|
|
1076
|
+
|
|
539
1077
|
Activate a template library. Built-in libraries (`humanize`, `cache`, `lorem`) are auto-activated — you only need `{% load %}` for custom libraries you've registered.
|
|
540
1078
|
|
|
1079
|
+
|
|
1080
|
+
|
|
541
1081
|
```html
|
|
1082
|
+
|
|
542
1083
|
{% load humanize %}
|
|
1084
|
+
|
|
543
1085
|
{{ views|intcomma }}
|
|
1086
|
+
|
|
544
1087
|
{{ count|ordinal }}
|
|
1088
|
+
|
|
545
1089
|
```
|
|
546
1090
|
|
|
1091
|
+
|
|
1092
|
+
|
|
547
1093
|
=== "CommonJS (registering a library)"
|
|
548
1094
|
|
|
1095
|
+
|
|
1096
|
+
|
|
549
1097
|
```javascript
|
|
1098
|
+
|
|
550
1099
|
const { registerLibrary } = require('miki-template');
|
|
551
1100
|
|
|
1101
|
+
|
|
1102
|
+
|
|
552
1103
|
registerLibrary('myutils', {
|
|
1104
|
+
|
|
553
1105
|
filters: {
|
|
1106
|
+
|
|
554
1107
|
shout: (val) => String(val).toUpperCase() + '!'
|
|
1108
|
+
|
|
555
1109
|
}
|
|
1110
|
+
|
|
556
1111
|
});
|
|
1112
|
+
|
|
557
1113
|
```
|
|
558
1114
|
|
|
1115
|
+
|
|
1116
|
+
|
|
559
1117
|
=== "ES Modules"
|
|
560
1118
|
|
|
1119
|
+
|
|
1120
|
+
|
|
561
1121
|
```javascript
|
|
1122
|
+
|
|
562
1123
|
import { registerLibrary } from 'miki-template';
|
|
563
1124
|
|
|
1125
|
+
|
|
1126
|
+
|
|
564
1127
|
registerLibrary('myutils', {
|
|
1128
|
+
|
|
565
1129
|
filters: {
|
|
1130
|
+
|
|
566
1131
|
shout: (val) => String(val).toUpperCase() + '!'
|
|
1132
|
+
|
|
567
1133
|
}
|
|
1134
|
+
|
|
568
1135
|
});
|
|
1136
|
+
|
|
569
1137
|
```
|
|
570
1138
|
|
|
1139
|
+
|
|
1140
|
+
|
|
571
1141
|
Then use in templates:
|
|
572
1142
|
|
|
1143
|
+
|
|
1144
|
+
|
|
573
1145
|
```html
|
|
1146
|
+
|
|
574
1147
|
{% load myutils %}
|
|
1148
|
+
|
|
575
1149
|
{{ name|shout }}
|
|
1150
|
+
|
|
576
1151
|
```
|
|
577
1152
|
|
|
1153
|
+
|
|
1154
|
+
|
|
578
1155
|
**Built-in libraries:**
|
|
579
1156
|
|
|
1157
|
+
|
|
1158
|
+
|
|
580
1159
|
- `i18n` — `{% trans %}`, `{% blocktrans %}`, `{% language %}`
|
|
1160
|
+
|
|
581
1161
|
- `humanize` — `intcomma`, `intword`, `apnumber`, `ordinal`, `naturalday`
|
|
1162
|
+
|
|
582
1163
|
- `cache` — `{% cache timeout key %}...{% endcache %}`
|
|
1164
|
+
|
|
583
1165
|
- `lorem` — `{% lorem %}` tag and `lorem` filter
|
|
584
1166
|
|
|
1167
|
+
|
|
1168
|
+
|
|
585
1169
|
---
|
|
586
1170
|
|
|
1171
|
+
|
|
1172
|
+
|
|
587
1173
|
## Template Tags
|
|
588
1174
|
|
|
1175
|
+
|
|
1176
|
+
|
|
589
1177
|
### templatetag
|
|
590
1178
|
|
|
1179
|
+
|
|
1180
|
+
|
|
591
1181
|
Output literal template tag tokens. Useful when generating documentation or when the template syntax conflicts with another templating layer.
|
|
592
1182
|
|
|
1183
|
+
|
|
1184
|
+
|
|
593
1185
|
```html
|
|
1186
|
+
|
|
594
1187
|
{% templatetag openblock %} if user.is_admin {% templatetag closeblock %}
|
|
1188
|
+
|
|
595
1189
|
<!-- Renders: {% if user.is_admin %} -->
|
|
596
1190
|
|
|
1191
|
+
|
|
1192
|
+
|
|
597
1193
|
{% templatetag openvariable %} name {% templatetag closevariable %}
|
|
1194
|
+
|
|
598
1195
|
<!-- Renders: {{ name }} -->
|
|
1196
|
+
|
|
599
1197
|
```
|
|
600
1198
|
|
|
1199
|
+
|
|
1200
|
+
|
|
601
1201
|
Available tokens:
|
|
602
1202
|
|
|
1203
|
+
|
|
1204
|
+
|
|
603
1205
|
| Token | Output |
|
|
1206
|
+
|
|
604
1207
|
|-------|--------|
|
|
1208
|
+
|
|
605
1209
|
| `openblock` | `{%` |
|
|
1210
|
+
|
|
606
1211
|
| `closeblock` | `%}` |
|
|
1212
|
+
|
|
607
1213
|
| `openvariable` | `{{` |
|
|
1214
|
+
|
|
608
1215
|
| `closevariable` | `}}` |
|
|
1216
|
+
|
|
609
1217
|
| `openbrace` | `{` |
|
|
1218
|
+
|
|
610
1219
|
| `closebrace` | `}` |
|
|
1220
|
+
|
|
611
1221
|
| `opencomment` | `{#` |
|
|
1222
|
+
|
|
612
1223
|
| `closecomment` | `#}` |
|
|
613
1224
|
|
|
1225
|
+
|
|
1226
|
+
|
|
614
1227
|
---
|
|
615
1228
|
|
|
1229
|
+
|
|
1230
|
+
|
|
616
1231
|
## Inheritance Tags
|
|
617
1232
|
|
|
1233
|
+
|
|
1234
|
+
|
|
618
1235
|
### extends
|
|
619
1236
|
|
|
1237
|
+
|
|
1238
|
+
|
|
620
1239
|
Inherit from a parent template.
|
|
621
1240
|
|
|
1241
|
+
|
|
1242
|
+
|
|
622
1243
|
```html
|
|
1244
|
+
|
|
623
1245
|
{% extends "base.html" %}
|
|
1246
|
+
|
|
624
1247
|
```
|
|
625
1248
|
|
|
1249
|
+
|
|
1250
|
+
|
|
626
1251
|
Can use expressions for dynamic parent selection:
|
|
627
1252
|
|
|
1253
|
+
|
|
1254
|
+
|
|
628
1255
|
```html
|
|
1256
|
+
|
|
629
1257
|
{% extends device|default:"desktop/base.html" %}
|
|
1258
|
+
|
|
630
1259
|
```
|
|
631
1260
|
|
|
1261
|
+
|
|
1262
|
+
|
|
632
1263
|
**Security:** Path traversal is blocked — `{% extends "../../etc/passwd" %}` is rejected.
|
|
633
1264
|
|
|
1265
|
+
|
|
1266
|
+
|
|
634
1267
|
### block / endblock
|
|
635
1268
|
|
|
1269
|
+
|
|
1270
|
+
|
|
636
1271
|
Define a block that can be overridden by child templates.
|
|
637
1272
|
|
|
1273
|
+
|
|
1274
|
+
|
|
638
1275
|
```html
|
|
1276
|
+
|
|
639
1277
|
<!-- base.html -->
|
|
1278
|
+
|
|
640
1279
|
<html>
|
|
1280
|
+
|
|
641
1281
|
<body>
|
|
1282
|
+
|
|
642
1283
|
{% block content %}Default content{% endblock %}
|
|
1284
|
+
|
|
643
1285
|
</body>
|
|
1286
|
+
|
|
644
1287
|
</html>
|
|
1288
|
+
|
|
645
1289
|
```
|
|
646
1290
|
|
|
1291
|
+
|
|
1292
|
+
|
|
647
1293
|
```html
|
|
1294
|
+
|
|
648
1295
|
<!-- child.html -->
|
|
1296
|
+
|
|
649
1297
|
{% extends "base.html" %}
|
|
1298
|
+
|
|
650
1299
|
{% block content %}
|
|
1300
|
+
|
|
651
1301
|
<h1>Child content</h1>
|
|
1302
|
+
|
|
652
1303
|
{{ block.super }}
|
|
1304
|
+
|
|
653
1305
|
{% endblock %}
|
|
1306
|
+
|
|
654
1307
|
```
|
|
655
1308
|
|
|
1309
|
+
|
|
1310
|
+
|
|
656
1311
|
### block.super
|
|
657
1312
|
|
|
1313
|
+
|
|
1314
|
+
|
|
658
1315
|
A special variable (not a tag). When used inside a `{% block %}`, it renders the parent template's version of that block.
|
|
659
1316
|
|
|
1317
|
+
|
|
1318
|
+
|
|
660
1319
|
See [Template Inheritance](template-inheritance.md) for a detailed guide.
|
|
661
1320
|
|
|
1321
|
+
|
|
1322
|
+
|
|
662
1323
|
### include
|
|
663
1324
|
|
|
1325
|
+
|
|
1326
|
+
|
|
664
1327
|
Include another template's content inline.
|
|
665
1328
|
|
|
1329
|
+
|
|
1330
|
+
|
|
666
1331
|
```html
|
|
1332
|
+
|
|
667
1333
|
{% include "header.html" %}
|
|
1334
|
+
|
|
668
1335
|
{% include "header.html" with title="Hello" %}
|
|
1336
|
+
|
|
669
1337
|
{% include "header.html#partial_name" %}
|
|
1338
|
+
|
|
670
1339
|
{% include "header.html" with title="Hello" %}
|
|
1340
|
+
|
|
671
1341
|
```
|
|
672
1342
|
|
|
1343
|
+
|
|
1344
|
+
|
|
673
1345
|
**Security:** Path traversal is blocked.
|
|
674
1346
|
|
|
1347
|
+
|
|
1348
|
+
|
|
675
1349
|
---
|
|
676
1350
|
|
|
1351
|
+
|
|
1352
|
+
|
|
677
1353
|
## Partial Tags
|
|
678
1354
|
|
|
1355
|
+
|
|
1356
|
+
|
|
679
1357
|
### partialdef / endpartialdef
|
|
680
1358
|
|
|
1359
|
+
|
|
1360
|
+
|
|
681
1361
|
Define a reusable partial block.
|
|
682
1362
|
|
|
1363
|
+
|
|
1364
|
+
|
|
683
1365
|
```html
|
|
1366
|
+
|
|
684
1367
|
{% partialdef card %}
|
|
1368
|
+
|
|
685
1369
|
<div class="card">
|
|
1370
|
+
|
|
686
1371
|
<h3>{{ title|default:"Untitled" }}</h3>
|
|
1372
|
+
|
|
687
1373
|
<p>{{ body|truncatewords:30 }}</p>
|
|
1374
|
+
|
|
688
1375
|
{% if featured %}<em>Featured</em>{% endif %}
|
|
1376
|
+
|
|
689
1377
|
</div>
|
|
1378
|
+
|
|
690
1379
|
{% endpartialdef %}
|
|
1380
|
+
|
|
691
1381
|
```
|
|
692
1382
|
|
|
1383
|
+
|
|
1384
|
+
|
|
693
1385
|
**Options:**
|
|
694
1386
|
|
|
1387
|
+
|
|
1388
|
+
|
|
695
1389
|
| Option | Description |
|
|
1390
|
+
|
|
696
1391
|
|--------|-------------|
|
|
1392
|
+
|
|
697
1393
|
| `inline` | Renders the definition inline at its location during parse (the body appears in output AND registers for later use). |
|
|
698
1394
|
|
|
1395
|
+
|
|
1396
|
+
|
|
699
1397
|
```html
|
|
1398
|
+
|
|
700
1399
|
{% partialdef greeting inline %}
|
|
1400
|
+
|
|
701
1401
|
Hello {{ name }}!
|
|
1402
|
+
|
|
702
1403
|
{% endpartialdef %}
|
|
1404
|
+
|
|
703
1405
|
<!-- Above line ALSO outputs "Hello World!" when rendered -->
|
|
1406
|
+
|
|
704
1407
|
```
|
|
705
1408
|
|
|
1409
|
+
|
|
1410
|
+
|
|
706
1411
|
**Programmatic access:**
|
|
707
1412
|
|
|
1413
|
+
|
|
1414
|
+
|
|
708
1415
|
=== "CommonJS"
|
|
709
1416
|
|
|
1417
|
+
|
|
1418
|
+
|
|
710
1419
|
```javascript
|
|
1420
|
+
|
|
711
1421
|
const { compile } = require('miki-template');
|
|
1422
|
+
|
|
712
1423
|
const compiled = compile(template);
|
|
1424
|
+
|
|
713
1425
|
compiled.renderPartial('card', { title: 'Hi', body: 'There' });
|
|
1426
|
+
|
|
714
1427
|
```
|
|
715
1428
|
|
|
1429
|
+
|
|
1430
|
+
|
|
716
1431
|
=== "ES Modules"
|
|
717
1432
|
|
|
1433
|
+
|
|
1434
|
+
|
|
718
1435
|
```javascript
|
|
1436
|
+
|
|
719
1437
|
import { compile } from 'miki-template';
|
|
1438
|
+
|
|
720
1439
|
const compiled = compile(template);
|
|
1440
|
+
|
|
721
1441
|
compiled.renderPartial('card', { title: 'Hi', body: 'There' });
|
|
1442
|
+
|
|
722
1443
|
```
|
|
723
1444
|
|
|
1445
|
+
|
|
1446
|
+
|
|
724
1447
|
### partial
|
|
725
1448
|
|
|
1449
|
+
|
|
1450
|
+
|
|
726
1451
|
Render a named partial.
|
|
727
1452
|
|
|
1453
|
+
|
|
1454
|
+
|
|
728
1455
|
```html
|
|
1456
|
+
|
|
729
1457
|
{% partial card %}
|
|
1458
|
+
|
|
730
1459
|
{% partial card with title="Custom" body="World" %}
|
|
1460
|
+
|
|
731
1461
|
{% partial greeting with name=user.name %}
|
|
1462
|
+
|
|
732
1463
|
```
|
|
733
1464
|
|
|
1465
|
+
|
|
1466
|
+
|
|
734
1467
|
See [Partial Templates](partial-templates.md) for a detailed guide.
|
|
735
1468
|
|
|
1469
|
+
|
|
1470
|
+
|
|
736
1471
|
---
|
|
737
1472
|
|
|
1473
|
+
|
|
1474
|
+
|
|
738
1475
|
## i18n Tags
|
|
739
1476
|
|
|
1477
|
+
|
|
1478
|
+
|
|
740
1479
|
### trans
|
|
741
1480
|
|
|
1481
|
+
|
|
1482
|
+
|
|
742
1483
|
Translate a string.
|
|
743
1484
|
|
|
1485
|
+
|
|
1486
|
+
|
|
744
1487
|
```html
|
|
1488
|
+
|
|
745
1489
|
{% trans "Hello, world!" %}
|
|
1490
|
+
|
|
746
1491
|
{% trans "Hello, %s!" name=user.name %}
|
|
1492
|
+
|
|
747
1493
|
{% trans context "verb" "He runs" %}
|
|
1494
|
+
|
|
748
1495
|
```
|
|
749
1496
|
|
|
1497
|
+
|
|
1498
|
+
|
|
750
1499
|
### blocktrans / endblocktrans
|
|
751
1500
|
|
|
1501
|
+
|
|
1502
|
+
|
|
752
1503
|
Translate a block of text with variable interpolation and pluralization.
|
|
753
1504
|
|
|
1505
|
+
|
|
1506
|
+
|
|
754
1507
|
```html
|
|
1508
|
+
|
|
755
1509
|
{% blocktrans with name=user.name %}
|
|
1510
|
+
|
|
756
1511
|
Hello, {{ name }}!
|
|
1512
|
+
|
|
757
1513
|
{% endblocktrans %}
|
|
758
1514
|
|
|
1515
|
+
|
|
1516
|
+
|
|
759
1517
|
{% blocktrans count items|length %}
|
|
1518
|
+
|
|
760
1519
|
{{ count }} item
|
|
1520
|
+
|
|
761
1521
|
{% plural %}
|
|
1522
|
+
|
|
762
1523
|
{{ count }} items
|
|
1524
|
+
|
|
763
1525
|
{% endblocktrans %}
|
|
1526
|
+
|
|
764
1527
|
```
|
|
765
1528
|
|
|
1529
|
+
|
|
1530
|
+
|
|
766
1531
|
### language / endlanguage
|
|
767
1532
|
|
|
1533
|
+
|
|
1534
|
+
|
|
768
1535
|
Switch language temporarily for a block.
|
|
769
1536
|
|
|
1537
|
+
|
|
1538
|
+
|
|
770
1539
|
```html
|
|
1540
|
+
|
|
771
1541
|
{% language "fr" %}
|
|
1542
|
+
|
|
772
1543
|
{% trans "Hello" %} → renders in French
|
|
1544
|
+
|
|
773
1545
|
{% endlanguage %}
|
|
1546
|
+
|
|
774
1547
|
```
|
|
775
1548
|
|
|
1549
|
+
|
|
1550
|
+
|
|
776
1551
|
---
|
|
777
1552
|
|
|
1553
|
+
|
|
1554
|
+
|
|
778
1555
|
## Next Steps
|
|
779
1556
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
- [
|
|
783
|
-
|
|
1557
|
+
|
|
1558
|
+
|
|
1559
|
+
- [Filters](./filters.md)
|
|
1560
|
+
|
|
1561
|
+
- [Template Inheritance](./template-inheritance.md)
|
|
1562
|
+
|
|
1563
|
+
- [Partial Templates](./partial-templates.md)
|
|
1564
|
+
|
|
1565
|
+
- [Custom Tags](./custom-tags.md)
|
|
1566
|
+
|