Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
RJyYwB
<template id="oi-template">
<table class="oi-card0">
<tr>
<th colspan="2">Month</th>
</tr>
<tr>
<td>Deckungsumme</td>
<td type="{{deckungsumme.type}}">{{deckungsumme.value}}</td>
</tr>
<tr>
<td>Outdoor coverage</td>
<td type="{{outdoorCoverage.type}}">{{outdoorCoverage.value}}</td>
</tr>
<tr>
<td>Theft</td>
<td type="{{theft.type}}"></td>
</tr>
<tr>
<td>Lighting</td>
<td type="{{lighting.type}}"></td>
</tr>
</table>
<table class="oi-card0">
<tr>
<th colspan="2">Month</th>
</tr>
<tr>
<td>Deckungsumme</td>
<td type="{{deckungsumme.type}}">{{deckungsumme.value}}</td>
</tr>
<tr>
<td>Outdoor coverage</td>
<td type="{{outdoorCoverage.type}}">{{outdoorCoverage.value}}</td>
</tr>
<tr>
<td>Theft</td>
<td type="{{theft.type}}"></td>
</tr>
<tr>
<td>Lighting</td>
<td type="{{lighting.type}}"></td>
</tr>
</table>
</template>
<div id="oi-outlet"></div>
/**
* @callback ResolverFn
* @param {string} varName - variable name before being parsed.
* For example: {a.b.c} -> 'a.b.c', { x } -> 'x'
* @param {Object} view - the view object that was passed to .render() function
* @returns {string|number|boolean|Object|undefined} the value to be
* interpolated. If the function returns undefined, the value resolution
* algorithm will go ahead with the default behaviour (resolving the
* variable name from the provided object).
*/
const VAR_MATCH_REGEX = /\{\{\s*(.*?)\s*\}\}/g;
function _valueToString (value) {
switch (typeof value) {
case 'string':
case 'number':
case 'boolean':
return value;
case 'object':
try {
// null is an object but is falsy. Swallow it.
return value === null ? '' : JSON.stringify(value);
} catch (jsonError) {
return '{...}';
}
default:
// Anything else will be replaced with an empty string
// For example: undefined, Symbol, etc.
return '';
}
}
/**
* Recursively goes through an object trying to resolve a path.
*
* @param {Object} scope - The object to traverse (in each recursive call we dig into this object)
* @param {string[]} path - An array of property names to traverse one-by-one
* @param {number} [pathIndex=0] - The current index in the path array
*/
function _recursivePathResolver(scope, path, pathIndex = 0) {
if (typeof scope !== 'object' || scope === null || scope === undefined) {
return '';
}
const varName = path[pathIndex];
const value = scope[varName];
if (pathIndex === path.length - 1) {
// It's a leaf, return whatever it is
return value;
}
return _recursivePathResolver(value, path, ++pathIndex);
}
function defaultResolver(varName, view) {
return _recursivePathResolver(view, varName.split('.'));
}
/**
* Replaces every {{variable}} inside the template with values provided by view.
*
* @param {string} template - The template containing one or more {{variableNames}} every variable
* names that is used in the template. If it's omitted, it'll be assumed an empty object.
* @param {Object} [view={}] - An object containing values for every variable names that is used in
* the template. If it's omitted, it'll be set to an empty object essentially removing all
* {{varName}}s in the template.
* @param {ResolverFn} [resolver] - An optional function that will be
* called for every {{varName}} to generate a value. If the resolver throws an error
* we'll proceed with the default value resolution algorithm (find the value from the view
* object).
* @returns {string} - Template where its variable names replaced with
* corresponding values. If a value is not found or is invalid, it will
* be assumed empty string ''. If the value is an object itself, it'll
* be stringified by JSON.
* In case of a JSON stringify error the result will look like "{...}".
*/
function render (template, view = {}, resolver = defaultResolver) {
// don't touch the template if it is not a string
if (typeof template !== 'string') {
return template;
}
return template.replace(VAR_MATCH_REGEX, function (match, varName) {
try {
// defaultResolver never throws
return _valueToString(resolver(varName, view));
} catch (e) {
// if your resolver throws, we proceed with the default resolver
return _valueToString(defaultResolver(varName, view));
}
});
}
const template = document.getElementById("oi-template").innerHTML;
const view = {
deckungsumme:{ value:'70.000€', type:'currency'},
outdoorCoverage:{ value:'15.000€', type:'currency'},
theft:{type:'checked'},
lighting:{ type:'unchecked'},
}
document.getElementById('oi-outlet').innerHTML = render(template,view);
$grey: #7d7d7d;
$blue: #7587a2;
$green: #00a699;
$line: #d2d2d6;
.oi-card0 {
border-spacing: 0 8px;
margin-bottom: 32px;
min-width: 260px;
@media screen and (min-width: 768px) {
border-spacing: 0 4px;
min-width: 300px;
}
th {
font-family: Montserrat;
font-weight: 400;
font-size: 17px;
color: $blue;
text-align: left;
padding-bottom: 4px;
border-bottom: 1px solid $line;
@media screen and (min-width: 768px) {
font-size: 20px;
}
}
td {
font-family: Montserrat;
font-weight: 400;
font-size: 14px;
color: $grey;
text-align: left;
height: 28x;
position: relative;
@media screen and (min-width: 768px) {
font-size: 18px;
height: 32px;
}
&[type="currency"] {
padding: 0;
text-align: center;
padding-left: 8px;
padding-right: 8px;
color: $green;
font-size: 14px;
border-radius: 100px;
border: 1px solid $green;
@media screen and (min-width: 768px) {
font-size: 16px;
border: 2px solid $green;
}
}
&[type="checked"],
&[type="unchecked"] {
text-align: right;
&:before {
font-family: Montserrat;
content: "";
padding: 0;
text-align: center;
color: $green;
font-size: 16px;
border-radius: 100px;
border: 1px solid $green;
height: 18px;
width: 18px;
position: absolute;
right: 0;
top: 0;
font-size: 14px;
line-height: 18px;
color: rgba($green, 0.8);
@media screen and (min-width: 768px) {
font-size: 14px;
height: 32px;
width: 32px;
line-height: 32px;
border: 2px solid $green;
}
}
}
&[type="unchecked"] {
&:before {
content: "";
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.