micromustache
Version:
A fast, minimal and secure template engine for JavaScript
111 lines (100 loc) • 3.46 kB
JavaScript
/*global micromustache,examples*/
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-return */
const id = (id) => document.getElementById(id)
const createEl = (tagName) => document.createElement(tagName)
const text = (el, contents) => (el.innerText = contents)
const getVal = (el) => {
switch (el.type) {
case 'checkbox':
return el.checked
case 'number':
return el.valueAsNumber
default:
return el.value
}
}
const setVal = (el, value) => (el.value = value)
const on = (el, eventName, handler) => el.addEventListener(eventName, handler)
const onInput = (el, handler) => on(el, 'input', handler)
const fire = (el, eventName) => el.dispatchEvent(new Event(eventName))
const ready = (fn) =>
['complete', 'interactive'].includes(document.readyState)
? fn()
: on(document, 'DOMContentLoaded', fn)
const exampleSelector = id('example-selector')
const template = id('template')
const optionsToggle = id('options-toggle')
const options = id('options')
const templateError = id('template-error')
const scope = id('scope')
const scopeError = id('scope-error')
const result = id('result')
const resultError = id('result-error')
// Runs a function showing its results or errors in appropriate DOM elements
function runFn(successEl, errorEl, fn) {
if (typeof fn !== 'function') {
throw new TypeError(`Expected a function. Got ${fn}`)
}
try {
const result = fn()
successEl.classList.remove('error')
text(errorEl, '')
return result
} catch (err) {
successEl.classList.add('error')
text(errorEl, '⛔ ' + err)
}
}
function render() {
console.log('Render', getVal(id('validateVarNames')))
// Handle the template errors
const renderer = runFn(template, templateError, () =>
micromustache.compile(getVal(template), {
depth: getVal(id('depth')),
explicit: getVal(id('explicit')),
maxVarNameLength: getVal(id('maxVarNameLength')),
propsExist: getVal(id('propsExist')),
tags: [getVal(id('tag0')), getVal(id('tag1'))],
validateVarNames: getVal(id('validateVarNames')),
})
)
// Handle the scope errors
const scopeObj = runFn(scope, scopeError, () => JSON.parse(getVal(scope)))
if (!renderer || !scopeObj) {
return text(result, '')
}
// If all is well try to generate the results handling the errors
text(
result,
runFn(result, resultError, () => renderer.render(scopeObj))
)
}
ready(() => {
examples.forEach((example, i) => {
const option = createEl('option')
text(option, example.name)
setVal(option, i)
exampleSelector.appendChild(option)
})
onInput(optionsToggle, () => (options.hidden = !optionsToggle.checked))
onInput(scope, render)
onInput(template, render)
onInput(id('depth'), render)
onInput(id('explicit'), render)
onInput(id('maxVarNameLength'), render)
onInput(id('propsExist'), render)
onInput(id('tag0'), render)
onInput(id('tag1'), render)
onInput(id('validateVarNames'), render)
onInput(exampleSelector, () => {
const example = examples[getVal(exampleSelector)]
setVal(template, example.template)
setVal(scope, JSON.stringify(example.scope, null, 2))
render()
})
fire(exampleSelector, 'input')
})