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') });
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
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.
In keystonejs (a node cms), I am using handlebars as templating engine in keystonejs. In handlebar file, I am using #each to iterate through an array.
{{#each data.positions}}
<h1 onclick = "callFunction()"> {{title}} </h1>
{{/each}}
Here I want to call a function within route, How can I do it?
Other way that is coming in my mind to to initialize locals variable. Like I have a variable locals.selectedPositionIndex in route file and I want to assign #index value of specific h1 element to this variable when any h1 element is clicked.
You could include a script at the bottom of your Handlebars template and include the function there:
<script>
document.querySelector('h1').addEventListener('click', function() {
// Code here
})
</script>
Alternatively, there is a question here with a useful answer:
Passing a function into a Handlebars template
I have amended the answer from the above question to work with KeystoneJS:
In your view you can assign your function to locals:
locals.func = function () {
// Code here
};
Then you will need to create a Handlebars helper in /templates/views/helpers/index.js:
_helpers.stringifyFunc = function(fn) {
return new hbs.SafeString("(" + fn.toString().replace(/\"/g,"'") + ")()");
};
You can then use this in your Handlebars template:
<h1 onclick="{{ stringifyFunc func }}">{{ title }}</h1>
I hope this helps.
Using handlebars.js as a template engine I need to query a json file and from here loop through an array within an object in said file. The flow is intended to work like: If a value define via data-attribute on click matches a string within an object array then loop and show data based on those conditions.
Essentially I need a query it like I would back-end to a database,(Using Laravel) e.g:
$attr = "Red";
$prodcuts = DB::table('products')->Where('tags', $tag)->get();
I am working from a very large json file for this. For example sake I created a much smaller one if you need it reference sake:
https://gist.github.com/Panoply/15dc30a9fc598d07b24f0f13a5d42df4
I am aware that with handlebars.js I need to create a helper to make a string with a value:
Handlebars.registerHelper("ifValue", function(conditional, options) {
if (conditional !== options.hash.equals) {
return options.fn(this);
} else {
return options.inverse(this);
}
});
Then I would have my handlebars loop:
<ul>
{{#each products}}
{{#ifvalue tags equals='Blue'}}
<li>{{title}}</li>
<li>{{handle}}</li>
<li>{{tags}}</li>
{{#with images.[0]}}<li>{{src}}</li>{{/with}}
{{#with variants.[0]}}<li>{{price}}</li>{{/with}}
{{/ifvalue}}
{{/each}}
</ul>
Issue is I get a blank result. You can try these code block at http://tryhandlebarsjs.com/ – Considering that the tags are in an object array I might be having some conflict.
Figured it out. Handlebars is touchy. If you stumble across this, here is how I fixed it:
Handlebars:
{{#products}}
{{#inArray tags "Red"}}
{{title}}
{{#with images.[0]}}<img src="{{ this.src }}">{{/with}}
{{/inArray}}
{{/products}}
Helper (Create an inArray):
Handlebars.registerHelper('inArray', function(array, value, options) {
if (array.indexOf.call(array, value) >= 0) {
return options.fn(this);
} else {
return options.inverse(this);
};
});
I've noticed Marionette is very un-opinionated as far things go for the freedom they give you to choose a method to render data. It seems like there are a lot of ways to initially render a template with custom data
Returning a template with data:
template: function () {
var myTemplate = $('.someTemplate')
return _.template(myTemplate.html())({some: data});
}
Very similarly:
render: function () {
var template = this.getTemplate();
var html = Marionette.Renderer.render(template, {
model: this.model.toJSON(),
customData: this.customData
});
this.$el.html(html);
}
Serialize data:
serializeData : function () {
var customData = {some: 'data'};
var model = this.model.toJSON()
return _.extend(customData, model);
}
I've seen a lot of people in different code use variations of the first and the second. I personally prefer using serializeData but am curious: is there an advantage or use case where it would be appropriate to use the first two methods instead of serializeData?
The first case is not efficient - you are recompiling the template every time you want to render.
Anyway, your use case is exactly why Marionette has templateHelpers. Its the most concise way to provide extra data to the template while also passing serialized model.
So you would write:
templateHelpers : function () {
return {some: 'data'};
}
Or if its just static stuff:
templateHelpers: {some: 'data'}
More examples on how to use it here.
I think it's all about exploring natural behaviour of these things. Backbone View render is empty function by default. Marionette ItemView render extends Backbone's with this code.
It takes template by getTemplate method, by default it gives what is stored in template option. You can override getTemplate if you want to choose between several templates.
Then it collects data needed to be rendered by runing serializeData and extending it with templateHelpers. First one by default returns your model or collection toJSON method result, there you can prepare your data some way on every render. Second one is for helpers that will be calculated (if they are functions) if needed in template.
Template and data then go to Marionette.Renderer where just return template(data) by default happens. And then result can be attached to view's element.