retold 4.0.4 → 4.0.8
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/.claude/launch.json +47 -0
- package/.claude/settings.local.json +45 -107
- package/CLAUDE.md +9 -7
- package/README.md +9 -7
- package/Retold-Modules-Manifest.json +616 -0
- package/docs/README.md +7 -1
- package/docs/{cover.md → _cover.md} +1 -1
- package/docs/_sidebar.md +30 -3
- package/docs/architecture/architecture.md +5 -2
- package/docs/architecture/dependencies/_generate-graph.js +186 -0
- package/docs/architecture/dependencies/_generate-svg.js +364 -0
- package/docs/architecture/dependencies/in-ecosystem-dependency-graph-generation.md +97 -0
- package/docs/architecture/dependencies/in-ecosystem-dependency-graph.json +3168 -0
- package/docs/architecture/dependencies/in-ecosystem-dependency-graph.md +221 -0
- package/docs/architecture/dependencies/in-ecosystem-dependency-graph.svg +664 -0
- package/docs/architecture/documentation-style-guide.md +65 -0
- package/docs/architecture/example-app-style-guide.md +154 -0
- package/docs/architecture/modules.md +33 -12
- package/docs/architecture/templating/data-access.md +196 -0
- package/docs/architecture/templating/data-formatting.md +350 -0
- package/docs/architecture/templating/data-generation.md +72 -0
- package/docs/architecture/templating/debugging.md +181 -0
- package/docs/architecture/templating/entity.md +99 -0
- package/docs/architecture/templating/iteration.md +170 -0
- package/docs/architecture/templating/jellyfish-deep-dive.md +271 -0
- package/docs/architecture/templating/jellyfish-templates.md +476 -0
- package/docs/architecture/templating/logic.md +185 -0
- package/docs/architecture/templating/ref-breakpoint.md +38 -0
- package/docs/architecture/templating/ref-data.md +51 -0
- package/docs/architecture/templating/ref-dateonlyformat.md +43 -0
- package/docs/architecture/templating/ref-dateonlyymd.md +39 -0
- package/docs/architecture/templating/ref-datetimeformat.md +59 -0
- package/docs/architecture/templating/ref-datetimeymd.md +44 -0
- package/docs/architecture/templating/ref-dejs.md +42 -0
- package/docs/architecture/templating/ref-digits.md +36 -0
- package/docs/architecture/templating/ref-dj.md +50 -0
- package/docs/architecture/templating/ref-dollars.md +36 -0
- package/docs/architecture/templating/ref-dt.md +38 -0
- package/docs/architecture/templating/ref-dvbk.md +46 -0
- package/docs/architecture/templating/ref-dwaf.md +45 -0
- package/docs/architecture/templating/ref-dwtf.md +45 -0
- package/docs/architecture/templating/ref-entity.md +47 -0
- package/docs/architecture/templating/ref-hce.md +29 -0
- package/docs/architecture/templating/ref-hcs.md +38 -0
- package/docs/architecture/templating/ref-join.md +45 -0
- package/docs/architecture/templating/ref-joinunique.md +34 -0
- package/docs/architecture/templating/ref-ls.md +37 -0
- package/docs/architecture/templating/ref-lv.md +38 -0
- package/docs/architecture/templating/ref-lvt.md +33 -0
- package/docs/architecture/templating/ref-ne.md +40 -0
- package/docs/architecture/templating/ref-pascalcaseidentifier.md +41 -0
- package/docs/architecture/templating/ref-pict.md +42 -0
- package/docs/architecture/templating/ref-pluckjoinunique.md +39 -0
- package/docs/architecture/templating/ref-rn.md +35 -0
- package/docs/architecture/templating/ref-rns.md +35 -0
- package/docs/architecture/templating/ref-sbr.md +36 -0
- package/docs/architecture/templating/ref-solve.md +46 -0
- package/docs/architecture/templating/ref-tbda.md +41 -0
- package/docs/architecture/templating/ref-tbr.md +43 -0
- package/docs/architecture/templating/ref-tbt.md +46 -0
- package/docs/architecture/templating/ref-template.md +40 -0
- package/docs/architecture/templating/ref-tfa.md +32 -0
- package/docs/architecture/templating/ref-tfm.md +43 -0
- package/docs/architecture/templating/ref-tif.md +45 -0
- package/docs/architecture/templating/ref-tifabs.md +41 -0
- package/docs/architecture/templating/ref-ts.md +41 -0
- package/docs/architecture/templating/ref-tsfm.md +42 -0
- package/docs/architecture/templating/ref-tswp.md +45 -0
- package/docs/architecture/templating/ref-tvs.md +48 -0
- package/docs/architecture/templating/ref-view.md +40 -0
- package/docs/architecture/templating/ref-vrs.md +39 -0
- package/docs/architecture/templating/solvers.md +153 -0
- package/docs/architecture/templating/template-composition.md +196 -0
- package/docs/architecture/templating/template-expressions.md +217 -0
- package/docs/architecture/templating/views.md +154 -0
- package/docs/examples/todolist/todo-list.md +1 -1
- package/docs/modules/apps.md +26 -0
- package/docs/modules/pict.md +18 -0
- package/docs/modules/utility.md +23 -1
- package/docs/retold-catalog.json +2541 -307
- package/docs/retold-keyword-index.json +267578 -117399
- package/modules/CLAUDE.md +1 -0
- package/modules/Checkout.sh +1 -0
- package/modules/Diff.sh +86 -0
- package/modules/Include-Retold-Module-List.sh +4 -2
- package/modules/Status.sh +1 -0
- package/modules/Update.sh +1 -0
- package/modules/apps/Apps.md +1 -0
- package/modules/utility/Utility.md +1 -0
- package/package.json +9 -11
- package/docs/retold-building-documentation.md +0 -33
- package/modules/.claude/settings.local.json +0 -52
- package/modules/Retold-Modules.md +0 -24
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Iteration Expressions
|
|
2
|
+
|
|
3
|
+
Iteration expressions render a template once for each item in a collection. These are the primary mechanism for generating lists, tables, and repeated structures.
|
|
4
|
+
|
|
5
|
+
## TemplateSet (TS)
|
|
6
|
+
|
|
7
|
+
Renders a registered template once for each item in an array or object. Each item becomes the `Record` inside the template.
|
|
8
|
+
|
|
9
|
+
**Tags:** `{~TemplateSet:TEMPLATE_HASH:COLLECTION_ADDRESS~}` `{~TS:TEMPLATE_HASH:COLLECTION_ADDRESS~}`
|
|
10
|
+
|
|
11
|
+
**Parameters:**
|
|
12
|
+
|
|
13
|
+
| Parameter | Description |
|
|
14
|
+
|-----------|-------------|
|
|
15
|
+
| TEMPLATE_HASH | Hash of the template to render for each item |
|
|
16
|
+
| COLLECTION_ADDRESS | Address of an array or object to iterate over |
|
|
17
|
+
|
|
18
|
+
**Examples:**
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
_Pict.TemplateProvider.addTemplate('ProductRow',
|
|
22
|
+
'<tr><td>{~D:Record.Name~}</td><td>{~Dollars:Record.Price~}</td></tr>');
|
|
23
|
+
|
|
24
|
+
_Pict.AppData.Products = [
|
|
25
|
+
{ Name: 'Widget', Price: 9.99 },
|
|
26
|
+
{ Name: 'Gadget', Price: 19.99 },
|
|
27
|
+
{ Name: 'Sprocket', Price: 4.50 }
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
_Pict.parseTemplate('<table>{~TS:ProductRow:AppData.Products~}</table>');
|
|
31
|
+
// '<table><tr><td>Widget</td><td>$9.99</td></tr><tr><td>Gadget</td><td>$19.99</td></tr><tr><td>Sprocket</td><td>$4.50</td></tr></table>'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
When iterating over an object, each value becomes Record and the iteration follows the object's key order.
|
|
35
|
+
|
|
36
|
+
TemplateSet is the workhorse of list rendering. Every table body, navigation menu, card grid, or repeated structure typically uses a TemplateSet.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## TemplateSetFromMap (TSFM)
|
|
41
|
+
|
|
42
|
+
Looks up a key in a map to get an array, then renders a template for each item in that array.
|
|
43
|
+
|
|
44
|
+
**Tags:** `{~TemplateSetFromMap:TEMPLATE_HASH:MAP_ADDRESS:KEY_ADDRESS~}` `{~TSFM:TEMPLATE_HASH:MAP_ADDRESS:KEY_ADDRESS~}`
|
|
45
|
+
|
|
46
|
+
**Parameters:**
|
|
47
|
+
|
|
48
|
+
| Parameter | Description |
|
|
49
|
+
|-----------|-------------|
|
|
50
|
+
| TEMPLATE_HASH | Hash of the template to render for each item |
|
|
51
|
+
| MAP_ADDRESS | Address of a map/object whose values are arrays |
|
|
52
|
+
| KEY_ADDRESS | Address containing the key to look up in the map |
|
|
53
|
+
|
|
54
|
+
**Examples:**
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
_Pict.AppData.TeamRoster = {
|
|
58
|
+
'Engineering': [
|
|
59
|
+
{ Name: 'Alice', Title: 'Lead' },
|
|
60
|
+
{ Name: 'Bob', Title: 'Senior' }
|
|
61
|
+
],
|
|
62
|
+
'Design': [
|
|
63
|
+
{ Name: 'Carol', Title: 'Director' },
|
|
64
|
+
{ Name: 'Dave', Title: 'Senior' }
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
_Pict.TemplateProvider.addTemplate('MemberRow',
|
|
69
|
+
'<li>{~D:Record.Name~} - {~D:Record.Title~}</li>');
|
|
70
|
+
|
|
71
|
+
_Pict.parseTemplate(
|
|
72
|
+
'<ul>{~TSFM:MemberRow:AppData.TeamRoster:Record.Department~}</ul>',
|
|
73
|
+
{ Department: 'Engineering' });
|
|
74
|
+
// '<ul><li>Alice - Lead</li><li>Bob - Senior</li></ul>'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This is useful when your data is organized as a map of arrays (e.g., items grouped by category, team members grouped by department) and you need to render one group at a time.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## TemplateSetWithPayload (TSWP)
|
|
82
|
+
|
|
83
|
+
Renders a template for each item in a collection, wrapping each item as `{ Data: <item>, Payload: <payloadData> }`. Inside templates, use `Record.Data` for the current item and `Record.Payload` for the extra data.
|
|
84
|
+
|
|
85
|
+
**Tags:** `{~TemplateSetWithPayload:TEMPLATE_HASH:COLLECTION_ADDRESS:PAYLOAD_ADDRESS~}` `{~TSWP:TEMPLATE_HASH:COLLECTION_ADDRESS:PAYLOAD_ADDRESS~}`
|
|
86
|
+
|
|
87
|
+
**Parameters:**
|
|
88
|
+
|
|
89
|
+
| Parameter | Description |
|
|
90
|
+
|-----------|-------------|
|
|
91
|
+
| TEMPLATE_HASH | Hash of the template to render for each item |
|
|
92
|
+
| COLLECTION_ADDRESS | Address of an array to iterate over |
|
|
93
|
+
| PAYLOAD_ADDRESS | Address of additional data to include with each item |
|
|
94
|
+
|
|
95
|
+
**Examples:**
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
_Pict.TemplateProvider.addTemplate('TaskItem',
|
|
99
|
+
'<div class="{~D:Record.Payload.itemClass~}">{~D:Record.Data.Title~}</div>');
|
|
100
|
+
|
|
101
|
+
_Pict.AppData.Tasks = [
|
|
102
|
+
{ Title: 'Write tests' },
|
|
103
|
+
{ Title: 'Fix bug' },
|
|
104
|
+
{ Title: 'Deploy' }
|
|
105
|
+
];
|
|
106
|
+
_Pict.AppData.DisplayConfig = { itemClass: 'task-card' };
|
|
107
|
+
|
|
108
|
+
_Pict.parseTemplate(
|
|
109
|
+
'{~TSWP:TaskItem:AppData.Tasks:AppData.DisplayConfig~}');
|
|
110
|
+
// '<div class="task-card">Write tests</div><div class="task-card">Fix bug</div><div class="task-card">Deploy</div>'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The payload pattern solves a common problem: when rendering a list, each item template needs access to shared configuration (CSS classes, labels, feature flags) that is not part of the item data itself. Without payload, you would need to reference `AppData` directly for this shared data. With payload, the template is self-contained and reusable.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## TemplateValueSet (TVS)
|
|
118
|
+
|
|
119
|
+
Iterates over the values of an object or array. Each value becomes Record. For arrays, the record includes `Key`, `Value`, `Index`, and `Count` properties. For objects, keys are iterated in sorted order.
|
|
120
|
+
|
|
121
|
+
**Tags:** `{~TemplateValueSet:TEMPLATE_HASH:DATA_ADDRESS~}` `{~TVS:TEMPLATE_HASH:DATA_ADDRESS~}`
|
|
122
|
+
|
|
123
|
+
**Parameters:**
|
|
124
|
+
|
|
125
|
+
| Parameter | Description |
|
|
126
|
+
|-----------|-------------|
|
|
127
|
+
| TEMPLATE_HASH | Hash of the template to render for each value |
|
|
128
|
+
| DATA_ADDRESS | Address of an object or array |
|
|
129
|
+
|
|
130
|
+
**Array Record Properties:**
|
|
131
|
+
|
|
132
|
+
| Property | Description |
|
|
133
|
+
|----------|-------------|
|
|
134
|
+
| `Record.Key` | The array index |
|
|
135
|
+
| `Record.Value` | The value at that index |
|
|
136
|
+
| `Record.Index` | Same as Key (zero-based) |
|
|
137
|
+
| `Record.Count` | Total number of items |
|
|
138
|
+
|
|
139
|
+
**Examples:**
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
// Iterating over array values
|
|
143
|
+
_Pict.TemplateProvider.addTemplate('ValueItem',
|
|
144
|
+
'<li>{~D:Record.Value~}</li>');
|
|
145
|
+
|
|
146
|
+
_Pict.AppData.Tags = ['javascript', 'node', 'express'];
|
|
147
|
+
|
|
148
|
+
_Pict.parseTemplate('<ul>{~TVS:ValueItem:AppData.Tags~}</ul>');
|
|
149
|
+
// '<ul><li>javascript</li><li>node</li><li>express</li></ul>'
|
|
150
|
+
|
|
151
|
+
// Iterating over object values (sorted keys)
|
|
152
|
+
_Pict.TemplateProvider.addTemplate('UserEntry',
|
|
153
|
+
'<p>{~D:Record.Value~}</p>');
|
|
154
|
+
|
|
155
|
+
_Pict.AppData.Users = { '3': 'Charlie', '1': 'Alice', '2': 'Bob' };
|
|
156
|
+
|
|
157
|
+
_Pict.parseTemplate('{~TVS:UserEntry:AppData.Users~}');
|
|
158
|
+
// '<p>Alice</p><p>Bob</p><p>Charlie</p>' (sorted by key)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
TemplateValueSet is distinct from TemplateSet. TemplateSet expects each array item to be an object that becomes Record directly. TemplateValueSet wraps each value in a `{ Key, Value, Index, Count }` envelope. Use TemplateValueSet when iterating over simple values (strings, numbers) or when you need the index/count metadata.
|
|
162
|
+
|
|
163
|
+
## Choosing the Right Iteration Expression
|
|
164
|
+
|
|
165
|
+
| Scenario | Expression |
|
|
166
|
+
|----------|------------|
|
|
167
|
+
| Array of objects, render each as Record | `{~TS:...~}` |
|
|
168
|
+
| Map of arrays, render one group | `{~TSFM:...~}` |
|
|
169
|
+
| Array of objects, each needs shared payload data | `{~TSWP:...~}` |
|
|
170
|
+
| Array of simple values, or need index/count | `{~TVS:...~}` |
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Jellyfish Deep Dive
|
|
2
|
+
|
|
3
|
+
This document covers the internals of the Jellyfish template engine -- how templates are parsed, how expressions are resolved, and how the rendering pipeline works from string input to final output.
|
|
4
|
+
|
|
5
|
+
## Three-Layer Architecture
|
|
6
|
+
|
|
7
|
+
Jellyfish is built from three distinct layers, each responsible for a different part of template processing.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
graph TB
|
|
11
|
+
subgraph L3["Layer 3: Pict Template Expressions"]
|
|
12
|
+
direction TB
|
|
13
|
+
expressions["44 built-in expressions<br/><i>Data, Logic, Composition, Iteration, etc.</i>"]
|
|
14
|
+
custom["Custom expressions<br/><i>Your application-specific tags</i>"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
subgraph L2["Layer 2: Fable MetaTemplate"]
|
|
18
|
+
direction TB
|
|
19
|
+
meta["Pattern registration<br/>Template parsing orchestration"]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
subgraph L1["Layer 1: Precedent"]
|
|
23
|
+
direction TB
|
|
24
|
+
trie["Word-trie pattern matching<br/>Fast string scanning"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
L3 --> L2
|
|
28
|
+
L2 --> L1
|
|
29
|
+
|
|
30
|
+
style L3 fill:#f3e5f5,stroke:#ab47bc,color:#333
|
|
31
|
+
style L2 fill:#fff3e0,stroke:#ffa726,color:#333
|
|
32
|
+
style L1 fill:#fce4ec,stroke:#ef5350,color:#333
|
|
33
|
+
style expressions fill:#fff,stroke:#ce93d8,color:#333
|
|
34
|
+
style custom fill:#fff,stroke:#ce93d8,color:#333
|
|
35
|
+
style meta fill:#fff,stroke:#ffcc80,color:#333
|
|
36
|
+
style trie fill:#fff,stroke:#ef9a9a,color:#333
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Layer 1: Precedent
|
|
40
|
+
|
|
41
|
+
[Precedent](#/doc/utility/precedent) is a word-trie-based pattern matcher. It scans strings for registered start/end delimiter pairs and extracts the content between them. The trie structure makes this efficient even for long template strings with many registered patterns.
|
|
42
|
+
|
|
43
|
+
When you register `{~D:` as a start pattern and `~}` as an end pattern, Precedent builds a trie that recognizes this pair anywhere in a string. Given the input `Hello {~D:Record.Name~}!`, Precedent identifies the match and extracts `Record.Name` as the content between delimiters.
|
|
44
|
+
|
|
45
|
+
### Layer 2: Fable MetaTemplate
|
|
46
|
+
|
|
47
|
+
The MetaTemplate service wraps Precedent and manages the connection between pattern matches and their handlers. It lives in Fable (not Pict), which means the basic template engine is available to any Fable-based application.
|
|
48
|
+
|
|
49
|
+
MetaTemplate provides two key methods:
|
|
50
|
+
|
|
51
|
+
- `addPatternBoth(startTag, endTag, renderSync, renderAsync, context)` -- Registers a delimiter pair with both sync and async render functions.
|
|
52
|
+
- `parseString(templateString, data, callback, contextArray, scope, state)` -- Processes a template string, finding all matches and calling their registered handlers.
|
|
53
|
+
|
|
54
|
+
### Layer 3: Pict Template Expressions
|
|
55
|
+
|
|
56
|
+
Each expression is a class extending `pict-template` (which extends `fable-serviceproviderbase`). An expression registers its delimiter patterns in its constructor and implements `render()` and optionally `renderAsync()`.
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
class MyExpression extends libPictTemplate
|
|
60
|
+
{
|
|
61
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
62
|
+
{
|
|
63
|
+
super(pFable, pOptions, pServiceHash);
|
|
64
|
+
this.addPattern('{~MyTag:', '~}');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
render(pTemplateHash, pRecord, pContextArray, pScope, pState)
|
|
68
|
+
{
|
|
69
|
+
// pTemplateHash is the content between {~MyTag: and ~}
|
|
70
|
+
return 'result string';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## The Rendering Pipeline
|
|
76
|
+
|
|
77
|
+
When you call `pict.parseTemplate(templateString, record)`, the following sequence occurs:
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
graph TD
|
|
81
|
+
input["Template String Input<br/><code>'Hello {~D:Record.Name~}!'</code>"]
|
|
82
|
+
scan["Precedent Scan<br/>Find all {~TAG:...~} matches"]
|
|
83
|
+
extract["Extract Template Hash<br/><code>Record.Name</code> with tag <code>D</code>"]
|
|
84
|
+
lookup["Handler Lookup<br/>Find registered expression for <code>{~D:</code>"]
|
|
85
|
+
render["Expression Render<br/><code>render('Record.Name', record, ...)</code>"]
|
|
86
|
+
resolve["State Resolution<br/>Resolve <code>Record.Name</code> from unified state"]
|
|
87
|
+
replace["String Replacement<br/>Replace <code>{~D:Record.Name~}</code> with result"]
|
|
88
|
+
recurse["Recursive Processing<br/>Check for remaining expressions"]
|
|
89
|
+
output["Final Output<br/><code>'Hello Alice!'</code>"]
|
|
90
|
+
|
|
91
|
+
input --> scan
|
|
92
|
+
scan --> extract
|
|
93
|
+
extract --> lookup
|
|
94
|
+
lookup --> render
|
|
95
|
+
render --> resolve
|
|
96
|
+
resolve --> replace
|
|
97
|
+
replace --> recurse
|
|
98
|
+
recurse --> output
|
|
99
|
+
|
|
100
|
+
style input fill:#e8f5e9,stroke:#43a047,color:#333
|
|
101
|
+
style output fill:#e8f5e9,stroke:#43a047,color:#333
|
|
102
|
+
style scan fill:#fce4ec,stroke:#ef5350,color:#333
|
|
103
|
+
style resolve fill:#fff3e0,stroke:#ffa726,color:#333
|
|
104
|
+
style render fill:#f3e5f5,stroke:#ab47bc,color:#333
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The recursive step is important. Template expressions can return strings that themselves contain template expressions. For example, `{~T:SomeTemplate~}` returns the content of `SomeTemplate`, which may contain `{~D:...~}` expressions that need further resolution. The engine continues processing until no more expressions remain.
|
|
108
|
+
|
|
109
|
+
## State Resolution
|
|
110
|
+
|
|
111
|
+
Every template expression has access to `resolveStateFromAddress()`, which resolves a dot-notation address against Pict's unified state object.
|
|
112
|
+
|
|
113
|
+
The unified state combines several namespaces into a single object:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
{
|
|
117
|
+
Fable: pict, // The Fable/Pict instance
|
|
118
|
+
Pict: pict, // Alias for the same instance
|
|
119
|
+
AppData: pict.AppData, // Persistent application state
|
|
120
|
+
TempData: pict.TempData, // Transient caches
|
|
121
|
+
Bundle: pict.Bundle, // Configuration and supporting data
|
|
122
|
+
Context: pContextArray, // Hierarchical context array
|
|
123
|
+
Record: pRecord, // Current record (loop item or passed data)
|
|
124
|
+
Scope: pScope, // Sticky scope for carrying state
|
|
125
|
+
__State: pState // Internal plumbing
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Address resolution uses [Manyfest](#/doc/utility/manyfest) for safe traversal. A path like `AppData.Users[0].Name` navigates the nested structure without throwing if any intermediate key is missing.
|
|
130
|
+
|
|
131
|
+
### Namespace Roles
|
|
132
|
+
|
|
133
|
+
**AppData** is the primary state container. Application code sets values here, and templates read them. It persists for the lifetime of the Pict instance.
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
_Pict.AppData.User = { Name: 'Alice', Role: 'admin' };
|
|
137
|
+
_Pict.parseTemplate('{~D:AppData.User.Name~}');
|
|
138
|
+
// 'Alice'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Record** is the context-specific data object. In a template set (`{~TS:...~}`), Record is the current item being iterated. When calling `parseTemplate` directly, the second argument becomes Record.
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
_Pict.parseTemplate('{~D:Record.Title~}', { Title: 'Dune' });
|
|
145
|
+
// 'Dune'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Bundle** holds configuration and supporting data that supplements AppData. Typically set during initialization.
|
|
149
|
+
|
|
150
|
+
**TempData** is for values that do not need to persist -- intermediate calculations, caches, or per-render scratch data.
|
|
151
|
+
|
|
152
|
+
**Context** is an array of objects accessible by index. It provides hierarchical context when templates are deeply nested.
|
|
153
|
+
|
|
154
|
+
**Scope** is a sticky state container that travels through template processing. A parent template can set scope values that child templates access. The `{~VRS:ViewHash~}` expression (View Retaining Scope) uses this to pass state into view rendering.
|
|
155
|
+
|
|
156
|
+
## Synchronous vs Asynchronous Rendering
|
|
157
|
+
|
|
158
|
+
Most expressions are synchronous -- they resolve data from memory and return a string immediately. The Data expression, formatting expressions, logic expressions, and solvers are all synchronous.
|
|
159
|
+
|
|
160
|
+
Some expressions require asynchronous work. The Entity expression (`{~E:...~}`) fetches records from a REST API. View expressions may trigger async rendering pipelines.
|
|
161
|
+
|
|
162
|
+
The engine handles both modes:
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
// Synchronous -- no callback, returns string
|
|
166
|
+
let tmpResult = _Pict.parseTemplate('{~D:AppData.Title~}');
|
|
167
|
+
|
|
168
|
+
// Asynchronous -- callback receives the result
|
|
169
|
+
_Pict.parseTemplate('{~E:Book^42^BookCard~}', {},
|
|
170
|
+
(pError, pResult) =>
|
|
171
|
+
{
|
|
172
|
+
console.log(pResult);
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
When a template contains a mix of sync and async expressions, the engine processes synchronous expressions immediately and queues async expressions. The callback fires when all async operations complete.
|
|
177
|
+
|
|
178
|
+
Every expression class defines both `render()` (sync) and `renderAsync()` (async). The base class default for `renderAsync` simply calls `render` and passes the result to the callback. Expressions that need true async behavior override `renderAsync`.
|
|
179
|
+
|
|
180
|
+
## Template Registration and Storage
|
|
181
|
+
|
|
182
|
+
Named templates are stored in the **TemplateProvider** -- a service that maps string hashes to template content.
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// Store a template
|
|
186
|
+
_Pict.TemplateProvider.addTemplate('BookCard',
|
|
187
|
+
'<div>{~D:Record.Title~}</div>');
|
|
188
|
+
|
|
189
|
+
// Retrieve it
|
|
190
|
+
let tmpTemplate = _Pict.TemplateProvider.getTemplate('BookCard');
|
|
191
|
+
// '<div>{~D:Record.Title~}</div>'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Templates can also be registered through view configurations, where the `Templates` array in a view's config is automatically loaded into the TemplateProvider.
|
|
195
|
+
|
|
196
|
+
### Default Templates
|
|
197
|
+
|
|
198
|
+
The TemplateProvider supports **default templates** -- patterns that match template hashes by prefix and postfix. This enables convention-based rendering without registering every template explicitly.
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// Register a default template matching any hash ending in '-ListRow'
|
|
202
|
+
_Pict.TemplateProvider.addDefaultTemplate('', '-ListRow',
|
|
203
|
+
'<li>{~D:Record.Name~}</li>');
|
|
204
|
+
|
|
205
|
+
// Now both of these resolve to the default:
|
|
206
|
+
_Pict.parseTemplate('{~TS:Product-ListRow:AppData.Products~}');
|
|
207
|
+
_Pict.parseTemplate('{~TS:User-ListRow:AppData.Users~}');
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The TemplateProvider looks for an exact match first. If none is found, it checks default templates for a matching prefix/postfix pattern.
|
|
211
|
+
|
|
212
|
+
## Parameter Conventions
|
|
213
|
+
|
|
214
|
+
Template expressions use two separator conventions:
|
|
215
|
+
|
|
216
|
+
- **Colon (`:`)** separates major parameters. For example, `{~T:TemplateHash:DataAddress~}` has two colon-separated parameters.
|
|
217
|
+
- **Caret (`^`)** separates sub-parameters within a single parameter. For example, `{~TIf:Template:Data:Left^==^Right~}` has a condition parameter with three caret-separated parts.
|
|
218
|
+
|
|
219
|
+
This distinction matters when parsing expression hashes. A TemplateIf expression receives `Template:Data:Left^==^Right` as its hash, splits on colons to get the three major parameters, then splits the third parameter on carets to get the comparison operands and operator.
|
|
220
|
+
|
|
221
|
+
## Nesting and Recursion
|
|
222
|
+
|
|
223
|
+
Templates can nest arbitrarily deep. A Template expression renders another template, which may contain TemplateSet expressions, which render templates that contain Data expressions, and so on.
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
_Pict.TemplateProvider.addTemplate('Page',
|
|
227
|
+
'<main>{~T:Header~}{~TS:Section:AppData.Sections~}{~T:Footer~}</main>');
|
|
228
|
+
|
|
229
|
+
_Pict.TemplateProvider.addTemplate('Header',
|
|
230
|
+
'<header><h1>{~D:AppData.Title~}</h1></header>');
|
|
231
|
+
|
|
232
|
+
_Pict.TemplateProvider.addTemplate('Section',
|
|
233
|
+
'<section><h2>{~D:Record.Heading~}</h2><p>{~D:Record.Body~}</p></section>');
|
|
234
|
+
|
|
235
|
+
_Pict.TemplateProvider.addTemplate('Footer',
|
|
236
|
+
'<footer>{~D:AppData.Copyright~}</footer>');
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Rendering `Page` triggers a cascade: the engine processes the Template expressions, which return content containing Data expressions, which are then resolved. Each level sees the same unified state but with the appropriate Record for the current iteration context.
|
|
240
|
+
|
|
241
|
+
## Expression Lifecycle
|
|
242
|
+
|
|
243
|
+
When Pict initializes, it calls `initializePictTemplateEngine()` which registers all 44 built-in expressions. Each expression:
|
|
244
|
+
|
|
245
|
+
1. Is instantiated as a Fable service
|
|
246
|
+
2. Registers its delimiter patterns with MetaTemplate (via `addPattern`)
|
|
247
|
+
3. Becomes available for immediate use in any template string
|
|
248
|
+
|
|
249
|
+
Custom expressions follow the same lifecycle. Calling `_Pict.addTemplate(ExpressionClass)` instantiates the class and registers its patterns.
|
|
250
|
+
|
|
251
|
+
The order of registration does not matter. Precedent's trie handles overlapping patterns correctly -- `{~D:` and `{~DJ:` and `{~DWTF:` all coexist because the trie matches the longest applicable start pattern.
|
|
252
|
+
|
|
253
|
+
## Error Handling
|
|
254
|
+
|
|
255
|
+
Jellyfish templates fail gracefully. The design principle is that a template should always produce output, even if some expressions cannot resolve.
|
|
256
|
+
|
|
257
|
+
- **Missing data:** Returns empty string. `{~D:AppData.Missing.Path~}` produces `''`.
|
|
258
|
+
- **Invalid template hash:** Logs a warning and returns empty string.
|
|
259
|
+
- **Missing named template:** The Template expression logs a warning and returns empty string.
|
|
260
|
+
- **Type mismatches:** Formatting expressions handle non-numeric input gracefully, typically returning the input unchanged or empty string.
|
|
261
|
+
- **Async errors:** Passed to the callback's error parameter without crashing the rendering pipeline.
|
|
262
|
+
|
|
263
|
+
This means a partially-populated data model still produces usable output. Missing values appear as empty strings rather than error messages or exceptions.
|
|
264
|
+
|
|
265
|
+
## Performance Considerations
|
|
266
|
+
|
|
267
|
+
Precedent's word-trie makes pattern scanning efficient. The trie is built once when patterns are registered and reused for every parse operation.
|
|
268
|
+
|
|
269
|
+
Template strings are not pre-compiled or cached by default. Each call to `parseTemplate` scans the string fresh. For templates rendered repeatedly with different data (like inside a TemplateSet), the named template system avoids string duplication -- the TemplateProvider stores each template once and the engine references it by hash.
|
|
270
|
+
|
|
271
|
+
For high-frequency rendering scenarios, the key optimization is keeping template strings short and using composition (`{~T:...~}`) to break large templates into smaller, focused pieces. This also improves readability and maintainability.
|