CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-dustjs-linkedin

Asynchronous templates for the browser and server (LinkedIn fork)

Overview
Eval results
Files

helpers.mddocs/

Helper System

Extensible helper system for implementing complex template logic and custom functionality through user-defined helper functions.

Capabilities

Helper Registration

System for registering and managing custom helper functions that extend template capabilities.

/**
 * Container for all registered helpers
 * Empty by default - all helpers must be added by users
 */
const helpers: { [helperName: string]: HelperFunction };

/**
 * Helper function signature
 * @param chunk - Output chunk for writing content
 * @param context - Template context for data access
 * @param bodies - Template bodies (block, else, etc.)
 * @param params - Parameters passed to helper
 * @returns Modified chunk
 */
type HelperFunction = (
  chunk: Chunk,
  context: Context,
  bodies: Bodies,
  params: Params
) => Chunk;

interface Bodies {
  /** Main template body */
  block?: TemplateFunction;
  /** Else template body */
  else?: TemplateFunction;
  /** Named bodies for custom helpers */
  [bodyName: string]: TemplateFunction | undefined;
}

interface Params {
  /** Parameters passed to helper as key-value pairs */
  [paramName: string]: any;
}

Usage Examples:

const dust = require('dustjs-linkedin');

// Register a simple helper
dust.helpers.uppercase = (chunk, context, bodies, params) => {
  if (bodies.block) {
    return chunk.capture(bodies.block, context, (data, chunk) => {
      return chunk.write(data.toUpperCase());
    });
  }
  return chunk;
};

// Register a helper with parameters
dust.helpers.repeat = (chunk, context, bodies, params) => {
  const times = parseInt(params.times) || 1;

  if (bodies.block) {
    for (let i = 0; i < times; i++) {
      chunk.render(bodies.block, context.push({ index: i }));
    }
  }

  return chunk;
};

// Register a conditional helper
dust.helpers.compare = (chunk, context, bodies, params) => {
  const left = params.left;
  const right = params.right;
  const operator = params.operator || 'eq';

  let result = false;
  switch (operator) {
    case 'eq': result = left == right; break;
    case 'ne': result = left != right; break;
    case 'gt': result = left > right; break;
    case 'lt': result = left < right; break;
    case 'gte': result = left >= right; break;
    case 'lte': result = left <= right; break;
  }

  if (result && bodies.block) {
    return chunk.render(bodies.block, context);
  } else if (!result && bodies.else) {
    return chunk.render(bodies.else, context);
  }

  return chunk;
};

// Usage in templates:
// {@uppercase}hello world{/uppercase} → HELLO WORLD
// {@repeat times="3"}Item {index}{/repeat} → Item 0Item 1Item 2
// {@compare left=age right=18 operator="gte"}Adult{:else}Minor{/compare}

Helper Context Access

Helpers have full access to the template context and can manipulate data resolution.

interface Context {
  /** Get data from context */
  get(path: string): any;
  /** Get current context data */
  current(): any;
  /** Push new context data */
  push(data: any): Context;
  /** Template name for debugging */
  getTemplateName(): string | undefined;
}

Usage Examples:

// Helper that accesses context data
dust.helpers.userInfo = (chunk, context, bodies, params) => {
  const user = context.get('user');
  const currentData = context.current();

  if (user) {
    const info = `User: ${user.name} (${user.role})`;
    return chunk.write(info);
  }

  return chunk.write('No user information');
};

// Helper that modifies context
dust.helpers.withDefaults = (chunk, context, bodies, params) => {
  const defaults = params.defaults || {};
  const enhanced = context.push(defaults);

  if (bodies.block) {
    return chunk.render(bodies.block, enhanced);
  }

  return chunk;
};

// Helper that creates isolated context
dust.helpers.isolate = (chunk, context, bodies, params) => {
  const isolatedData = params.data || {};
  const isolatedContext = dust.context(isolatedData);

  if (bodies.block) {
    return chunk.render(bodies.block, isolatedContext);
  }

  return chunk;
};

Chunk Manipulation

Helpers work with chunks to control template output and handle asynchronous operations.

interface Chunk {
  /** Write string content to output */
  write(data: string): Chunk;
  /** End chunk with optional final content */
  end(data?: string): Chunk;
  /** Render template body with context */
  render(body: TemplateFunction, context: Context): Chunk;
  /** Capture rendered content for processing */
  capture(body: TemplateFunction, context: Context, callback: (data: string, chunk: Chunk) => Chunk): Chunk;
  /** Create asynchronous chunk for async operations */
  map(callback: (chunk: Chunk) => Chunk): Chunk;
  /** Set error on chunk */
  setError(error: Error): Chunk;
}

