0
# Helper System
1
2
Extensible helper system for implementing complex template logic and custom functionality through user-defined helper functions.
3
4
## Capabilities
5
6
### Helper Registration
7
8
System for registering and managing custom helper functions that extend template capabilities.
9
10
```javascript { .api }
11
/**
12
* Container for all registered helpers
13
* Empty by default - all helpers must be added by users
14
*/
15
const helpers: { [helperName: string]: HelperFunction };
16
17
/**
18
* Helper function signature
19
* @param chunk - Output chunk for writing content
20
* @param context - Template context for data access
21
* @param bodies - Template bodies (block, else, etc.)
22
* @param params - Parameters passed to helper
23
* @returns Modified chunk
24
*/
25
type HelperFunction = (
26
chunk: Chunk,
27
context: Context,
28
bodies: Bodies,
29
params: Params
30
) => Chunk;
31
32
interface Bodies {
33
/** Main template body */
34
block?: TemplateFunction;
35
/** Else template body */
36
else?: TemplateFunction;
37
/** Named bodies for custom helpers */
38
[bodyName: string]: TemplateFunction | undefined;
39
}
40
41
interface Params {
42
/** Parameters passed to helper as key-value pairs */
43
[paramName: string]: any;
44
}
45
```
46
47
**Usage Examples:**
48
49
```javascript
50
const dust = require('dustjs-linkedin');
51
52
// Register a simple helper
53
dust.helpers.uppercase = (chunk, context, bodies, params) => {
54
if (bodies.block) {
55
return chunk.capture(bodies.block, context, (data, chunk) => {
56
return chunk.write(data.toUpperCase());
57
});
58
}
59
return chunk;
60
};
61
62
// Register a helper with parameters
63
dust.helpers.repeat = (chunk, context, bodies, params) => {
64
const times = parseInt(params.times) || 1;
65
66
if (bodies.block) {
67
for (let i = 0; i < times; i++) {
68
chunk.render(bodies.block, context.push({ index: i }));
69
}
70
}
71
72
return chunk;
73
};
74
75
// Register a conditional helper
76
dust.helpers.compare = (chunk, context, bodies, params) => {
77
const left = params.left;
78
const right = params.right;
79
const operator = params.operator || 'eq';
80
81
let result = false;
82
switch (operator) {
83
case 'eq': result = left == right; break;
84
case 'ne': result = left != right; break;
85
case 'gt': result = left > right; break;
86
case 'lt': result = left < right; break;
87
case 'gte': result = left >= right; break;
88
case 'lte': result = left <= right; break;
89
}
90
91
if (result && bodies.block) {
92
return chunk.render(bodies.block, context);
93
} else if (!result && bodies.else) {
94
return chunk.render(bodies.else, context);
95
}
96
97
return chunk;
98
};
99
100
// Usage in templates:
101
// {@uppercase}hello world{/uppercase} → HELLO WORLD
102
// {@repeat times="3"}Item {index}{/repeat} → Item 0Item 1Item 2
103
// {@compare left=age right=18 operator="gte"}Adult{:else}Minor{/compare}
104
```
105
106
### Helper Context Access
107
108
Helpers have full access to the template context and can manipulate data resolution.
109
110
```javascript { .api }
111
interface Context {
112
/** Get data from context */
113
get(path: string): any;
114
/** Get current context data */
115
current(): any;
116
/** Push new context data */
117
push(data: any): Context;
118
/** Template name for debugging */
119
getTemplateName(): string | undefined;
120
}
121
```
122
123
**Usage Examples:**
124
125
```javascript
126
// Helper that accesses context data
127
dust.helpers.userInfo = (chunk, context, bodies, params) => {
128
const user = context.get('user');
129
const currentData = context.current();
130
131
if (user) {
132
const info = `User: ${user.name} (${user.role})`;
133
return chunk.write(info);
134
}
135
136
return chunk.write('No user information');
137
};
138
139
// Helper that modifies context
140
dust.helpers.withDefaults = (chunk, context, bodies, params) => {
141
const defaults = params.defaults || {};
142
const enhanced = context.push(defaults);
143
144
if (bodies.block) {
145
return chunk.render(bodies.block, enhanced);
146
}
147
148
return chunk;
149
};
150
151
// Helper that creates isolated context
152
dust.helpers.isolate = (chunk, context, bodies, params) => {
153
const isolatedData = params.data || {};
154
const isolatedContext = dust.context(isolatedData);
155
156
if (bodies.block) {
157
return chunk.render(bodies.block, isolatedContext);
158
}
159
160
return chunk;
161
};
162
```
163
164
### Chunk Manipulation
165
166
Helpers work with chunks to control template output and handle asynchronous operations.
167
168
```javascript { .api }
169
interface Chunk {
170
/** Write string content to output */
171
write(data: string): Chunk;
172
/** End chunk with optional final content */
173
end(data?: string): Chunk;
174
/** Render template body with context */
175
render(body: TemplateFunction, context: Context): Chunk;
176
/** Capture rendered content for processing */
177
capture(body: TemplateFunction, context: Context, callback: (data: string, chunk: Chunk) => Chunk): Chunk;
178
/** Create asynchronous chunk for async operations */
179
map(callback: (chunk: Chunk) => Chunk): Chunk;
180
/** Set error on chunk */
181
setError(error: Error): Chunk;
182
}
183
```
184
185
**Usage Examples:**
186
187
```javascript
188
// Helper that manipulates output
189
dust.helpers.wrap = (chunk, context, bodies, params) => {
190
const tag = params.tag || 'div';
191
const className = params.class || '';
192
193
chunk.write(`<${tag}${className ? ` class="${className}"` : ''}>`);
194
195
if (bodies.block) {
196
chunk.render(bodies.block, context);
197
}
198
199
return chunk.write(`</${tag}>`);
200
};
201
202
// Helper that processes content
203
dust.helpers.markdown = (chunk, context, bodies, params) => {
204
if (bodies.block) {
205
return chunk.capture(bodies.block, context, (data, chunk) => {
206
// Pseudo-code: convert markdown to HTML
207
const html = convertMarkdownToHtml(data);
208
return chunk.write(html);
209
});
210
}
211
return chunk;
212
};
213
214
// Asynchronous helper
215
dust.helpers.asyncData = (chunk, context, bodies, params) => {
216
const url = params.url;
217
218
return chunk.map((chunk) => {
219
// Async operation (pseudo-code)
220
fetchData(url, (err, data) => {
221
if (err) {
222
chunk.setError(err);
223
} else {
224
const dataContext = context.push({ asyncData: data });
225
if (bodies.block) {
226
chunk.render(bodies.block, dataContext);
227
}
228
}
229
chunk.end();
230
});
231
232
return chunk;
233
});
234
};
235
```
236
237
## Common Helper Patterns
238
239
### Conditional Helpers
240
241
Patterns for implementing conditional logic in templates.
242
243
```javascript
244
// Simple existence check helper
245
dust.helpers.ifExists = (chunk, context, bodies, params) => {
246
const value = context.get(params.key);
247
248
if (value !== undefined && value !== null && value !== '') {
249
if (bodies.block) {
250
return chunk.render(bodies.block, context);
251
}
252
} else {
253
if (bodies.else) {
254
return chunk.render(bodies.else, context);
255
}
256
}
257
258
return chunk;
259
};
260
261
// Multi-condition helper
262
dust.helpers.when = (chunk, context, bodies, params) => {
263
const conditions = params.conditions || [];
264
265
for (const condition of conditions) {
266
if (evaluateCondition(condition, context)) {
267
if (bodies[condition.body]) {
268
return chunk.render(bodies[condition.body], context);
269
}
270
}
271
}
272
273
if (bodies.else) {
274
return chunk.render(bodies.else, context);
275
}
276
277
return chunk;
278
};
279
280
// Usage in templates:
281
// {@ifExists key="user.email"}Has email{:else}No email{/ifExists}
282
```
283
284
### Data Manipulation Helpers
285
286
Helpers for transforming and processing data within templates.
287
288
```javascript
289
// Array manipulation helper
290
dust.helpers.slice = (chunk, context, bodies, params) => {
291
const array = context.get(params.array) || [];
292
const start = parseInt(params.start) || 0;
293
const end = params.end ? parseInt(params.end) : array.length;
294
295
const sliced = array.slice(start, end);
296
const sliceContext = context.push({ items: sliced });
297
298
if (bodies.block) {
299
return chunk.render(bodies.block, sliceContext);
300
}
301
302
return chunk;
303
};
304
305
// Object manipulation helper
306
dust.helpers.pick = (chunk, context, bodies, params) => {
307
const obj = context.get(params.from) || {};
308
const keys = params.keys ? params.keys.split(',') : [];
309
310
const picked = {};
311
keys.forEach(key => {
312
if (obj.hasOwnProperty(key.trim())) {
313
picked[key.trim()] = obj[key.trim()];
314
}
315
});
316
317
const pickedContext = context.push(picked);
318
319
if (bodies.block) {
320
return chunk.render(bodies.block, pickedContext);
321
}
322
323
return chunk;
324
};
325
326
// String manipulation helper
327
dust.helpers.truncate = (chunk, context, bodies, params) => {
328
const text = params.text || '';
329
const length = parseInt(params.length) || 50;
330
const suffix = params.suffix || '...';
331
332
const truncated = text.length > length
333
? text.substring(0, length) + suffix
334
: text;
335
336
return chunk.write(truncated);
337
};
338
```
339
340
### Formatting Helpers
341
342
Helpers for formatting data output in templates.
343
344
```javascript
345
// Date formatting helper
346
dust.helpers.formatDate = (chunk, context, bodies, params) => {
347
const date = new Date(params.date);
348
const format = params.format || 'YYYY-MM-DD';
349
350
if (isNaN(date.getTime())) {
351
return chunk.write('Invalid Date');
352
}
353
354
// Pseudo-code: format date according to format string
355
const formatted = formatDate(date, format);
356
return chunk.write(formatted);
357
};
358
359
// Number formatting helper
360
dust.helpers.formatNumber = (chunk, context, bodies, params) => {
361
const number = parseFloat(params.number);
362
const decimals = parseInt(params.decimals) || 2;
363
const locale = params.locale || 'en-US';
364
365
if (isNaN(number)) {
366
return chunk.write('Invalid Number');
367
}
368
369
const formatted = number.toLocaleString(locale, {
370
minimumFractionDigits: decimals,
371
maximumFractionDigits: decimals
372
});
373
374
return chunk.write(formatted);
375
};
376
377
// Currency formatting helper
378
dust.helpers.formatCurrency = (chunk, context, bodies, params) => {
379
const amount = parseFloat(params.amount);
380
const currency = params.currency || 'USD';
381
const locale = params.locale || 'en-US';
382
383
if (isNaN(amount)) {
384
return chunk.write('Invalid Amount');
385
}
386
387
const formatted = new Intl.NumberFormat(locale, {
388
style: 'currency',
389
currency: currency
390
}).format(amount);
391
392
return chunk.write(formatted);
393
};
394
```
395
396
## Advanced Helper Features
397
398
### Helper with Named Bodies
399
400
Helpers can define multiple named bodies for complex template structures.
401
402
```javascript
403
dust.helpers.switch = (chunk, context, bodies, params) => {
404
const value = context.get(params.value);
405
const cases = params.cases || {};
406
407
// Look for matching case body
408
if (cases[value] && bodies[cases[value]]) {
409
return chunk.render(bodies[cases[value]], context);
410
}
411
412
// Fall back to default
413
if (bodies.default) {
414
return chunk.render(bodies.default, context);
415
}
416
417
return chunk;
418
};
419
420
// Usage in template:
421
// {@switch value=status cases.active="activeBody" cases.inactive="inactiveBody"}
422
// {<activeBody}Status is active{/activeBody}
423
// {<inactiveBody}Status is inactive{/inactiveBody}
424
// {<default}Unknown status{/default}
425
// {/switch}
426
```
427
428
### Error Handling in Helpers
429
430
Best practices for handling errors within helper functions.
431
432
```javascript
433
dust.helpers.safeHelper = (chunk, context, bodies, params) => {
434
try {
435
// Potentially error-prone operation
436
const result = riskyOperation(params.input);
437
return chunk.write(result);
438
} catch (err) {
439
// Log error for debugging
440
dust.log('Helper error: ' + err.message, 'ERROR');
441
442
// Render fallback content
443
if (bodies.error) {
444
return chunk.render(bodies.error, context.push({ error: err.message }));
445
}
446
447
// Or set error on chunk
448
return chunk.setError(err);
449
}
450
};
451
452
// Helper that validates parameters
453
dust.helpers.requireParams = (chunk, context, bodies, params) => {
454
const required = ['name', 'email'];
455
const missing = required.filter(param => !params[param]);
456
457
if (missing.length > 0) {
458
const error = new Error(`Missing required parameters: ${missing.join(', ')}`);
459
return chunk.setError(error);
460
}
461
462
if (bodies.block) {
463
return chunk.render(bodies.block, context);
464
}
465
466
return chunk;
467
};
468
```
469
470
### Helper Testing
471
472
Patterns for testing custom helpers in isolation.
473
474
```javascript
475
// Test helper function
476
function testHelper() {
477
const mockChunk = {
478
output: '',
479
write(data) { this.output += data; return this; },
480
render(body, ctx) { /* mock implementation */ return this; },
481
end() { return this; }
482
};
483
484
const mockContext = dust.context({ test: 'data' });
485
const mockBodies = { block: () => 'rendered content' };
486
const mockParams = { param1: 'value1' };
487
488
// Test helper
489
const result = dust.helpers.myHelper(mockChunk, mockContext, mockBodies, mockParams);
490
491
console.log('Helper output:', mockChunk.output);
492
return result;
493
}
494
```
495
496
## Helper Best Practices
497
498
### Performance Considerations
499
500
- Cache expensive computations within helpers
501
- Avoid creating new objects/functions in helper execution
502
- Use chunk operations efficiently
503
504
### Security Considerations
505
506
- Always sanitize user input in helpers
507
- Validate parameters before processing
508
- Be cautious with dynamic code execution
509
510
### Maintainability
511
512
- Keep helpers focused on single responsibilities
513
- Document helper parameters and usage
514
- Provide error handling and fallbacks
515
516
```javascript
517
// Well-structured helper example
518
dust.helpers.bestPracticeHelper = (chunk, context, bodies, params) => {
519
// 1. Validate required parameters
520
if (!params.required) {
521
return chunk.setError(new Error('Missing required parameter'));
522
}
523
524
// 2. Sanitize inputs
525
const sanitized = sanitizeInput(params.input);
526
527
// 3. Perform operation with error handling
528
try {
529
const result = processData(sanitized);
530
531
// 4. Use appropriate chunk operations
532
if (bodies.block) {
533
const resultContext = context.push({ result });
534
return chunk.render(bodies.block, resultContext);
535
} else {
536
return chunk.write(result);
537
}
538
} catch (err) {
539
// 5. Handle errors gracefully
540
dust.log(`Helper error: ${err.message}`, 'ERROR');
541
return bodies.error
542
? chunk.render(bodies.error, context)
543
: chunk.write('Error processing data');
544
}
545
};
546
```