Delayed evaluation for string formatting - javascript

I am building up a constants.js file with URLs used in my ReactJS project. These strings include query parameters that can be used with the URL. However, the values used in these strings are only available in a component where the string is used, not in the constants file itself. So for example, I want something like this:
export const BASE_URL = 'https://example.com';
export const FOO_QUERY = '?foo=%s';
where %s is just a placeholder that can be replaced later in a component. (I borrowed printf() syntax from C here for illustration purposes.) What is the correct syntax to do something like this in JavaScript? Is it even possible?

I'd probably use a template literal in a function: The component calls the function with the foo value:
export const fooQuery = foo => `?foo=${foo}`;
Usage:
const query = fooQuery("foo value");

With tagged template literals you could do:
function query(parts, ...pos) {
return apply(...args) {
return parts.map((part, i) => part + args[ pos[i] ]).join("");
}
}
Usable as:
const find = query`?name=${0}&fullname=${0}&age=${1}`;
console.log(find("jonas", 18));

let string = 'bar';
console.log("Foo %s", string);
var teststr = (string) => `Foo ${string}`; //result: Foo bar

Related

What is the difference between `${variableName}` and {variableName}?

Are these two syntaxes the same : `${variableName}` and {variableName}
No. First one is JavaScript's template literals
According to the tags of the question I assume the context is about React js. So the second one is use to write javascript within JSX.
For example, If you want to loop through an array in JSX, You can do something like,
<div>
{arr.map(item => <p>{item}</p>)}
</div>
But template literals is feature of pure javascript.
Template literals are string literals allowing embedded expressions.
You can use multi-line strings and string interpolation features with
them.
No, these are not the same. The first one is a template literal, which is used to embed a JavaScript variable inside a string.
For Example
let name = "Rashid";
let age = 20;
console.log(`My Name is ${name} and i'm ${age} years old`);
image example
The second one is to use JavaScript variables in JSX which is mixture of JavaScript and html used in React.
For Example
import "./styles.css";
let name = "Rashid";
let age = 22;
export default function App() {
return (
<div className="App">
<h1>Hello {name}</h1>
<h2>I think you are {age} years old.</h2>
</div>
);
}
image example
CodeSandBox Link
No, they are not the same. One is a javascript template literal, ie: string interpolation and the other is an object or possibly an object decomposition. Or, as pointed out by Dilshan, a javascript expression embedded within JSX. So:
let var1 = 15;
let var2 = {prop: true};
let var3 = 'text';
console.log(`${var1} ${var2} ${var3}`);
outputs the string: 15 [object Object] text
and the following:
let var1 = {prop: true};
let {prop} = var1;
let var2 = {var1};
console.log(var1)
console.log(prop)
console.log(var2)
will produce the output:
{ prop: true }
true
{ var1: { prop: true } }
as the first three statements are equal to:
let var1 = {prop: true};
let prop = var1.prop;
let var2 = {var1: var1};
Or in a JSX file:
import ReactDOMServer from 'react-dom/server';
let var1 = "A string"
let var2 = 34
console.log(
ReactDOMServer.renderToStaticMarkup(
<ul>
<li>{var1}</li>
<li>{var2}</li>
</ul>
)
);
Which Babel will convert to:
"use strict";
var _server = _interopRequireDefault(require("react-dom/server"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var var1 = "A string";
var var2 = 34;
console.log(_server.default.renderToStaticMarkup( /*#__PURE__*/React.createElement("ul", null, /*#__PURE__*/React.createElement("li", null, var1), /*#__PURE__*/React.createElement("li", null, var2))));
And, when run, will output:
<ul>
<li>A string</li>
<li>34</li>
</ul>
Unless of course you meant to type:
`{variableName}`
In which case it's just a regular string without any interpolation. If you printed it out like so:
console.log(`{variableName}`);
Your output would be:
{variableName}
${variableName} Used when concatenating the string:
let my_name = 'abc';
console.log(`my name is ${yourname}. Bye`)
console.log('my name is ' + yourname + '. Bye')

Is it possible to use tagged template literals with template literals saved as constants?

I am working on an application where we have a number of languages. We are not using a package like 'i18n' but instead -- since the number of string literals isn't so large -- using a system like this:
// constants
const GREETING = 'greeting'
// es.js -- a language object
const aLanguageObject = {[GREETING] : '¿Cómo estás?'}
// helper function
const getText(selector) = currentLanguageObject[selector]
// usage file
const greetingPhrase = getText(GREETING);
where currentLanguageObject is one of es.js, en.js, etc, that is, one of the languageObjects already defined (we select based on a process.env value)
We'd like to be able to make use of tagged template literals to interpolate values into these template literals saved as constants.
I've tried to make tagged template literals work with our system like this:
// en.js
const aLanguageObject = {[CONFIRM_NUMBER] : `is the number ${0} correct?`}
and then use tagged templates like this
const theNumber = 12;
const myTag = (strings, numberExp) => `${strings[0]}${theNumber}${strings[1]}`;
const numberConfirmString = myTag`${getText(CONFIRM_NUMBER)})`;
but numberConfirmString is undefined.
Is there a way to "fill in" template literals saved as constants? Any help much appreciated!
The syntax:
const obj = {[key] = value}
Should be:
const obj = {[key]: value}
otherwise, a SyntaxError arises.
Apart of this, I think that something like this should work:
function generaTemplate (strings, ...keys) {
return function(data) {
let temp = strings.slice();
keys.forEach((key, i) => {
temp[i] = temp[i] + data[key];
});
return temp.join('');
}
}
const userTpl = user => generaTemplate`<article>
<p>Name: ${'name'}</p>
<p>Age: ${'age'}</p>
</article>`(user);
const users = [{
name: 'Eric',
age: '10'
}, {
name: 'Rob',
age: '20'
}];
const usersList = users.reduce((acc, user) => {
return acc + userTpl(user);
}, '');
console.log(usersList);
It returns this:
<article>
<p>Name: Eric</p>
<p>Age: 10</p>
</article><article>
<p>Name: Rob</p>
<p>Age: 20</p>
</article>
As alternative, you can also use:
const welcome = param => `Hello, ${param}`;
console.log(welcome('world'));
Output:
Hello, world
Check this: Defer execution for ES6 Template Literals

How to split ``(Backtick string) on each instance of ${variable}

So my question is how i can split the a string with backtick on each instance of variable.
I tried with \${.*?} but this will not work because ${variable} will be replaced by variable values first and than the split function will be executed.
Any idea how to do it ?
let a = 2
let b = 4
let x = `Superman${a}hello${b}one more`.split(/\${.*?}/g)
console.log(x)
On side not: I don't want a solution with wrapping it to " or '.
console.log('`Superman${a}hello${b}one more`'.split(/\${.*?}/g))
After the line executes, there is no way to get the original template string. However, you can use a tag function/tagged template literal to get the parts of the string, including the substitution values:
function Test() {
console.log(arguments)
return arguments.length - 1
}
let a = 2
let b = 4
let c = Test `Superman${a}hello${b}one more`
console.log(`This template string has ${c} substituted values`)
To clarify my comment to the original question here is an example of what the default Template Literal Function does:
function defaultTemplateLiteralFn(strs, ...args) {
return strs.map((str, idx) => str+(args[idx]||'')).join('');
}
const test = "special test";
const a = 10;
const b = 432;
console.log(`This is a ${test}. "${a}+${b}=${a+b}"`)
console.log(defaultTemplateLiteralFn`This is a ${test}. "${a}+${b}=${a+b}"`)
When you use a tagged template (IE: You don't provide a function to handle the template literal) The the language provides a default function that does something similar to what I do in my function defaultTemplateLiteralFn above. It returns the combined parts of the string with the values.
The example function takes each part of the string and puts the appropriate value after the string. If there is no value then it uses a blank string.
One way i have done is using template literal. i have seen this is being used in a in a library called styled-components which allows us to write css with js.
Would love to see other methods if there are any ?
function splitOnVariable(str, age){
// first argument to function will always be array of strings provided in input string.
return str
}
let a = 1;
let b = 2;
console.log(splitOnVariable`hello${a} break me on variable${b} !!!`)

Don't allow template literal to contain variable

I wish to create a function that takes an argument as a template literal, however will throw an error if the template literal has any variables within the template.
For example the following is valid.
const value = checker(`hello world`)
However this would throw an error.
const value = checker(`hello ${name}`)
What's the best way to achieve this?
Template literals allow for tagging, it can be achieved like this:
var a = 5;
var b = 10;
function noVarTemplate(strings, ...values) {
if (values.length) throw new Error('the noVarTemplate does not allow template literal values')
return strings[0]
}
const value = noVarTemplate`Hello World`
// const value = noVarTemplate`Hello ${a}`
console.log(value)

Defer execution for ES6 Template Literals

I am playing with the new ES6 Template Literals feature and the first thing that came to my head was a String.format for JavaScript so I went about implementing a prototype:
String.prototype.format = function() {
var self = this;
arguments.forEach(function(val,idx) {
self["p"+idx] = val;
});
return this.toString();
};
console.log(`Hello, ${p0}. This is a ${p1}`.format("world", "test"));
ES6Fiddle
However, the Template Literal is evaluated before it's passed to my prototype method. Is there any way I can write the above code to defer the result until after I have dynamically created the elements?
I can see three ways around this:
Use template strings like they were designed to be used, without any format function:
console.log(`Hello, ${"world"}. This is a ${"test"}`);
// might make more sense with variables:
var p0 = "world", p1 = "test";
console.log(`Hello, ${p0}. This is a ${p1}`);
or even function parameters for actual deferral of the evaluation:
const welcome = (p0, p1) => `Hello, ${p0}. This is a ${p1}`;
console.log(welcome("world", "test"));
Don't use a template string, but a plain string literal:
String.prototype.format = function() {
var args = arguments;
return this.replace(/\$\{p(\d)\}/g, function(match, id) {
return args[id];
});
};
console.log("Hello, ${p0}. This is a ${p1}".format("world", "test"));
Use a tagged template literal. Notice that the substitutions will still be evaluated without interception by the handler, so you cannot use identifiers like p0 without having a variable named so. This behavior may change if a different substitution body syntax proposal is accepted (Update: it was not).
function formatter(literals, ...substitutions) {
return {
format: function() {
var out = [];
for(var i=0, k=0; i < literals.length; i++) {
out[k++] = literals[i];
out[k++] = arguments[substitutions[i]];
}
out[k] = literals[i];
return out.join("");
}
};
}
console.log(formatter`Hello, ${0}. This is a ${1}`.format("world", "test"));
// Notice the number literals: ^ ^
Extending #Bergi 's answer, the power of tagged template strings reveals itself when you realize you can return anything as a result, not only plain strings. In his example, the tag constructs and returns an object with a closure and function property format.
In my favorite approach, I return a function value by itself, that you can call later and pass new parameters to fill the template. Like this:
function fmt([fisrt, ...rest], ...tags) {
return values => rest.reduce((acc, curr, i) => {
return acc + values[tags[i]] + curr;
}, fisrt);
}
Or, for the code golfers:
let fmt=([f,...r],...t)=>v=>r.reduce((a,c,i)=>a+v[t[i]]+c,f)
Then you construct your templates and defer the substitutions:
> fmt`Test with ${0}, ${1}, ${2} and ${0} again`(['A', 'B', 'C']);
// 'Test with A, B, C and A again'
> template = fmt`Test with ${'foo'}, ${'bar'}, ${'baz'} and ${'foo'} again`
> template({ foo:'FOO', bar:'BAR' })
// 'Test with FOO, BAR, undefined and FOO again'
Another option, closer to what you wrote, would be to return a object extended from a string, to get duck-typing out of the box and respect interface. An extension to the String.prototype wouldn't work because you'd need the closure of the template tag to resolve the parameters later.
class FormatString extends String {
// Some other custom extensions that don't need the template closure
}
function fmt([fisrt, ...rest], ...tags) {
const str = new FormatString(rest.reduce((acc, curr, i) => `${acc}\${${tags[i]}}${curr}`, fisrt));
str.format = values => rest.reduce((acc, curr, i) => {
return acc + values[tags[i]] + curr;
}, fisrt);
return str;
}
Then, in the call-site:
> console.log(fmt`Hello, ${0}. This is a ${1}.`.format(["world", "test"]));
// Hello, world. This is a test.
> template = fmt`Hello, ${'foo'}. This is a ${'bar'}.`
> console.log(template)
// { [String: 'Hello, ${foo}. This is a ${bar}.'] format: [Function] }
> console.log(template.format({ foo: true, bar: null }))
// Hello, true. This is a null.
You can refer to more information and applications in this other answer.
AFAIS, the useful feature "deferred execution of string templates" is still not available. Using a lambda is an expressive, readable and short solution, however:
var greetingTmpl = (...p)=>`Hello, ${p[0]}. This is a ${p[1]}`;
console.log( greetingTmpl("world","test") );
console.log( greetingTmpl("#CodingIntrigue","try") );
You can inject values into string using below function
let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);
let inject = (str, obj) => str.replace(/\${(.*?)}/g, (x,g)=> obj[g]);
// --- Examples ---
// 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 = "Today ${0} saw ${2} at shop ${1} times - ${0} was haapy."
let r2 = inject(t2, {...['JOHN', 6, 'SUsAN']} );
console.log("ARRAY :", r2);
I also like the idea of the String.format function, and being able to explicitly define the variables for resolution.
This is what I came up with... basically a String.replace method with a deepObject lookup.
const isUndefined = o => typeof o === 'undefined'
const nvl = (o, valueIfUndefined) => isUndefined(o) ? valueIfUndefined : o
// gets a deep value from an object, given a 'path'.
const getDeepValue = (obj, path) =>
path
.replace(/\[|\]\.?/g, '.')
.split('.')
.filter(s => s)
.reduce((acc, val) => acc && acc[val], obj)
// given a string, resolves all template variables.
const resolveTemplate = (str, variables) => {
return str.replace(/\$\{([^\}]+)\}/g, (m, g1) =>
nvl(getDeepValue(variables, g1), m))
}
// add a 'format' method to the String prototype.
String.prototype.format = function(variables) {
return resolveTemplate(this, variables)
}
// setup variables for resolution...
var variables = {}
variables['top level'] = 'Foo'
variables['deep object'] = {text:'Bar'}
var aGlobalVariable = 'Dog'
// ==> Foo Bar <==
console.log('==> ${top level} ${deep object.text} <=='.format(variables))
// ==> Dog Dog <==
console.log('==> ${aGlobalVariable} ${aGlobalVariable} <=='.format(this))
// ==> ${not an object.text} <==
console.log('==> ${not an object.text} <=='.format(variables))
Alternatively, if you want more than just variable resolution (e.g. the behaviour of template literals), you can use the following.
N.B. eval is considered 'evil' - consider using a safe-eval alternative.
// evalutes with a provided 'this' context.
const evalWithContext = (string, context) => function(s){
return eval(s);
}.call(context, string)
// given a string, resolves all template variables.
const resolveTemplate = function(str, variables) {
return str.replace(/\$\{([^\}]+)\}/g, (m, g1) => evalWithContext(g1, variables))
}
// add a 'format' method to the String prototype.
String.prototype.format = function(variables) {
return resolveTemplate(this, variables)
}
// ==> 5Foobar <==
console.log('==> ${1 + 4 + this.someVal} <=='.format({someVal: 'Foobar'}))
I posted an answer to a similar question that gives two approaches where the execution of the template literal is delayed. When the template literal is in a function, the template literal is only evaluated when the function is called, and it is evaluated using the scope of the function.
https://stackoverflow.com/a/49539260/188963
Although this question is already answered, here I have a simple implementations I use when I load configuration files (the code is typescript, but it is very easy to convert into JS, just remove the typings):
/**
* This approach has many limitations:
* - it does not accept variable names with numbers or other symbols (relatively easy to fix)
* - it does not accept arbitrary expressions (quite difficult to fix)
*/
function deferredTemplateLiteral(template: string, env: { [key: string]: string | undefined }): string {
const varsMatcher = /\${([a-zA-Z_]+)}/
const globalVarsmatcher = /\${[a-zA-Z_]+}/g
const varMatches: string[] = template.match(globalVarsmatcher) ?? []
const templateVarNames = varMatches.map(v => v.match(varsMatcher)?.[1] ?? '')
const templateValues: (string | undefined)[] = templateVarNames.map(v => env[v])
const templateInterpolator = new Function(...[...templateVarNames, `return \`${template}\`;`])
return templateInterpolator(...templateValues)
}
// Usage:
deferredTemplateLiteral("hello ${thing}", {thing: "world"}) === "hello world"
Although it's possible to make this stuff more powerful & flexible, it introduces too much complexity and risk without much benefit.
Here a link to the gist: https://gist.github.com/castarco/94c5385539cf4d7104cc4d3513c14f55
(see #Bergi's very similar answer above)
function interpolate(strings, ...positions) {
var errors = positions.filter(pos=>~~pos!==pos);
if (errors.length) {
throw "Invalid Interpolation Positions: " + errors.join(', ');
}
return function $(...vals) {
var output = '';
for (let i = 0; i < positions.length; i ++) {
output += (strings[i] || '') + (vals[positions[i] - 1] || '');
}
output += strings[strings.length - 1];
return output;
};
}
var iString = interpolate`This is ${1}, which is pretty ${2} and ${3}. Just to reiterate, ${1} is ${2}! (nothing ${0} ${100} here)`;
// Sets iString to an interpolation function
console.log(iString('interpolation', 'cool', 'useful', 'extra'));
// Substitutes the values into the iString and returns:
// 'This is interpolation, which is pretty cool and useful.
// Just to reiterate, interpolation is cool! (nothing here)'
The main difference between this and #Bergi's answer is how errors are handled (silently vs not).
It should be easy enough to expand this idea into a syntax that accepts named arguments:
interpolate`This is ${'foo'}, which is pretty ${'bar'}.`({foo: 'interpolation', bar: 'cool'});
https://github.com/spikesagal/es6interpolate/blob/main/src/interpolate.js

Categories