Usage Examples:

// Helper that manipulates output
dust.helpers.wrap = (chunk, context, bodies, params) => {
  const tag = params.tag || 'div';
  const className = params.class || '';

  chunk.write(`<${tag}${className ? ` class="${className}"` : ''}>`);

  if (bodies.block) {
    chunk.render(bodies.block, context);
  }

  return chunk.write(`</${tag}>`);
};

// Helper that processes content
dust.helpers.markdown = (chunk, context, bodies, params) => {
  if (bodies.block) {
    return chunk.capture(bodies.block, context, (data, chunk) => {
      // Pseudo-code: convert markdown to HTML
      const html = convertMarkdownToHtml(data);
      return chunk.write(html);
    });
  }
  return chunk;
};

// Asynchronous helper
dust.helpers.asyncData = (chunk, context, bodies, params) => {
  const url = params.url;

  return chunk.map((chunk) => {
    // Async operation (pseudo-code)
    fetchData(url, (err, data) => {
      if (err) {
        chunk.setError(err);
      } else {
        const dataContext = context.push({ asyncData: data });
        if (bodies.block) {
          chunk.render(bodies.block, dataContext);
        }
      }
      chunk.end();
    });

    return chunk;
  });
};

Common Helper Patterns

Conditional Helpers

Patterns for implementing conditional logic in templates.

// Simple existence check helper
dust.helpers.ifExists = (chunk, context, bodies, params) => {
  const value = context.get(params.key);

  if (value !== undefined && value !== null && value !== '') {
    if (bodies.block) {
      return chunk.render(bodies.block, context);
    }
  } else {
    if (bodies.else) {
      return chunk.render(bodies.else, context);
    }
  }

  return chunk;
};

// Multi-condition helper
dust.helpers.when = (chunk, context, bodies, params) => {
  const conditions = params.conditions || [];

  for (const condition of conditions) {
    if (evaluateCondition(condition, context)) {
      if (bodies[condition.body]) {
        return chunk.render(bodies[condition.body], context);
      }
    }
  }

  if (bodies.else) {
    return chunk.render(bodies.else, context);
  }

  return chunk;
};

// Usage in templates:
// {@ifExists key="user.email"}Has email{:else}No email{/ifExists}

Data Manipulation Helpers

Helpers for transforming and processing data within templates.

// Array manipulation helper
dust.helpers.slice = (chunk, context, bodies, params) => {
  const array = context.get(params.array) || [];
  const start = parseInt(params.start) || 0;
  const end = params.end ? parseInt(params.end) : array.length;

  const sliced = array.slice(start, end);
  const sliceContext = context.push({ items: sliced });

  if (bodies.block) {
    return chunk.render(bodies.block, sliceContext);
  }

  return chunk;
};

// Object manipulation helper
dust.helpers.pick = (chunk, context, bodies, params) => {
  const obj = context.get(params.from) || {};
  const keys = params.keys ? params.keys.split(',') : [];

  const picked = {};
  keys.forEach(key => {
    if (obj.hasOwnProperty(key.trim())) {
      picked[key.trim()] = obj[key.trim()];
    }
  });

  const pickedContext = context.push(picked);

  if (bodies.block) {
    return chunk.render(bodies.block, pickedContext);
  }

  return chunk;
};

// String manipulation helper
dust.helpers.truncate = (chunk, context, bodies, params) => {
  const text = params.text || '';
  const length = parseInt(params.length) || 50;
  const suffix = params.suffix || '...';

  const truncated = text.length > length
    ? text.substring(0, length) + suffix
    : text;

  return chunk.write(truncated);
};

Formatting Helpers

Helpers for formatting data output in templates.

// Date formatting helper
dust.helpers.formatDate = (chunk, context, bodies, params) => {
  const date = new Date(params.date);
  const format = params.format || 'YYYY-MM-DD';

  if (isNaN(date.getTime())) {
    return chunk.write('Invalid Date');
  }

  // Pseudo-code: format date according to format string
  const formatted = formatDate(date, format);
  return chunk.write(formatted);
};

// Number formatting helper
dust.helpers.formatNumber = (chunk, context, bodies, params) => {
  const number = parseFloat(params.number);
  const decimals = parseInt(params.decimals) || 2;
  const locale = params.locale || 'en-US';

  if (isNaN(number)) {
    return chunk.write('Invalid Number');
  }

  const formatted = number.toLocaleString(locale, {
    minimumFractionDigits: decimals,
    maximumFractionDigits: decimals
  });

  return chunk.write(formatted);
};

