@mscalora/overtemplate

2.1.3 • Public • Published

OverTemplate

What is it?

A fairly simple templating system that combines the escaped and interpolated parts of Lodash/Underscore templates with simple conditional and looping over an array while conforming to Content Security Policy (CSP).

Based on Silvermine UnderTemplate which is a simple replacement for _.template from either [Underscore][utmpl] or [Lodash][ltmpl] that removes the features that make those libraries incompatible with Content Security Policy (CSP).

History

  • 1.0 2021-10-11 initial release of OverTemplate
    • loop and conditional structures
  • 2.0 2021-11-03 major upgrade
    • custom expression evaluation
    • custom formatters
  • 2.1 2021-11-05 minor upgrade
    • filters
    • documentation
    • reorganized tests

NOTE: Version 2.0.0 introduced a breaking change in the syntax of the loop construct, the parameter separator in the for tag will be a semicolon by default instead of a comma. A new option parameterSeparator has been added to allow for separators like comma or other characters that may function better with custom expression evaluators. Passing a comma character for the parameterSeparator setting will effectively revert the syntax to be 1.x compatible.

How do I use it?

Similar to templates in Underscore's _.template:

let overtemplate = require('@mscalora/overtemplate'),
    compiled = overtemplate('Hello <%= name %>');

console.log(compiled({ name: 'John Smith' }));

Documentation

Demo Page

Default Syntax

Note: syntax of tags is customizable, default syntax shown below

HTML Escaped Interpolated Output

Syntax: <%- expression [ | filter ... ] %>

Raw Interpolated Output

Syntax: <%= expression [ | filter ... ] %>

Conditional Structure if-else-end

Syntax: <% if ( expression ) %>

Syntax: <% else %>

Syntax: <% end %>

Loops for-end structure

Syntax: <% for ( expression ; alias ) %>

Syntax: <% end %>

The portion of the template between the for tag and the end tag is rendered once for each item on the collection represented by the expression value. The expression value is expected to be 'array like'. The current item from the collection is set using the alias parameter as the property name. In addition, the following properties are set in the current data model:

  • alias_index - zero based index of the current item
  • alias_count - one based index of the current item
  • alias_first - boolean is the first item of the array
  • alias_last - boolean is the last item of the array
  • alias_length - number of items in the array (same as .length on array)

Note: _first and _last will both be true for one item arrays. For example, to emit a comma between all items, use the else clause of a conditional:

const overtemplate = require('@mscalora/overtemplate');
  template = '<% for (letters;item)%><%- item %><% if (item_last) %><% else %>, <% end %><% end %>',
  compiled = overtemplate(template);

console.log(compiled({letters: Array.from('abc')}));

> a, b, c

Alternate Syntax [Reduced Conflict]

The alternate syntax is provided for use when separator characters used in the default syntax might cause conflicts with template data.

HTML Escaped Interpolated Output

Syntax: «- expression [ ~ filter ... ] »

Raw Interpolated Output

Syntax: «= expression [ ~ filter ... ] »

Conditional Structure if-else-end

Syntax: « if ( expression ) »

Syntax: « else »

Syntax: « end »

Loops for-end structure

Syntax: « for ( expression • alias ) »

Syntax: « end »

See the loop documentation of the default syntax section documents for usage information.

Note: The default parameter separator character for the alternate syntax is the bullet character (Mac keystroke: option-8) and the filter separator is a tilde character. The tag beginning and end characters are accessed from a typical US-EN keyboard layout with option-\ and option-shift-\.

Replaceable Formatter Callbacks

By default, expressions resulting in Date objects are converted to a string using the .toLocaleString() method and 'Numbers' are converted to a string using template literals ${n}. You can replace either or both of these 'formatters' in the user settings like:

function two_digit_fixed (value, data, settings, expression) {
  return (Math.round(value * 100) / 100).toFixed(2);
}

const overtemplate = require('@mscalora/overtemplate');
  template = '<%- num %>',
  compiled = overtemplate(template, {numberFormatter: two_digit_fixed});

console.log(compiled({num: 2/3}));

Formatter functions receive four parameters:

  • value - value resolved from expression and data; date formatters should always receive a Date object, number formatters should always receive a value where _.isNumber returns true
  • data - current data object (e.g. with loop values in scope)
  • settings - current settings (see settings for callbacks section)
  • expression - raw expression string

Formatter functions are expected to return a string value

Clients can provide a customFormatter callback which is used for ALL expression values. If a customFormatter is passed in user settings numberFormatter and dateFormatter are not called directly but can be used in your customFormatter implementation.

