I am trying to pass an array of data to a custom helper with express-handlebars. I can't get it to work.
I can pass in a string and I can pass in an array to an individual view, but I can't seem to pass in an array to a custom helper. Here is my code:
String Passed to Custom Helper (this works)
// app.js
const hbs = expressHandlebars.create({
extname: 'html',
helpers: {
foo: function () {
return 'FOO!'
}
})
// views/layout/main.html
<h2>{{ foo }}</h2>
// Result --> FOO! outputed as expected
Passing an Array to an Individual view (this also works)
// controllers/pageControllers.js
exports.getIndexPage = (req, res) => {
res.render('index', {
testList: ['one', 'two', 'three']
})
}
// views/index.html
{{#each testList}}
<div>{{this}}</div>
{{/each}}
// Result --> one, two, three outputted as expected
Array Passed to a Custom Helper (this does NOT work)
// app.js
const hbs = expressHandlebars.create({
extname: 'html',
helpers: {
testList2: function () {
return ['one', 'two', 'three']
},
}
})
// views/layout/main.html
{{#each testList2}}
<div>{{this}}</div>
{{/each}}
// Result --> NOTHING. No output, no error messages.
Do custom helpers not take arrays? The same code works on an individual view. The same code works for a string on a custom helper. It just does not work for an array on a custom helper.
If custom helpers do NOT accept arrays, then how can I get an array of data into my main layout file? If it does take an array, then what I am doing wrong and how to get this to work?
Thanks.
According to documentation#helpers, -i'm not using express-handlebars myself.
A Handlebars helper call is a simple identifier, followed by zero or more parameters (separated by a space)
This means you call it with the first identifier and what follows is to be evaluated.
The helper just takes in data and outputs a manipulated outcome to inject in the view.
Taking in data would look something like {{ foo exp1 exp2 }} which translates to {{foo(exp1,exp2)}} and the helper would look something like this.
foo: function (exp1, exp2) {
return `this is ${exp1} and ${exp2}`
}
It's like invoking a regular function.
In your first example you call foo() (the helper function) which takes nothing in but outputs a string, and when activated you get the value return.
In your second example you are using express to render a variable which is loopable on a view. nothing to do with handlebars instance creating. (you are creating basicly an express res.locals.testList variable.)
In your third example you try to loop the helper function and not really invoking it. It's trying to do something like this:
for(let v of function() {}) {
//...
}
This will give the error (probably silent in your case,) is not iterable
It's seems like you should manipulate the data array inside the helper function and then return a string or an html string (using Handlebars.Safestring as mentioned in the docs for escaping html). Then in your view just call it {{ testList2 }} which will out put a ready made HTML for the structure you were trying to do.
Hope this makes sense.
That been said, There are more advanced ways of using helpers as mentioned in docs. that maybe could fit more for whatever it is you are trying to do.
Related
I want to use ejs layouts to only some of the files (not the whole project). But using express-ejs-layouts it uses layout to all files. Here is the core part of the code.
app.get("/page1",(req,res)=>{
res.render("p1");
});
app.get("/page2",(req,res)=>{
res.render("p2");
});
app.get("/page3",(req,res)=>{
// Don't add Layout file in this page
res.render("p3",{
layout:null
});
});
I found in stackoverflow that you can use multiple layouts by passing an object having a key layout and its value as name of layout file { layout : "layoutNumberOne" }. But setting its value to undefined or null doesn't work. And the solution I want is not to create an empty layout file and passing the name of that file in the object to the render() function which will work, because if that is the case I will have to specify the { layout : "emptyLayout" } in all the pages which I have a lot. I am looking for an answer that can add a layout to a specific route, so that all requests in the route will have this layout file and will not affect other paths. How can I do that ?
you could write a helper function like this:
function demo(route, view, data={}){
app.get(route, (req, res) => {
let options;
if(route === "/page3"){
options = {
layout: "blank-layout"
};
};
const locals = {
...data,
...options
};
res.render(view, locals)
});
}
and call it whenever you want to render a page:
demo("/page3", "p3", {anything: "whatever you want"})
This function takes in a route, view file, and an optional data object in the likely event that you want to pass in some dynamic data to use inside your template.
It sets your desired route, then initialises a variable called "options" to later be passed into the "locals" object below once the if statement has given it a value.
The function then compares the value of your "route" parameter to the route you want to assign some unique options to (in this case "/page3"), should the result be true it'll set the "options" variable to be an object with a property called layout and a value of "blank-layout". The value pertains to the name of the layout file you would like to use so feel free to change that value to suit your needs.
Finally this "options" object is merged with the "data" object you may have passed in earlier (if you didn't pass one in it'll just use an empty object); the result of merging the two objects is stored in the "locals" variable and now contains every property your data object and options object have. This variable is then passed into the render function along with the view file.
note that you can use multiple if statements to set options for other routes
In flask, to create a url I use {{ url_for('login') }} in the template (Jinja2) and it returns whatever the url is associated the name login (for example /auth/login/). How do I do this in Handlebars.js and Backbone.js? Is it already implemented?
What I want to achieve:
{{#urlfor}}loginRoute{{/urlfor}}
and for that to return:
/auth/login.
My routes:
routes: {
'': 'indexRoute',
'auth/login': 'loginRoute'
}
I cannot make sense of why you would be wanting to do this. All you would be doing is creating a Handlebars helper for finding and returning the name of a key of some Object that holds some value:
The helper would be simple enough:
Handlebars.registerHelper('urlfor', function (value) {
for (var key in routes) {
if (routes.hasOwnProperty(key) && routes[key] === value) {
return key;
}
}
return null;
});
(This assumes, of course, that the helper has access to routes in its lexical scope.)
You would use this helper in a template in the following way:
<script id="Template" type="text/template">
<p>The URL for loginRoute is {{urlfor 'loginRoute'}}</p>
</script>
However, it seems to me that it would make more sense to forego the helper and instead do this computation before executing your template function and simply pass the value as a parameter:
template({ loginUrl: getUrlFor('loginRoute') });
I just want to run the template string against an object and examine the result
I have a string that is a template. I've "compiled" it. Now I want to run it against an object and examine the result.
But this doesn't work:
var template = '<div>{{#each items}}<div>{{item}}</div>{{/each}}</div>';
var compiled = Ember.Handlebars.compile(template);
var result = compiled({ items: [1, 2, 3] }); // ERRORS
What I want to get is the DOM result of running my compiled string against an object. In other words, a set of DOM elements that looks something like this:
<div>
<div>1</div>
<div>2</div>
<div>3</div>
</div>
It appears that Ember.Handlebars.compile is very tightly coupled to other parts of an Ember application, to the point it expects a lot of things to be populated in the context I'm passing ot the compiled function. I have yet to figure out what all of these things are, or if there is a better way to create a context to pass to the compiled function.
Other things:
I don't want to use plain "non-Ember" Handlebars.
I'd like to avoid creating an Ember Application if I can.
I don't really want to answer questions about "why" I want to do this. This is what I want to do. :P
Why do you want to do this? ;)
Honestly the easiest way to do this will be to create a view. Ember hooks up a bunch of fancy rendering stuff when it calls compile due to the data binding etc, so it's difficult to create it straight from the compile function (it passes in a slew of additional stuff, like buffers etc...)
var view = Ember.View.extend({
template:Ember.Handlebars.compile('hello <div>{{#each item in view.items}}<div>{{item}}</div>{{/each}}</div>')
});
var foo = view.create({ items: [1, 2, 3] });
foo.appendTo('#blah');
Example
http://emberjs.jsbin.com/qeyenuyi/1/edit
// you must wait for all bindings to sync before you can check the contents of #blah:
var empty = $('#blah').html(); // this will be empty
Ember.run.next(function(){
var notEmpty = $('#blah').html(); // this will have the proper result in it
});
or you can hook up to the didInsertElement callback
var foo = view.create(blah);
foo.didInsertElement = function(){
console.log(foo.$().html());
}
foo.appendTo('#blah');
http://emberjs.jsbin.com/qeyenuyi/6/edit
The bindings are still in tact when you create a Ember handlebars template, so you can modify the object passed in and it will update the template.
http://emberjs.jsbin.com/qeyenuyi/2/edit
I'm working on my first Ember.js app and am having some trouble connecting all the dots. It would be really helpful if I could just see all the variables available within a given handlebars template.
There is a related question, but you have to know the variable that is in scope to use it:
How do I add console.log() JavaScript logic inside of a Handlebars template?
How can I output all the variables?
a good option is to debug the value of 'this' in a template using the Handlebars helpers:
1.
{{#each}}
{{log this}}
{{/each}}
or,
2. similar to #watson suggested
{{#each}}
{{debugger}}
{{/each}}
and then drill in to the Local Scope Variables for 'this' in the Dev Tools
or alternatively, 3. you could log things directly from inside your Controller init method, such as:
App.UsersController = Ember.ArrayController.extend({
init: function() {
console.log(this);
console.log(this.getProperties('.'));
}
});
Make sure you try out Firebug - you'll get a different perspective on things, which I found helpful. But don't abandon chrome completely; you will need the Ember Inspector at some point.
I'm using the same debugging helper everyone recommends, and this is how Chrome displays it:
When I expand the same object in firebug, I get the following info, including the variables I was looking for (sources[]) and some other useful properties I hadn't seen in Chrome.
I created Barhandles a few years ago. It will use the Handlebars parser to produce the AST, and then extract variable references from it. The extractSchema method will — well — extract a schema. That schema is not based on JSON Schema or Joi or anything. It's a homegrown format that captures most of the things you could possibly extract from Handlebars template.
So, this barhandlers.extractSchema('{{foo.bar}}') produces:
{
"foo": {
"_type": "object",
"_optional": false,
"bar": {
"_type": "any",
"_optional": false
}
}
}
It will take into account that an {{#if expr}} will automatically make nested references optional. It correctly handles scope changes based on {{#with expr}} constructs, and it allows you to add support for your own custom directives as well.
http://nxt.flotsam.nl/barhandles
https://medium.com/east-pole/advanced-barhandles-4a7e64c1bc0d
We used it to do validation on the data structures that we passed into the template, and it was working pretty well for that purpose.
If you really need to dump the variables in your template, you can explore the template AST and output the content of the relevant nodes (see the compiler sources). This is not an easy task because you have to find your way through trials and errors, and the code is quite low-level and there are not so many comments.
It seems Handlerbars doesn't have a shortcut for what you're asking, so the steps would be:
precompile a template (see the command line source, I think the function is called handlebars.precompile())
explore the AST
You can do this by leveraging Handlebars.parseWithoutProcessing which takes the input template string. If you use TypeScript, that returns a specific type hbs.AST.Program. You can filter for only the moustache statements, and then iterate through these statements to get the variable names.
This method also supports Handlebars helpers, so you can get the key for that, but because of this, this function is a bit more complex as you'd need to check different properties on the moustache statement:
/**
* Getting the variables from the Handlebars template.
* Supports helpers too.
* #param input
*/
const getHandlebarsVariables = (input = '') => {
const ast = Handlebars.parseWithoutProcessing(input);
return ast.body
.filter(({ type }) => type === 'MustacheStatement')
.map((statement) => statement.params[0]?.original || statement.path?.original);
};
Here's the TypeScript version, which is a bit involved due to the conditional properties, but can help explain the types a bit more:
/**
* Getting the variables from the Handlebars template.
* Supports helpers too.
* #param input
*/
const getHandlebarsVariables = (input: string): string[] => {
const ast: hbs.AST.Program = Handlebars.parseWithoutProcessing(input);
return ast.body.filter(({ type }: hbs.AST.Statement) => (
type === 'MustacheStatement'
))
.map((statement: hbs.AST.Statement) => {
const moustacheStatement: hbs.AST.MustacheStatement = statement as hbs.AST.MustacheStatement;
const paramsExpressionList = moustacheStatement.params as hbs.AST.PathExpression[];
const pathExpression = moustacheStatement.path as hbs.AST.PathExpression;
return paramsExpressionList[0]?.original || pathExpression.original;
});
};
I've made a Codepen that illustrates this. Essentially, given the following template:
Hello, {{first_name}}! The lottery prize is {{formatCurrency prize_amount}}! Good luck!
It will use window.prompt to ask the user for their name and the prize amount. The example also implements a helper formatCurrency. You can see it here: https://codepen.io/tinacious/pen/GRqYWJE
The sample Ember app you mention defines its EmberObjects right in its app.js. So basically, what's available on the objects are the properties that are defined onto them there. (e.g. subreddit has a title, etc).
If you want a globally available way to dump an object's property schema out to the console, one approach would be to create a "debug" helper that walks the members of the passed-in contexts and writes them out. Something like:
Handlebars.registerHelper('debug', function (emberObject) {
if (emberObject && emberObject.contexts) {
var out = '';
for (var context in emberObject.contexts) {
for (var prop in context) {
out += prop + ": " + context[prop] + "\n"
}
}
if (console && console.log) {
console.log("Debug\n----------------\n" + out);
}
}
});
Then call it on whatever you want to inspect:
<div>Some markup</div>{{debug}}<div>Blah</div>
This will use whatever EmberObject is in scope, so pop it inside of an {{#each}} if you want to inspect the list elements, as opposed to the object with that list.
The variables available within a template are only constrained by the model you are using to render the template.
You should set a breakpoint in your app where you render the template and see what is in your model at that point, which will should you what you have available to put in your template.
I'm using a handlebar helper for calculating how many rows there is in an array. If it's above 2 it returns true, and it works as it's suppose to. Looks like this:
define('templates/helpers/countRows', ['handlebars'], function ( Handlebars ) {
function countRows(selectedArray) {
var selectedArrayLength = selectedArray.length;
if (parseInt(selectedArrayLength) > 2) {
return true;
}
}
Handlebars.registerHelper('countRows', countRows);
return countRows;
});
The problem is that I want to set a condition in my hbs template to check if the value is true or not before outputting it. If it isn't true, I don't want it to output. I was hoping I could do something like this:
{{#if countRows "my array"}}
markup that only gets displayed if value is true
{{/if}}
But this isn't valid unfortunately..
The best approach would be to define computed properties on your controller to handle this type of logic.
App.ThingsController = Ember.ArrayController.extend({
enoughRows: Ember.computed.gte('content.length', 2)
});
Then in your template:
{{#if enoughRows}}
...
{{/if}}
Having logic like this embedded in a template is hard to debug and test. Following this philosophy, handlebars makes it hard to do condition checks outside of true/false.
If you need to repeat this kind of logic in many controllers, consider making a mixin.
App.EnoughRowsMixin = Ember.Mixin.create({
enoughRows: Ember.computed.gte('content.length', 2)
});