// Currency formatting helper
dust.helpers.formatCurrency = (chunk, context, bodies, params) => {
  const amount = parseFloat(params.amount);
  const currency = params.currency || 'USD';
  const locale = params.locale || 'en-US';

  if (isNaN(amount)) {
    return chunk.write('Invalid Amount');
  }

  const formatted = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: currency
  }).format(amount);

  return chunk.write(formatted);
};

Advanced Helper Features

Helper with Named Bodies

Helpers can define multiple named bodies for complex template structures.

dust.helpers.switch = (chunk, context, bodies, params) => {
  const value = context.get(params.value);
  const cases = params.cases || {};

  // Look for matching case body
  if (cases[value] && bodies[cases[value]]) {
    return chunk.render(bodies[cases[value]], context);
  }

  // Fall back to default
  if (bodies.default) {
    return chunk.render(bodies.default, context);
  }

  return chunk;
};

// Usage in template:
// {@switch value=status cases.active="activeBody" cases.inactive="inactiveBody"}
//   {<activeBody}Status is active{/activeBody}
//   {<inactiveBody}Status is inactive{/inactiveBody}
//   {<default}Unknown status{/default}
// {/switch}

Error Handling in Helpers

Best practices for handling errors within helper functions.

dust.helpers.safeHelper = (chunk, context, bodies, params) => {
  try {
    // Potentially error-prone operation
    const result = riskyOperation(params.input);
    return chunk.write(result);
  } catch (err) {
    // Log error for debugging
    dust.log('Helper error: ' + err.message, 'ERROR');

    // Render fallback content
    if (bodies.error) {
      return chunk.render(bodies.error, context.push({ error: err.message }));
    }

    // Or set error on chunk
    return chunk.setError(err);
  }
};

// Helper that validates parameters
dust.helpers.requireParams = (chunk, context, bodies, params) => {
  const required = ['name', 'email'];
  const missing = required.filter(param => !params[param]);

  if (missing.length > 0) {
    const error = new Error(`Missing required parameters: ${missing.join(', ')}`);
    return chunk.setError(error);
  }

  if (bodies.block) {
    return chunk.render(bodies.block, context);
  }

  return chunk;
};

Helper Testing

Patterns for testing custom helpers in isolation.

// Test helper function
function testHelper() {
  const mockChunk = {
    output: '',
    write(data) { this.output += data; return this; },
    render(body, ctx) { /* mock implementation */ return this; },
    end() { return this; }
  };

  const mockContext = dust.context({ test: 'data' });
  const mockBodies = { block: () => 'rendered content' };
  const mockParams = { param1: 'value1' };

  // Test helper
  const result = dust.helpers.myHelper(mockChunk, mockContext, mockBodies, mockParams);

  console.log('Helper output:', mockChunk.output);
  return result;
}

Helper Best Practices

Performance Considerations

  • Cache expensive computations within helpers
  • Avoid creating new objects/functions in helper execution
  • Use chunk operations efficiently

Security Considerations

  • Always sanitize user input in helpers
  • Validate parameters before processing
  • Be cautious with dynamic code execution

Maintainability

  • Keep helpers focused on single responsibilities
  • Document helper parameters and usage
  • Provide error handling and fallbacks
// Well-structured helper example
dust.helpers.bestPracticeHelper = (chunk, context, bodies, params) => {
  // 1. Validate required parameters
  if (!params.required) {
    return chunk.setError(new Error('Missing required parameter'));
  }

  // 2. Sanitize inputs
  const sanitized = sanitizeInput(params.input);

  // 3. Perform operation with error handling
  try {
    const result = processData(sanitized);

    // 4. Use appropriate chunk operations
    if (bodies.block) {
      const resultContext = context.push({ result });
      return chunk.render(bodies.block, resultContext);
    } else {
      return chunk.write(result);
    }
  } catch (err) {
    // 5. Handle errors gracefully
    dust.log(`Helper error: ${err.message}`, 'ERROR');
    return bodies.error
      ? chunk.render(bodies.error, context)
      : chunk.write('Error processing data');
  }
};

Install with Tessl CLI

npx tessl i tessl/npm-dustjs-linkedin

docs

cli.md

compilation.md

context.md

filters.md

helpers.md

index.md

parsing.md

rendering.md

tile.json