Settings for callbacks:

  • defaultDateFormatter - built-in, default data formatter
  • defaultNumberFormatter - built-in, default number formatter
  • defaultFormatter - built-in, default formatter for all expressions

Note: The built-in defaultFormatter will call the dateFormatter and numberFormatter in the current settings object so custom implementations of these formatters should not use this function or infinite recursion may occur. It can however be safely used by customFormatter implementations.

Example Custom Formatter:

function pwObfuscator (v, d, s, e) {
  if (/(\W|^)(password|passwd)(\W|$)/i.test(e)) {
    return `${v}`.replace(/./g, '*');
  }
  return s.defaultFormatter(v, d, s, e);
}

Example Date Formatter:

function simple_date (v, d, s, e) {
  if (v.getFullYear() < 1970) {
    return '-invalid-';
  }
  return `${v.getFullYear()}-${v.getMonth()}-${v.getDate()}`;
}

Example Number Formatter:

function price_formatter (v, d, s, e) {
  if (/price/.exec(e) && v.toFixed) {
    return `$$${v.toFixed(2)}`;
  }
  return s.defaultFormatter(v, d, s, e);
}

Custom Expression Evaluator

Custom expression evaluators are functions with the signature:

function (expression, data, [defaultValue], [settings])

Example Expression Evaluator using expr-eval

const exprEval = require('expr-eval');

function customEval (data, exp, _, __) {
   return (new exprEval.Parser()).evaluate(exp, data);
}

const overtemplate = require('@mscalora/overtemplate'),
  template = '<%- 17^3 + v[2].x %>',
  compiled = overtemplate(template, {expressionEvaluator: customEval});

console.log(compiled({v:[1,2,{x:11.7}]}));

Example Expression Evaluator using expr-eval with a custom function

The expr-eval module supports custom javascript extension functions attached to the functions object of the Parser class in addition to overriding the module provided functions and operators.

const exprEval = require('expr-eval'),
  exprParser = new exprEval.Parser();

// returns a dynamicly created array of counting numbers
exprParser.functions.counting = function (len) {
  return Array.from({length:len},(_,i)=>i+1);
}

const overtemplate = require('@mscalora/overtemplate'),
  template = '<% for (counting(n);c) %><%- c %>\n<% end %>',
  customEval = (data, exp, _, __) => exprParser.evaluate(exp, data),
  compiled = overtemplate(template, {expressionEvaluator: customEval});

console.log(compiled({n:5}));

Output Filters

Filters are a way to process interpolation tag expression values by specifying filter names in interpolation and escape tags.

Example of converting a string value to uppercase
<%- author.name | upperCase %>

Filters vs Formatters

Formatters are oriented towards global processing by data type. Since they have visibility to the raw expression text, you can create formatters to handle serialization (converting to a string) by field name, see the pwObfuscator example above.

Filters are specified in output tags in the template so more oriented to use of a value in a template.

A Couple Notes

Templating in Underscore/Lodash was operating by building up a JS function as a string. This meant that if your template referred to a variable that did not exist, you would get a JS error thrown. In this library, however, undefined variables in the template will result in an empty string being placed in that location.

By default, expression evaluation is limited to "paths" supported by the lodash get method and literal values for strings and numbers. The module is tested with custom expression evaluation using the expr-eval module. This module provides rich algebraic expressions with numbers, strings and arrays with many built-in operators and functions well beyond what's available in javascript. Expression syntax and semantics are similar to javascript but incompatible in several ways. For example, the addition operator only supports numeric values, string concatenation requires the use of a different operator. Visit the modules github page for more info: https://github.com/silentmatt/expr-eval

We rely on Lodash to provide a number of convenience functions that would require polyfills to support a wide array of browsers. To help reduce bloat, we do two things:

  1. Rely on a fairly loose version of Lodash: 4.x, meaning that if you already have it as a dependency, we'll use your version.
  2. Only require('lodash/foo') for each foo function we need. This helps if you're using UnderTemplate in a browserify-style environment since only the files from Lodash that are actually needed will be included in your bundle.

How do I contribute?

Pull requests with tests and documentation updates as needed

License

This software is released under the MIT license. See the license file for more details.

Copyrighted by Mike Scalora and original Silvermine UnderTemplate code was copyrited by Jeremy Thomerson 2018 [utmpl]: http://underscorejs.org/#template [ltmpl]: https://lodash.com/docs/#template

Package Sidebar

Install

npm i @mscalora/overtemplate

Weekly Downloads

1

Version

2.1.3

License

MIT

Unpacked Size

242 kB

Total Files

35

Last publish

Collaborators

  • mscalora