/* eslint-env browser */ 'use strict'; /** @module automat */ var ENCODERS = /%\{(\d+)\}/g; // double $$ to encode var REPLACERS = /\$\{(.*?)\}/g; // single $ to replace /** * @summary String formatter. * * @desc String substitution is performed on numbered _replacer_ patterns like `${n}` or _encoder_ patterns like `%{n}` where n is the zero-based `arguments` index. So `${0}` would be replaced with the first argument following `text`. * * Encoders are just like replacers except the argument is HTML-encoded before being used. * * To change the format patterns, assign new `RegExp` patterns to `automat.encoders` and `automat.replacers`. * * @param {string|function} template - A template to be formatted as described above. Overloads: * * A string primitive containing the template. * * A function to be called with `this` as the calling context. The template is the value returned from this call. * * @param {...*} [replacements] - Replacement values for numbered format patterns. * * @return {string} The formatted text. * * @memberOf module:automat */ function automat(template, replacements/*...*/) { var hasReplacements = arguments.length > 1; // if `template` is a function, convert it to text if (typeof template === 'function') { template = template.call(this); // non-template function: call it with context and use return value } if (hasReplacements) { var args = arguments; template = template.replace(automat.replacersRegex, function(match, key) { key -= -1; // convert to number and increment return args.length > key ? args[key] : ''; }); template = template.replace(automat.encodersRegex, function(match, key) { key -= -1; // convert to number and increment if (args.length > key) { var htmlEncoderNode = document.createElement('DIV'); htmlEncoderNode.textContent = args[key]; return htmlEncoderNode.innerHTML; } else { return ''; } }); } return template; } /** * @summary Replace contents of `el` with `Nodes` generated from formatted template. * * @param {string|function} template - See `template` parameter of {@link automat}. * * @param {HTMLElement} [el] - Node in which to return markup generated from template. If omitted, a new `
...
` element will be created and returned. * * @param {...*} [replacements] - Replacement values for numbered format patterns. * * @return {HTMLElement} The `el` provided or a new `
...
` element, its `innerHTML` set to the formatted text. * * @memberOf module:automat */ function replace(template, el, replacements/*...*/) { var elOmitted = typeof el !== 'object', args = Array.prototype.slice.call(arguments, 1); if (elOmitted) { el = document.createElement('DIV'); args.unshift(template); } else { args[0] = template; } el.innerHTML = automat.apply(null, args); return el; } /** * @summary Append or insert `Node`s generated from formatted template into given `el`. * * @param {string|function} template - See `template` parameter of {@link automat}. * * @param {HTMLElement} el * * @param {Node} [referenceNode=null] Inserts before this element within `el` or at end of `el` if `null`. * * @param {...*} [replacements] - Replacement values for numbered format patterns. * * @returns {Node[]} Array of the generated nodes (this is an actual Array instance; not an Array-like object). * * @memberOf module:automat */ function append(template, el, referenceNode, replacements/*...*/) { var replacementsStartAt = 3, referenceNodeOmitted = typeof referenceNode !== 'object'; // replacements are never objects if (referenceNodeOmitted) { referenceNode = null; replacementsStartAt = 2; } replacements = Array.prototype.slice.call(arguments, replacementsStartAt); var result = [], div = replace.apply(null, [template].concat(replacements)); while (div.childNodes.length) { result.push(div.firstChild); el.insertBefore(div.firstChild, referenceNode); // removes child from div } return result; } /** * Use this convenience wrapper to return the first child node described in `template`. * * @param {string|function} template - If a function, extract template from comment within. * * @returns {HTMLElement} The first `Node` in your template. * * @memberOf module:automat */ function firstChild(template, replacements/*...*/) { return replace.apply(null, arguments).firstChild; } /** * Use this convenience wrapper to return the first child element described in `template`. * * @param {string|function} template - If a function, extract template from comment within. * * @returns {HTMLElement} The first `HTMLElement` in your template. * * @memberOf module:automat */ function firstElement(template, replacements/*...*/) { return replace.apply(null, arguments).firstElementChild; } /** * @summary Finds string substitution lexemes that require HTML encoding. * @desc Modify to suit. * @default %{n} * @type {RegExp} * @memberOf module:automat */ automat.encodersRegex = ENCODERS; /** * @summary Finds string substitution lexemes. * @desc Modify to suit. * @default ${n} * @type {RegExp} * @memberOf module:automat */ automat.replacersRegex = REPLACERS; automat.format = automat; // if you find using just `automat()` confusing automat.replace = replace; automat.append = append; automat.firstChild = firstChild; automat.firstElement = firstElement; module.exports = automat;