mikel 0.3.2 → 0.5.0

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.
Files changed (3) hide show
  1. package/README.md +185 -2
  2. package/index.js +45 -15
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -68,7 +68,9 @@ const result = m("{{^isAdmin}}You are not Admin{{/isAdmin}}", data);
68
68
  // Output: 'You are not Admin'
69
69
  ```
70
70
 
71
- ### Partials (added in v0.3.0)
71
+ ### Partials
72
+
73
+ > This feature was added in `v0.3.0`
72
74
 
73
75
  Partials allow you to include separate templates within your main template. Use the greater than symbol `>` followed by the partial name inside double curly braces `{{> partialName }}`.
74
76
 
@@ -87,7 +89,9 @@ const result = m("{{> hello}}", data, {partials});
87
89
  // Output: 'Hello Bob!'
88
90
  ```
89
91
 
90
- #### Custom context in partials (added in v0.3.1)
92
+ #### Custom context in partials
93
+
94
+ > This feature was added in `v0.3.1`.
91
95
 
92
96
  You can provide a custom context for the partial by specifying a field of the data: `{{> partialName dataField}}`.
93
97
 
@@ -106,6 +110,183 @@ const result = m("User: {{> user currentUser}}", data, {partials});
106
110
  // Output: 'User: John Doe <john@example.com>'
