In react-template-helper, is there a way to pass an argument into the component?
something like:
{{> React component=view 'hello'}}
Template.Home.helpers({
view: function (bar) {
console.log(bar) // logs 'hello'
...
return someComponent;
}
})
Thanks
Related
How can I render two different views in a one page app without changing URLs. I'm using meteor with the default blaze as well as the flow:router package. Right now I have it set up like this:
routes.js..
FlowRouter.route("/", {
name: "App.home",
action() {
BlazeLayout.render("App_body", {
main: "App_home",
mainContent: "calendar"
});
}
});
FlowRouter.route("/list", {
name: "App.list",
action() {
BlazeLayout.render("App_body", { main: "App_home", mainContent: "list" });
}
});
but this way I'm using the url /list and i dont want that. I would like to simply render an alternate component template in the same url. I'm very new to coding so forgive me if this is obvious. Essentially I just want two different view styles: a list and a calendar. So I would like a way to set it up so that a spacebars template can be rendered if a certain button is clicked, and a different one can be rendered instead if another button is clicked.
Thanks so much for any help, i've been at this for a couple of days :)
Create another template, which renders particular view conditionally. Something like this:
FlowRouter.route("/", {
name: "App.home",
action() {
BlazeLayout.render("App_body", {
main: "App_home",
mainContent: "listOrCal"
});
}
});
<template name="listOrCal">
{{#if showList}}
{{> list}}
{{else}}
{{> calendar}}
{{/if}}
<button id="switchView">Switch view</button>
</template>
Template.listOrCal.onCreated(function listOrCalOnCreated() {
this.showList = new ReactiveVar(true);
})
Template.listOrCal.helpers({
showList() {
return Template.instance().showList.get();
}
})
Template.listOrCal.events({
'click #switchView' {
let showList = Template.instance().showList.get();
Template.instance().showList.set(!showList);
}
})
You can handle this within a single template like so:
FlowRouter.route('/', {
name: 'App.home',
action() {
BlazeLayout.render('App_body', { main: 'App_home', mainContent: 'ListOrCalendar' });
}
And then the ListOrCalendar template would look like this:
{{#if displayList}}
{{> List}}
{{else}}
{{> Calendar}}
{{/if}}
<button>Switch</button>
You would set up a ReactiveVar in the ListOrCalendar template:
Template.ListOrCalendar.onCreated(function() {
const instance = this;
instance.displayList = new ReactiveVar(true);
});
See ReactiveVar explanation here (ignore Session)
Then you would have a helper which returns the value of your ReactiveVar:
Template.ListOrCalendar.helpers({
displayList() {
const instance = Template.instance();
return instance.displayList.get();
}
});
Finally, you would hook up an event to change the value of displayList to switch between templates:
Template.ListOrCalendar.events({
"click button"(event, instance) {
const displayListCurrent = instance.displayList.get();
const displayListNew = !displayListCurrent;
instance.displayList.set(displayListNew);
// or, more concisely, instance.displayList.set(!instance.displayList.get());
}
});
So, in summary:
When the template is created, your ReactiveVar is true
so your displayList returns true
so the #if displayList condition in the template is satisfied
and so the List template is displayed
When the button is clicked
The ReactiveVar is set to false
so the displayList helper returns false
so the #if displayList condition in the template is not satisfied and it goes to the else statement
and so, finally, the Calendar template is displayed
When the button is clicked again, the ReactiveVar is toggled back to true, and on we go as above
This might seem daunting or over-complicated, but there's nothing fancy going on here at all. You'll get used to it pretty quickly
How to get attribute value from tag like width, color, value ...
<template>
{{#if body.width > 979px }}
{{> tmp1 }}
{{else}}
{{> tmp2 }}
{{/if}}
</template>
<template name="tmp1">...</template>
<template name="tmp2">...</template>
You can't access tag attributes directly from Spacebars template. You need to create a template helper for that.
Template.templateXY.helpers({
bigBody: function() {
return $("body").width() > 979;
}
});
then you use it like this:
<template name="templateXY">
{{#if bigBody}}
{{> tmp1}}
{{else}}
{{> tmp2}}
{{/if}}
</template>
UPDATE: for the helper to recompute on window resize event, you need to modify it a little bit. You can use a Dependency object for this.
Template.templateXY.onCreated(function() {
// create a dependency
this.resizeDep = new Dependency();
});
Template.templateXY.onRendered(function() {
let tmpl = this;
// invalidate the dependency on resize event (every 200ms)
$(window).on("resize.myEvent", _.throttle(function() {
tmpl.resizeDep.changed();
}, 200));
});
Template.templateXY.helpers({
bigBody: function() {
// recompute when the dependency changes
Template.instance().resizeDep.depend();
return $("body").width() > 979;
}
})
Template.templateXY.onDestroyed(function() {
$(window).unbind("resize.myEvent");
});
Other possible solution would be to store the window width to a ReactiveVar (which is a reactive data source itself) and use .on("resize", fn) to change the ReactiveVar
After some researches I found that there's no spacebars proper solution and the best option is to use js code.
So here's the code:
Session.set("width", $(window).innerWidth());
window.onresize = function () { Session.set("width", $(window).innerWidth()); };
if(Meteor.isClient) {
Template.body.helpers({ 'dynamicTemplateName': function () { return Session.get("width"); } });
}
I am looping through documents in a template with Blaze spacebars to create a list
<template name="objectTemplate">
{{#if checkIfObjectExists}}
({{document.[0].object.object1}})
{{/if}}
</template>
I know that in some documents, some objects do not exist in that object position. normally if I didnt have (), it would be blank and I could move on, but in this case when empty, I will have a lot of () which is not good.
I created a helper, but its not working. I have tried null, 0, typeOf etc and still cant get it right. Anyhow here is the helper
Template.objectTemplate.helper ({
checkIfObjectExists: function() {
if (this !== 'null') {
return true;
} else {
return false;
}
}
});`
You can use _.has(object, key) if you want to check if the object document.[0].object has the property object1 set. The function _.isObject(value) will check instead if document.[0].object.object1 is an Object (this also includes arrays).
So, depending on your requirements, your template helpers should look like this:
Template.objectTemplate.helper({
checkIfObjectPropertyExists: function() {
return _.has(this.document[0].object, "object1");
},
checkIfPropertyIsObject: function() {
return _.isObject(this.document.[0].object.object1);
}
});
You could also register an Underscore.js global template helper and then use it directly in your Meteor templates:
Template.registerHelper('_', function () {
return _;
});
<template name="objectTemplate">
{{#if _.has this.document.[0].object 'object1'}}
({{document.[0].object.object1}})
{{/if}}
</template>
Your if is not in the right place. Your objectTemplate is probably called that way :
{{#each datum in data}}
{{>objectTemplate data=data}}
{{/each}}
So it's always rendered. Even if the datum is empty. The this you check in your helper will always be true, it's the template himself.
So, you should call it that way :
{{#each datum in data}}
{{#if datum.thingToTest}}
{{>objectTemplate datum=datum}}
{{/if}}
{{/each}}
The entire sub template won't be called.
I have the following helper
Template.meetingRow.helpers({
isOwner: () => {
console.log(this);
return Meteor.userId() === this.owner;
}
});
which will log the Window object to the console.
The this object returns the correct object in Template.meetingRow.events, and uses the correct values in the template, but isn't correct in the helper. Am I misunderstanding something?
Here's how it's being instantiated in its parent template:
<ul class="list-group">
{{#each meetings}}
{{ > meetingRow }}
{{/each}}
</ul>
It's because in ES2015, arrow functions inherit the context of the surrounding function, rather than being given their own. What this means is that Meteor can't bind a context to your helper function if it's defined this way.
Solution (in ES2015 style):
Template.meetingRow.helpers({
isOwner() function {
console.log(this);
return Meteor.userId() === this.owner;
}
});
More here under "Lexical this".
In Iron-router, we can pass the data to a page in the data field. For example:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate',
data: function () {
return {
title: getTitle(),
description: getDescription(),
}
}
});
});
In the template, title and description are actually some value passed to subtemplates:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
Since the data field in the iron-router is reactive, whenever a session variable change, the data field is recalculated.
In this case, however, the session variable in getTitle function only changes the template "titleTemplate", and the session variable in getDescription() function only changes the template "descriptionTemplate".
If the session variable in the getTitle() function changes, I would like to only execute the getTitle() function, and do not execute the getDescription() function. If possible, I would also like to only render the "titleTemplate" and do not render "descriptionTemplate".
I wonder whether that is possible. If this is not the right way of writing the Meteor application, what is a better way to do it?
Thanks.
This is an interesting situation. Despite the fact that the getTitle and getDescription functions may be dependent on completely different reactive variables, they will both be recomputed whenever either one of them changes.
One possible solution is to pass the functions themselves instead of the result of calling the functions. That may or may not be convenient depending on how they are used in the sub-templates, but it will prevent them from both being run every time. Here is a simple example:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>
<template name="titleTemplate">
<p>{{excitedTitle}}</p>
</template>
<template name="descriptionTemplate">
<p>{{this}}</p>
</template>
var getTitle = function() {
console.log('computed title');
return Session.get('title');
};
var getDescription = function() {
console.log('computed description');
return Session.get('description');
};
Router.map(function() {
this.route('home', {
path: '/',
template: 'myTemplate',
data: function() {
return {
title: getTitle,
description: getDescription
};
}
});
});
Meteor.startup(function() {
Session.setDefault('title', 'title1');
Session.setDefault('description', 'description1');
});
Template.titleTemplate.excitedTitle = function() {
return "" + (this.toUpperCase()) + "!";
};
From the console you can change the session variables (title and description) and you will see that only one function will be run at a time. I hope that helps.
One way to solve this is to not use the data context, but just use template specific helpers. Since I don't know what your getTitle and getDescription function do, I can't tell whether that is an option for you. It depends on whether you need to use the this object in those functions and need this to refer to the route object or not. If not, then the following seems like the better solution:
JS:
Router.map(function () {
this.route('myroute', {
path: '/route',
template: 'myTemplate'
});
});
Template.myTemplate.title = getTitle;
Template.myTemplate.description = getDescription;
HTML:
<template name="myTemplate">
{{> titleTemplate title}}
{{> descriptionTemplate description}}
</template>