How to pass a string as an argument in Handlebars helper expression? - javascript

I am trying to create my own Handlebars helper, but am having problems passing it arguments. For context, I'm trying to hack Ghost (http://tryghost.org), but I suspect this is a more general Handlebars problem.
Working
First, a working example. Here is the relevant part of my template:
<h1>{{myHelper}}</h1>
Here is my Handlebars.registerHelper method (Ghost renames it but it's the same):
ghost.registerThemeHelper("myHelper", function() {
console.log(this.posts[0].title); // outputs "Welcome to Ghost" to console
return this.posts[0].title; // returns "Welcome to Ghost"
})
Not Working
Below is what I want to achieve. The template:
<h1>{{myHelper "title"}}</h1>
<h3>{{myHelper "slug"}}</h3>
When I try to pass arguments to the method it fails to substitute the variable:
ghost.registerThemeHelper("myHelper", function(myData) {
console.log(this.posts[0].myData); // outputs "undefined" to console
return this.posts[0].myData; // returns nothing
})
What is the proper means of passing a string like "title" in order for it to be evaluated in an expression?
For any curious Ghost users, I registered my own helper within the activateTheme() function in ghost/core/server.js

The console.log output for arguments:
{ '0': 'title', '1': { hash: {}, data: { blog: [Object] } } }
suggests that the first argument to the helper is 'title' when you say {{myHelper "title"}}. If this.posts[0] is the post you're interested in, then you'd want to look at:
this.posts[0][myData]
to get the title property of this.posts[0] when myData is 'title'. Note that square brackets are used to access both normal arrays (which are indexed by non-negative integers) and objects (which are indexed by strings).
A bit of time in the MDN JavaScript reference might be helpful.

Related

Invoking Javascript array methods without the use of parentheses [duplicate]

I'm not sure how to explain this, but when I run
console.log`1`
In google chrome, I get output like
console.log`1`
VM12380:2 ["1", raw: Array[1]]
Why is the backtick calling the log function, and why is it making a index of raw: Array[1]?
Question brought up in the JS room by Catgocat, but no answers made sense besides something about templating strings that didn't really fit why this is happening.
It is called Tagged Template in ES-6 more could be read about them Here, funny I found the link in the starred section of the very chat.
But the relevant part of the code is below (you can basically create a filtered sort).
function tag(strings, ...values) {
assert(strings[0] === 'a');
assert(strings[1] === 'b');
assert(values[0] === 42);
return 'whatever';
}
tag `a${ 42 }b` // "whatever"
Basically, its merely tagging the "1" with console.log function, as it would do with any other function. The tagging functions accept parsed values of template strings and the values separately upon which further tasks can be performed.
Babel transpiles the above code to
var _taggedTemplateLiteralLoose = function (strings, raw) { strings.raw = raw; return strings; };
console.log(_taggedTemplateLiteralLoose(["1"], ["1"]));
As you can see it in the example above, after being transpiled by babel, the tagging function (console.log) is being passed the return value of the following es6->5 transpiled code.
_taggedTemplateLiteralLoose( ["1"], ["1"] );
The return value of this function is passed to console.log which will then print the array.
Tagged template literal:
The following syntax:
function`your template ${foo}`;
Is called the tagged template literal.
The function which is called as a tagged template literal receives the its arguments in the following manner:
function taggedTemplate(strings, arg1, arg2, arg3, arg4) {
console.log(strings);
console.log(arg1, arg2, arg3, arg4);
}
taggedTemplate`a${1}b${2}c${3}`;
The first argument is an array of all the individual string characters
The remaining argument correspond with the values of the variables which we receive via string interpolation. Notice in the example that there is no value for arg4 (because there are only 3 times string interpolation) and thus undefined is logged when we try to log arg4
Using the rest parameter syntax:
If we don't know beforehand how many times string interpolation will take place in the template string it is often useful to use the rest parameter syntax. This syntax stores the remaining arguments which the function receives into an array. For example:
function taggedTemplate(strings, ...rest) {
console.log(rest);
}
taggedTemplate `a${1}b${2}c${3}`;
taggedTemplate `a${1}b${2}c${3}d${4}`;
Late to the party but, TBH, none of the answers give an explanation to 50% of the original question ("why the raw: Array[1]")
1. Why is it possible to call the function without parenthesis, using backticks?
console.log`1`
As others have pointed out, this is called Tagged Template (more details also here).
Using this syntax, the function will receive the following arguments:
First argument: an array containing the different parts of the string that are not expressions.
Rest of arguments: each of the values that are being interpolated (ie. those which are expressions).
Basically, the following are 'almost' equivalent:
// Tagged Template
fn`My uncle ${uncleName} is ${uncleAge} years old!`
// function call
fn(["My uncle ", " is ", " years old!"], uncleName, uncleAge);
(see point 2. to understand why they're not exactly the same)
2. Why the ["1", raw: Array[1]] ???
The array being passed as the first argument contains a property raw, wich allows accessing the raw strings as they were entered (without processing escape sequences).
Example use case:
let fileName = "asdf";
fn`In the folder C:\Documents\Foo, create a new file ${fileName}`
function fn(a, ...rest) {
console.log(a); //In the folder C:DocumentsFoo, create a new file
console.log(a.raw); //In the folder C:\Documents\Foo, create a new file
}
What, an array with a property ??? ???
Yes, since JavaScript arrays are actually objects, they can store properties.
Example:
const arr = [1, 2, 3];
arr.property = "value";
console.log(arr); //[1, 2, 3, property: "value"]

Why can functions be called using `` operator in JavaScript? [duplicate]

I'm not sure how to explain this, but when I run
console.log`1`
In google chrome, I get output like
console.log`1`
VM12380:2 ["1", raw: Array[1]]
Why is the backtick calling the log function, and why is it making a index of raw: Array[1]?
Question brought up in the JS room by Catgocat, but no answers made sense besides something about templating strings that didn't really fit why this is happening.
It is called Tagged Template in ES-6 more could be read about them Here, funny I found the link in the starred section of the very chat.
But the relevant part of the code is below (you can basically create a filtered sort).
function tag(strings, ...values) {
assert(strings[0] === 'a');
assert(strings[1] === 'b');
assert(values[0] === 42);
return 'whatever';
}
tag `a${ 42 }b` // "whatever"
Basically, its merely tagging the "1" with console.log function, as it would do with any other function. The tagging functions accept parsed values of template strings and the values separately upon which further tasks can be performed.
Babel transpiles the above code to
var _taggedTemplateLiteralLoose = function (strings, raw) { strings.raw = raw; return strings; };
console.log(_taggedTemplateLiteralLoose(["1"], ["1"]));
As you can see it in the example above, after being transpiled by babel, the tagging function (console.log) is being passed the return value of the following es6->5 transpiled code.
_taggedTemplateLiteralLoose( ["1"], ["1"] );
The return value of this function is passed to console.log which will then print the array.
Tagged template literal:
The following syntax:
function`your template ${foo}`;
Is called the tagged template literal.
The function which is called as a tagged template literal receives the its arguments in the following manner:
function taggedTemplate(strings, arg1, arg2, arg3, arg4) {
console.log(strings);
console.log(arg1, arg2, arg3, arg4);
}
taggedTemplate`a${1}b${2}c${3}`;
The first argument is an array of all the individual string characters
The remaining argument correspond with the values of the variables which we receive via string interpolation. Notice in the example that there is no value for arg4 (because there are only 3 times string interpolation) and thus undefined is logged when we try to log arg4
Using the rest parameter syntax:
If we don't know beforehand how many times string interpolation will take place in the template string it is often useful to use the rest parameter syntax. This syntax stores the remaining arguments which the function receives into an array. For example:
function taggedTemplate(strings, ...rest) {
console.log(rest);
}
taggedTemplate `a${1}b${2}c${3}`;
taggedTemplate `a${1}b${2}c${3}d${4}`;
Late to the party but, TBH, none of the answers give an explanation to 50% of the original question ("why the raw: Array[1]")
1. Why is it possible to call the function without parenthesis, using backticks?
console.log`1`
As others have pointed out, this is called Tagged Template (more details also here).
Using this syntax, the function will receive the following arguments:
First argument: an array containing the different parts of the string that are not expressions.
Rest of arguments: each of the values that are being interpolated (ie. those which are expressions).
Basically, the following are 'almost' equivalent:
// Tagged Template
fn`My uncle ${uncleName} is ${uncleAge} years old!`
// function call
fn(["My uncle ", " is ", " years old!"], uncleName, uncleAge);
(see point 2. to understand why they're not exactly the same)
2. Why the ["1", raw: Array[1]] ???
The array being passed as the first argument contains a property raw, wich allows accessing the raw strings as they were entered (without processing escape sequences).
Example use case:
let fileName = "asdf";
fn`In the folder C:\Documents\Foo, create a new file ${fileName}`
function fn(a, ...rest) {
console.log(a); //In the folder C:DocumentsFoo, create a new file
console.log(a.raw); //In the folder C:\Documents\Foo, create a new file
}
What, an array with a property ??? ???
Yes, since JavaScript arrays are actually objects, they can store properties.
Example:
const arr = [1, 2, 3];
arr.property = "value";
console.log(arr); //[1, 2, 3, property: "value"]

functionName`string` - what does the syntax work? [duplicate]

I'm not sure how to explain this, but when I run
console.log`1`
In google chrome, I get output like
console.log`1`
VM12380:2 ["1", raw: Array[1]]
Why is the backtick calling the log function, and why is it making a index of raw: Array[1]?
Question brought up in the JS room by Catgocat, but no answers made sense besides something about templating strings that didn't really fit why this is happening.
It is called Tagged Template in ES-6 more could be read about them Here, funny I found the link in the starred section of the very chat.
But the relevant part of the code is below (you can basically create a filtered sort).
function tag(strings, ...values) {
assert(strings[0] === 'a');
assert(strings[1] === 'b');
assert(values[0] === 42);
return 'whatever';
}
tag `a${ 42 }b` // "whatever"
Basically, its merely tagging the "1" with console.log function, as it would do with any other function. The tagging functions accept parsed values of template strings and the values separately upon which further tasks can be performed.
Babel transpiles the above code to
var _taggedTemplateLiteralLoose = function (strings, raw) { strings.raw = raw; return strings; };
console.log(_taggedTemplateLiteralLoose(["1"], ["1"]));
As you can see it in the example above, after being transpiled by babel, the tagging function (console.log) is being passed the return value of the following es6->5 transpiled code.
_taggedTemplateLiteralLoose( ["1"], ["1"] );
The return value of this function is passed to console.log which will then print the array.
Tagged template literal:
The following syntax:
function`your template ${foo}`;
Is called the tagged template literal.
The function which is called as a tagged template literal receives the its arguments in the following manner:
function taggedTemplate(strings, arg1, arg2, arg3, arg4) {
console.log(strings);
console.log(arg1, arg2, arg3, arg4);
}
taggedTemplate`a${1}b${2}c${3}`;
The first argument is an array of all the individual string characters
The remaining argument correspond with the values of the variables which we receive via string interpolation. Notice in the example that there is no value for arg4 (because there are only 3 times string interpolation) and thus undefined is logged when we try to log arg4
Using the rest parameter syntax:
If we don't know beforehand how many times string interpolation will take place in the template string it is often useful to use the rest parameter syntax. This syntax stores the remaining arguments which the function receives into an array. For example:
function taggedTemplate(strings, ...rest) {
console.log(rest);
}
taggedTemplate `a${1}b${2}c${3}`;
taggedTemplate `a${1}b${2}c${3}d${4}`;
Late to the party but, TBH, none of the answers give an explanation to 50% of the original question ("why the raw: Array[1]")
1. Why is it possible to call the function without parenthesis, using backticks?
console.log`1`
As others have pointed out, this is called Tagged Template (more details also here).
Using this syntax, the function will receive the following arguments:
First argument: an array containing the different parts of the string that are not expressions.
Rest of arguments: each of the values that are being interpolated (ie. those which are expressions).
Basically, the following are 'almost' equivalent:
// Tagged Template
fn`My uncle ${uncleName} is ${uncleAge} years old!`
// function call
fn(["My uncle ", " is ", " years old!"], uncleName, uncleAge);
(see point 2. to understand why they're not exactly the same)
2. Why the ["1", raw: Array[1]] ???
The array being passed as the first argument contains a property raw, wich allows accessing the raw strings as they were entered (without processing escape sequences).
Example use case:
let fileName = "asdf";
fn`In the folder C:\Documents\Foo, create a new file ${fileName}`
function fn(a, ...rest) {
console.log(a); //In the folder C:DocumentsFoo, create a new file
console.log(a.raw); //In the folder C:\Documents\Foo, create a new file
}
What, an array with a property ??? ???
Yes, since JavaScript arrays are actually objects, they can store properties.
Example:
const arr = [1, 2, 3];
arr.property = "value";
console.log(arr); //[1, 2, 3, property: "value"]

JS: using eval on a function while trying to pass an array as parameter, but it throws an error

i want to create a dynamic generated form using javascript, everything works fine, until i try to pass an array as parameter. When i do this, an error happens. Coulr anyone explain what this is?
Heres my code:
var loadFrm = function(component) {
for(nItem in component) {
var myComponent = "add" + firstToUpper(component[nItem].type);
var callComponent = myComponent + "(" + component[nItem].opt + ");";
eval(callComponent);
}
}
var json = [
{
type: "scale",
opt: {content: [{label: "male", value: "m"}, {label: "female", value: "f"}]}
}
];
loadFrm(json);
Edit Here's the error:
missing ] after element list
[Break on this error] addScale([object Object]);
If you use a debugger to look at the string callComponent, you'll probably find it looks something like this:
addScale([object Object])
...which isn't what you want. That's because you're effectively calling toString on your opt object, and the default toString on objects just looks like that. The eval error is because that's invalid syntax.
Generally speaking, any time you think you need to use eval, there's almost certainly a better answer. In this case, it looks like you're trying to call a function and pass in opt. Assuming these functions are "globals", you can do that like this:
var loadFrm = function(component) {
var nItem, functionName;
for (nItem = 0; nItem < component.length; ++nItem) {
functionName = "add" + firstToUpper(component[nItem].type);
window[functionName](component[nItem].opt);
}
}
Live example
Notes on the above:
Don't use for..in to loop through arrays unless you really know what you're doing. for..in does not enumerate the indexes of an array, it enumerates the properties of an object.
We look up the function by name using window[functionName]. This works because "globals" are actually properties of the window object, and you can look up properties using a string name for them using bracketed notation.
Having gotten the function via window[functionName], we just call it directly, passing in the object opt rather than a string form of it. I assume addScale expects to see an object.
I moved all of the vars to the top of the function because that's where they really are (details).
If you can, I'd recommend moving addScale and the related functions to their own object rather than putting them on window. The window namespace is already pretty crowded. Here's the live example modified to not add any symbols to window at all, instead putting the addScale function on an object called functions and using it from there.
Off-topic: The syntax var loadFrm = function(component) creates an anonymous function that it then assigns to a variable. This is used a lot, but unless you're creating different functions based on a condition, e.g.:
var f;
if (...) {
f = function() { ... };
}
else {
f = function() { ... };
}
...it's not actually useful. (If you are creating different functions based on a condition like that, then it's not only useful, it's necessary.) I recommend using named functions whenever possible, because a function with a name helps your tools help you by showing you the function name in error messages, call stacks, etc.
Off-topic 2: You have a variable called json, but FYI, it's not using JSON notation. It's using a combination of JavaScript array and object literal notation, which is a superset of JSON. You'll see a lot of people confused about this, I mention it because you said you're new and so it's worth nipping in the bud. :-) JSON is purely a notation. (A very useful one.)
Use this:
fn = eval(functionName);
fn(objParameter)

Passing unknown amounts of variables using through a string string and eval and multiple functions and all sorts

In short, I want to use an object literal to allow me to pass a unknown amount of variables in any order to a function. Whilst this is not big deal in theory, in my code, this object literal is passed to a second function called on_change.
on_change works by comparing an element's innerHTML to a string; If it is the same, it sets a timeout to call the function again. If the element's innerHTML is different from the string, then the third parameter is executed, this will either be a function or a string. either way it will execute. I have tested this function plenty and used it for a while now.
However, I cannot seem to get the object literal to flow through the function calls...
var params = { xpos:'false'};
on_change('window_3_cont_buffer','','
if(Window_manager.windows[3].window_cont_buffer.getElementsByTagName(\'content\')[0].getElementsByTagName(\'p\')[0].innerHTML == \'ERROR\'){
alert(Window_manager.windows[3].window_cont_buffer.getElementsByTagName(\'content\')[0].getElementsByTagName(\'p\')[1].innerHTML);
return false;
} else {
Window_manager.windows[3].load_xml(\'location/view.php?location_ID=3\', \'\', ' + params + ' ); }
');
I call this as part of the form submission. After this line, I then call a function to load some content via ajax, which works fine and will trigger the code from the on_change function.
I have tested the load_xml function, it is able to call alert(param.xpos) and get the correct response. I can even added in a check for being undefined so that rest of the times I call load_xml I don't get swamped with alerts.
The load_xml function first sets up the on_change function, then calls the function to load the content to a hidden div. Once the AJAX request has updated that DIV, the on_change function should now call the parse_xml function. This pulls out the information from the xml file. However... The idea of this object literal param is that it can tell this parse_xml function to ignore certain things.
on_change("window_" + this.id + "_cont_buffer", "", "Window_manager.windows[" + this.id + "].parse_xml('" + param + "')");
This is part of load_xml, it works perfectly fine, even with the param bit in there. except, parse_xml does not seem to be able to use that parameter.
I have been able to get it to a point where parse_xml can alert(param) and give [object object] which I would of thought meant that the object literal had been passed through, but when I try and call alert(param.xpos) I get undefined.
I know this is a pig of a problem, and I could get around it by just having the function take a zillion boolean parameters, but its just not a very nice solution.
In effect, what you have is this:
var params = {param: "value"};
foo("bar('one', 'two', 'three');");
...where foo uses eval on that string, something like:
function foo(codestring) {
eval(codestring);
}
...and you're looking for how to embed params in that.
You could do this by serializing the object literal as a string so that when you combine it with the other string, and the total string is evaluated, it gets evaluated. Browsers are slowly getting JSON serialization built in, but for now you want to use jQuery, Prototype, or (if you just want this part) json2.js from Crockford, which offers JSON.stringify for turning objects that can be turned into JSON strings, into JSON strings. So:
var params = {param: "value"};
foo("bar(" + JSON.stringify(params) + ");");
But what you really want to do is refactor so that all of that logic is expressed as code, not code within a string. Then you could pass the literal directly, plus a whole raft of other benefits, including modularization, debugging, etc.
var params = {param: "value"};
function callBar() {
bar(params);
}
foo(callBar);
...changing foo so that it calls a function rather than evaling a string. (eval is to be avoided whenever possible, and to paraphrase the Dalai Lama, it's [almost] always possible.) My sample foo changes to:
function foo(func) {
func();
}
If foo needs to include additional information for bar (and if callBar is set up to handle those extra arguments), it can use Function#call or Function#apply to do that. (Those links are to MDC, but don't worry, they're not Firefox-specific, they've been in the ECMA spec for years and are nearly universally supported.)
You can't put an object inside a string. You would have to serialise the object, add it into the string, then parse it back into a structured object on the other side. The simplest way to do that would be to use JSON (via JSON.stringify or a library fallback for older browsers that don't have it), since JSON evaluates as simple JavaScript.
Note that you wouldn't get the exact same object back, but a new one with the same attributes and properties, and it only works for simple types, so you can't include a function in the object or anything.
However, in any case, passing JavaScript code around in strings is an anti-pattern to be strenuously avoided. Instead use inline functions, and you don't have to worry about what you can and can't put in a string, and you can get rid of all that unreadable wrapping and \-escaping:
var params = {xpos: 'false'};
on_change('window_3_cont_buffer', '', function() {
var w= Window_manager.windows[3];
var ps= w.window_cont_buffer.getElementsByTagName('content')[0].getElementsByTagName('p');
if (ps[0].innerHTML==='ERROR') {
alert(ps[1].innerHTML);
return false;
} else {
w.load_xml('location/view.php?location_ID=3', '', params);
}
});
Some general techniques which may be helpful for you:
// Example of calling function objects given as arguments:
function on_change(foo, callback1, callback2) {
if (foo)
callback1();
else
callback2.call(available_as_this);
}
on_change(foo, function() { your code }, function() { another code });
// Example of function taking arbitrary number of arguments:
function any_params() {
console.log('I got ' + arguments.length + 'arguments!');
console.log('First argument: ' + arguments[0]);
}
any_params('one', 'two', 'three', [], null, {});
See arguments variable and call().
I want to use object literal to allow me to pass a random amount of variables in any order to a function.
Why oh why don't you just create an object which contains the parameters and functions, and pass that around? The receiving function can just test to see if a property of the object is set before trying to use it.

Categories