107
111
  ```
108
112
 
113
+ ### Built-in helpers
114
+
115
+ > Added in `v0.4.0`.
116
+
117
+ Helpers allows you to execute special functions within blocks or sections of your template. Mikel currently supports the following built-in helpers:
118
+
119
+ #### each
120
+
121
+ The `each` helper iterates over an array and renders the block for each item in the array.
122
+
123
+ Syntax: `{{#each arrayName}} ... {{/each}}`.
124
+
125
+ Example:
126
+
127
+ ```javascript
128
+ const data = {
129
+ users: ["John", "Alice", "Bob"],
130
+ };
131
+
132
+ console.log(m("{{#each users}}{{.}}, {{/each}}", data)); // --> 'John, Alice, Bob, '
133
+ ```
134
+
135
+ When looping throug arrays, you can use the variable `@index` to access to the current index of the item in the array:
136
+
137
+ ```javascript
138
+ const data = {
139
+ users: ["John", "Alice", "Bob"],
140
+ };
141
+
142
+ console.log(m("{{#each users}}{{@index}}: {{.}}, {{/each}}", data)); // --> '0: John, 1: Alice, 2: Bob, '
143
+ ```
144
+
145
+ The `each` helper can also iterate over objects:
146
+
147
+ ```javascript
148
+ const data = {
149
+ values: {
150
+ foo: "bar",
151
+ },
152
+ };
153
+
154
+ console.log(m("{{#each values}}{{.}}{{/each}}", data)); // --> 'bar'
155
+ ```
156
+
157
+ When looping throug objects, you can use the variable `@key` to access to the current key in the object, and the variable `@value` to access to the corresponding value:
158
+
159
+ ```javascript
160
+ const data = {
161
+ values: {
162
+ foo: "0",
163
+ bar: "1",
164
+ },
165
+ };
166
+
167
+ console.log(m("{{#each values}}{{@key}}: {{@value}}, {{/each}}", data)); // --> 'foo: 0, bar: 1, '
168
+ ```
169
+
170
+ #### if
171
+
172
+ The `if` helper renders the block only if the condition is truthy.
173
+
174
+ Syntax: `{{#if condition}} ... {{/if}}`
175
+
176
+ Example:
177
+
178
+ ```javascript
179
+ const data = {
180
+ isAdmin: true,
181
+ };
182
+
183
+ console.log(m("{{#if isAdmin}}Hello admin{{/if}}", data)); // --> 'Hello admin'
184
+ ```
185
+
186
+ #### unless
187
+
188
+ The `unless` helper renders the block only if the condition is falsy.
189
+
190
+ Syntax: `{{#unless condition}} ... {{/unless}}`
191
+
192
+ Example:
193
+
194
+ ```javascript
195
+ const data = {
196
+ isAdmin: false,
197
+ };
198
+
199
+ console.log(m("{{#unless isAdmin}}Hello guest{{/unless}}", data)); // --> 'Hello guest'
200
+ ```
201
+
202
+ ### Custom Helpers
203
+
204
+ > Added in `v0.5.0`.
205
+
206
+ Custom helpers should be provided as an object in the `options.helpers` field, where each key represents the name of the helper and the corresponding value is a function defining the helper's behavior.
207
+
208
+ Example:
209
+
210
+ ```javascript
211
+ const template = "{{#greeting name}}{{/greeting}}";
212
+ const data = {
213
+ name: "World!",
214
+ };
215
+ const options = {
216
+ helpers: {
217
+ customHelper: ({context, value, key, options, fn}) => {
218
+ return `Hello, ${value}!`;
219
+ },
220
+ },
221
+ };
222
+
223
+ const result = m(template, data, options);
224
+ console.log(result); // Output: "Hello, World!"
225
+ ```
226
+
227
+ Custom helper functions receive a single object parameter containing the following fields:
228
+
229
+ - `context`: The current context (data) where the helper has been executed.
230
+ - `value`: The current value passed to the helper.
231
+ - `key`: The field used to extract the value from the current context.
232
+ - `options`: The global options object.
233
+ - `fn`: A function that executes the template provided in the helper block and returns a string with the evaluated template in the provided context.
234
+
235
+ The helper function must return a string, which will be injected into the result string.
236
+
237
+ ### Variables
238
+
239
+ > Added in `v0.4.0`.
240
+
241
+ Data Variables in Mikel provide convenient access to special values within your templates. These variables, denoted by the `@` symbol, allow users to interact with specific data contexts or values.
242
+
243
+ #### @root
244
+
245
+ The `@root` variable grants access to the root data context provided to the template. It is always defined and enables users to retrieve values from the top-level data object.
246
+
247
+ Example:
248
+
249
+ ```javascript
250
+ const data = {
251
+ name: "World",
252
+ };
253
+
254
+ console.log(m("Hello, {{@root.name}}!", data)); // -> 'Hello, World!'
255
+ ```
256
+
257
+ #### @index
258
+
259
+ The `@index` variable facilitates access to the current index of the item when iterating over an array using the `#each` helper. It aids in dynamic rendering and indexing within loops.
260
+
261
+ #### @key
262
+
263
+ The `@key` variable allows users to retrieve the current key of the object entry when looping through an object using the `#each` helper. It provides access to object keys for dynamic rendering and customization.
264
+
265
+ #### @value
266
+
267
+ The `@value` variable allows users to retrieve the current value of the object entry when iterating over an object using the `#each` helper. It simplifies access to object values for dynamic rendering and data manipulation.
268
+
269
+ ### Custom variables
270
+
271
+ > Added in `v0.5.0`
272
+
273
+ Mikel allows users to define custom data variables, providing enhanced flexibility and customization options for templates. These custom data variables can be accessed within the template using the `@` character.
274
+
275
+ Custom data variables should be provided in the `options.variables` field of the options object when rendering a template. Each custom data variable should be defined as a key-value pair, where the key represents the variable name and the value represents the data associated with that variable.
276
+
277
+ Example:
278
+
279
+ ```javascript
280
+ const result = m("Hello, {{@customVariable}}!", {}, {
281
+ variables: {
282
+ customVariable: "World",
283
+ },
284
+ });
285
+ console.log(result); // --> 'Hello, World!'
286
+ ```
287
+
288
+ In this example, the custom data variable `customVariable` is defined with the value `"World"`, and it can be accessed in the template using `@customVariable`.
289
+
109
290
  ## API
110
291
 
111
292
  ### `m(template, data[, options])`
@@ -116,6 +297,8 @@ Render the given template string with the provided data object.
116
297
  - `data` (object): The data object containing the values to render.
117
298
  - `options` (object): An object containing the following optional values:
118
299
  - `partials` (object): An object containing the available partials.
300
+ - `variables` (object): An object containing custom data variables.
301
+ - `helpers` (object): An object containing custom helpers.
119
302
 
120
303
  Returns: A string with the rendered output.
121
304
 
package/index.js CHANGED
@@ -7,42 +7,72 @@ const escapedChars = {
7
7
  "'": "&#039;",
8
8
  };
9
9
 
10
- const escape = str => {
11
- return str.toString().replace(/[&<>\"']/g, m => escapedChars[m]);
12
- };
10
+ const escape = s => s.toString().replace(/[&<>\"']/g, m => escapedChars[m]);
13
11
 
14
- const get = (ctx, path) => {
15
- return path === "." ? ctx : (path.split(".").reduce((p, k) => p?.[k], ctx) || "");
16
- };
12
+ const get = (c, p) => (p === "." ? c : p.split(".").reduce((x, k) => x?.[k], c)) ?? "";
13
+
14
+ const helpers = new Map(Object.entries({
15
+ "each": ({value, fn}) => {
16
+ return (typeof value === "object" ? Object.entries(value || {}) : [])
17
+ .map((item, index) => fn(item[1], {index: index, key: item[0], value: item[1]}))
18
+ .join("");
19
+ },
20
+ "if": ({value, fn, context}) => !!value ? fn(context) : "",
21
+ "unless": ({value, fn, context}) => !!!value ? fn(context) : "",
22
+ }));
23
+
24
+ const hasHelper = (n, o) => helpers.has(n) || typeof o?.helpers?.[n] === "function";
25
+ const getHelper = (n, o) => helpers.get(n) || o?.helpers?.[n];
17
26
 
18
- const compile = (tokens, output, ctx, opt, index, section) => {
27
+ const compile = (tokens, output, context, opt, index = 0, section = "", vars = {}) => {
19
28
  let i = index;
20
29
  while (i < tokens.length) {
21
30
  if (i % 2 === 0) {
22
31
  output.push(tokens[i]);
23
32
  }
33
+ else if (tokens[i].startsWith("@")) {
34
+ output.push(get({...(opt?.variables), ...vars}, tokens[i].slice(1).trim() ?? "_") ?? "");
35
+ }
24
36
  else if (tokens[i].startsWith("!")) {
25
- output.push(get(ctx, tokens[i].slice(1).trim()));
37
+ output.push(get(context, tokens[i].slice(1).trim()));
38
+ }
39
+ else if (tokens[i].startsWith("#") && hasHelper(tokens[i].slice(1).trim().split(" ")[0], opt)) {
40
+ const [t, v] = tokens[i].slice(1).trim().split(" ");
41
+ const j = i + 1;
42
+ output.push(getHelper(t, opt)({
43
+ context: context,
44
+ key: v || ".",
45
+ value: get(context, v || "."),
46
+ options: opt,
47
+ fn: (blockContext = {}, blockVars = {}, blockOutput = []) => {
48
+ i = compile(tokens, blockOutput, blockContext, opt, j, t, {root: vars.root, ...blockVars});
49
+ return blockOutput.join("");
50
+ },
51
+ }));
52
+ // Make sure that this block has been executed
53
+ if (i + 1 === j) {
54
+ i = compile(tokens, [], {}, opt, j, t, vars);
55
+ }
26
56
  }
27
57
  else if (tokens[i].startsWith("#") || tokens[i].startsWith("^")) {
28
58
  const t = tokens[i].slice(1).trim();
29
- const value = get(ctx, t);
59
+ const value = get(context, t);
30
60
  const negate = tokens[i].startsWith("^");
31
61
  if (!negate && value && Array.isArray(value)) {
32
62
  const j = i + 1;
33
63
  (value.length > 0 ? value : [""]).forEach(item => {
34
- i = compile(tokens, value.length > 0 ? output : [], item, opt, j, t);
64
+ i = compile(tokens, value.length > 0 ? output : [], item, opt, j, t, vars);
35
65
  });
36
66
  }
37
67
  else {
38
68
  const includeOutput = (!negate && !!value) || (negate && !!!value);
39
- i = compile(tokens, includeOutput ? output : [], ctx, opt, i + 1, t);
69
+ i = compile(tokens, includeOutput ? output : [], context, opt, i + 1, t, vars);
40
70
  }
41
71
  }
42
72
  else if (tokens[i].startsWith(">")) {
43
73
  const [t, v] = tokens[i].slice(1).trim().split(" ");
44
74
  if (typeof opt?.partials?.[t] === "string") {
45
- compile(opt.partials[t].split(tags), output, v ? get(ctx, v) : ctx, opt, 0, "");
75
+ compile(opt.partials[t].split(tags), output, v ? get(context, v) : context, opt, 0, "", vars);
46
76
  }
47
77
  }
48
78
  else if (tokens[i].startsWith("/")) {
@@ -52,14 +82,14 @@ const compile = (tokens, output, ctx, opt, index, section) => {
52
82
  break;
53
83
  }
54
84
  else {
55
- output.push(escape(get(ctx, tokens[i].trim())));
85
+ output.push(escape(get(context, tokens[i].trim())));
56
86
  }
57
87
  i = i + 1;
58
88
  }
59
89
  return i;
60
90
  };
61
91
 
62
- export default (str, ctx = {}, opt = {}, output = []) => {
63
- compile(str.split(tags), output, ctx, opt, 0, "");
92
+ export default (str, context = {}, opt = {}, output = []) => {
93
+ compile(str.split(tags), output, context, opt, 0, "", {root: context});
64
94
  return output.join("");
65
95
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mikel",
3
3
  "description": "Micro templating library with zero dependencies",
4
- "version": "0.3.2",
4
+ "version": "0.5.0",
5
5
  "type": "module",
6
6
  "author": {
7
7
  "name": "Josemi Juanes",
@@ -20,7 +20,7 @@
20
20
  "test": "node test.js"
21
21
  },
22
22
  "keywords": [
23
- "mustache",
23
+ "template",
24
24
  "templating"
25
25
  ],
26
26
  "files": [