Is there any way to dynamically load a template with onCreated method of Meteor.js? I have different template and one display area (main template).
<template name="main">
</template>
default loaded template
<template name="default">
</template>
Loaded templates via links
<template name="page1">
</template>
<template name="page2">
</template>
Is there a way I can use oncreated function to load the default, and remove (default) and load other template in the same main template when they are clicked?
Check out Template.dynamic.
It allows you to load a template only by its name. Easy thing here because you can let your template handle dynamic names by using a Reactive var or Reactive Dict.
Declare some templates:
<template name="main">
{{> Template.dynamic template=getTemplateName }}
<button class="loadTemplate" data-target="page1">Load Page 1</button>
<button class="loadTemplate" data-target="page2">Load Page 2</button>
</template>
<template name="page1">
</template>
<template name="page2">
</template>
<template name="default">
</template>
In your main template you can set in onCreated the default template name default:
Template.main.onCreated(function(){
this.state = new ReactiveDict();
this.state.set("targetTemplate", "default");
})
Get the template to load via helper:
Template.main.helpers({
getTemplateName() {
return Template.instance().state.get("targetTemplate");
},
})
And set the new template name by button click event:
Template.main.events({
'click .loadTemplate'(event, templateInstance) {
event.preventDefault();
const targetName = $(event.currentTarget).attr('data-target');
templateInstance.state.set("targetTemplate", targetName);
}
})
You can of course do this also by other events than only by clicking a button, since it depends on a ReactiveDict / ReactiveVar it will render the new template if the variable updates.
Note: Very important is to handle the data that is passed to the template, too. Once your dynamic template becomes more complex you have to be more aware of that, too.
Related
I have a parent template with several nested child templates inside it. And inside each nested template I have a string generated from an array. A simplified model of my html:
<template name="parent">
<a class="logId">Console log parent Id</a>
{{> firstChild}}
{{> secondChild}}
</template>
In addition my child templates have an each loop like this:
<template name="firstChild">
{{#each users}}
{{this}}
{{> adminSelectUser}}
{{/each}}
</template>
<template name="adminSelectUser">
<a class="selectUser">SelectUser</a>
</template>
In my js code:
Template.parent.events({
'click .logId': function () {
var eventId = this._id._str;
console.log(eventId);
});
Template.adminSelectUser.events({
'click .selectUser': function(template){
var userString = this.valueOf();
console.log(userString);
}
});
To this point I get it to work and in both of my templates I can find the id of my parent and the string of my user as I want it to do. But then I would also want to use both my variables eventId and userString in the same method call, called from the template adminSelectUser. Is there any way that I can pass the eventId found in the parent template down to the adminSelectUser template so that I can work with both variables with the same method call.
This is what I aim to do with my server methods:
Meteor.methods({
removeUser: function(){
Meets.update({_id: eventId}, {$pull : {usersApplied: userString}});
}
});
I want my server method to recognize the eventId and the userString and get them to work together when I call the method later on in the adminSelectUser template.
Hope this is clear enough!
There are several ways to go about this.
Using Template.parentData
You can traverse the template hierarchy from within template JS using Template.parentData(n) where n is the number of levels up in the hierarchy you want to go. So specifically for your example:
Template.adminSelectUser.events({
'click .selectUser': function(template){
var user = this;
var parentId = Template.parentData(2)._id;
myRemoveMethod(parentId, user);
}
});
However, this means that the inner template relies on always being rendered within the same hierarchy, which kinda has a bad smell. If the templates are tightly coupled and this is the case (which is likely) then using parentData is probably fine. However, if a template has data dependencies it does make sense to pass the appropriate data explicitly (see #2).
Pass the data into the templates explicitly.
<template name="firstChild">
{{#each users}}
{{this}}
{{> adminSelectUser user=this parent=..}}
{{/each}}
</template>
In your example, when you pass no argument to a template render, the data context is assumed to be this. Here instead we pass a user and a parent into the template (Note the use of .. here to reference this outside of the users loop). Your handler would then look more like:
Template.adminSelectUser.events({
'click .selectUser': function() {
var data = Template.currentData();
myRemoveMethod(data.parent._id, data.user);
}
});
I've just begun using the templates:tabs package for Meteor. By default, this generates a custom template that is inserted as {{#basicTabs tabs=tabs}}.
I have made a simple app to understand how this kind of custom template works, and in particular to understand how this is different from using {{> custom}}
HTML:
<body>
{{> parent1}}
{{> parent2}}
</body>
<template name="parent1">
<h1>Parent 1</h1>
{{> child}}
</template>
<template name="parent2">
<h1>Parent 2</h1>
{{#child}}
<h2>Stuff inside "child" tag</h2>
{{/child}}
</template>
<template name="child">
<button type="button">Child button</button>
</template>
JS:
if (Meteor.isClient) {
Template.child.events({
'click button': function (event, template) {
console.log("child", event, template)
}
});
Template.parent1.events({
'click button': function (event, template) {
console.log("parent1", event, template)
}
});
Template.parent2.events({
'click button': function (event, template) {
console.log("parent2", event, template)
}
});
}
I don't see any of the Stuff inside the "child" tag appearing. When I click on a button, I can see in the browser console that both the parent and the child templates can react to the input, but this happens in both cases.
How should I be using the {{#custom}} ... {{/custom}} syntax?
EDIT
Here are updated files that show how the {{#child}} block works, in connection with events and helpers:
<body>
{{> parent1}}
{{> parent2}}
</body>
<template name="parent1">
<h1>Parent 1</h1>
{{> child}}
</template>
<template name="parent2">
<h1>Parent 2</h1>
{{#child}}
<h2>Stuff inside "child" tag</h2>
{{else}}
<h3>Other stuff</h3>
{{/child}}
</template>
<template name="child">
{{extras}}
{{> Template.contentBlock}}
{{> Template.elseBlock}}
<button type="button">Child button</button>
</template>
JS:
if (Meteor.isClient) {
Template.child.events({
'click button': function (event, template) {
console.log("child", event, template)
}
});
Template.parent1.events({
'click button': function (event, template) {
console.log("parent1", event, template)
}
});
Template.parent2.events({
'click button': function (event, template) {
console.log("parent2", event, template)
}
});
Template.child.helpers({
extras: function () {
return "Child extras"
}
})
Template.parent1.helpers({
extras: function () {
return "Parent 1 extras"
}
})
Template.parent2.helpers({
extras: function () {
return "Parent 2 extras"
}
})
}
Output with Meteor 1.2.0.2:
Parent 1
Child extras [Child button]
Parent 2
Child extras
Stuff inside "child" tag
Other stuff
[Child button]
It seems that the only thing you're lacking here is Template.contentBlock, i.e.
<template name="child">
<button type="button">Child button</button>
{{> Template.contentBlock}}
</template>
You can think of it as a helper capable of rendering whatever the user of your child template puts within the {{#child}}..{{/child}} block.
Back to your question, the main difference between {{> child}} and {{#child}} is that the former has Template.contentBlock equal to null. For your information, there is also another helper Template.elseContentBlock which represents the part of markup placed after the {{else}} "tag", e.g.
{{#child}}
this content goes to Template.contentBlock
{{else}}
this content goes to Template.elseContentBlock
{{/child}}
You can use it to let your child template choose what it is going to render based on some context, as shown in the example here.
It was not directly pointed out in your question, but another thing to keep in mind is if you use the block syntax {{#child}}, then the block's content has access to your parent template's helpers and does not have access to the helpers of child template.
So I'm looking for some understanding on how templates and such work.
I've movie.html file with a template named movies in it, with a movie.js helper that returns a collection.
Movies Template
<template name="movies">
<li>{{title}}</li>
</template>
JS Helpers
Template.body.helpers({
movie: function () {
return Movies.find({});
}
});
Now I've another template that does a bunch of other things but one of them is to iterate over this list and display it.
List Template
<template name="list">
<ul>
{{#each movie}}
{{> movies}}
{{/each}}
</ul>
</template>
In this situation the list doesn't popular with the data.
However, if I move the contents of the list template outside of a template and just on the main.html it works great!
This is how I used to use it but I've started to use Houston Admin Package which uses Iron:Router so I've moved the main (and only) page to a template for routing purposes, which breaks my looping list.
I'm sure I'm missing something minor but I can't figure it out.
You are using the {{#each movie}} helper on the list Template so change the Template.helper to the list template
Template.list.helpers({
movie: function () {
return Movies.find({});
}
});
We are you calling this <template name="list"> on the body tag? you have something like this.
<body>
{{> list}}
</body>
Or you have something like this.
<template name="layout">
{{> yield}} <!-- since you are mentioning iron:route package -->
</template>
Or you have a route to that list template? localhost:3000/lists
Router.route('/movie-lists', function () {
this.render('lists')
});
On whatever of this 3 cases, you should point the helper into the template where you are calling it (in this case list template)
I'm developping an hybrid App based on Meteor with Meteoric (http://meteoric.github.io/). But I think the problem doubt I'm having is more general.
I have a template called AppLayout (AppLayout.html/AppLayout.js) used as a layout for the whole App. Basically it has the tabbar on it:
<template name="appLayout">
{{#ionBody}}
{{> ionNavBar }}
{{#ionNavView}}
{{> yield}}
{{/ionNavView}}
{{#ionTabs style="ios"}}
{{> ionTab title="Home" path="pictures" iconOff="ios-home-outline" iconOn="ios-home"}}
{{> ionTab title="Notificaciones" path=notificationsId iconOff="android-notifications-none" iconOn="android-notifications"}}
{{> ionTab title="Locales" path="places" iconOff="ios-world-outline" iconOn="ios-world"}}
{{> ionTab title="Mi Actividad" path="activity" iconOff="ios-list-outline" iconOn="ios-list"}}
{{> ionTab title="Perfil" path="profile" iconOff="ios-person-outline" iconOn="ios-person"}}
{{/ionTabs}}
{{/ionBody}}
</template>
My problem is that I need one of the ionTab icons to have a path that it's not constant.
{{> ionTab title="Notificaciones" path=notificationsId iconOff="android-notifications-none" iconOn="android-notifications"}}
To do so, I have the variable "notificationsId" declared in the helpers section of the Template:
Template.appLayout.helpers({
notificationsId : function() {
var id = "0";
if(Meteor.user()){
id = Meteor.user()._id;
}
return "notifications/"+id;
}
});
But it doesn't work and I can't find a way to pass a variable to the ionTab helper.
This example seems to match the pattern you're using, namely a helper that returns a variable which is then used as a named parameter for a template.
I'm having trouble understanding exactly what is available as my context in a template invoked by Meteor with Iron-Router – and how these things inherit.
Here are all the potential sources for "stuff I can refer to inside double curly braces" that I can think of:
Built-in helpers
Handlebars.registerHelper(...)
Template.myTemplate.myVar/myHelper = ...
Template.myTemplate.helpers({ ... })
data: { ... } inside route (Router.map)
Something to do with #each?
Something to do with #with?
Have I forgotten anything? Are there any global variables?
I guess I'm a bit confused about what the standard way is of giving the template a context in the first place. Also about what happens inside control structures such as #each and #with.
A clarification would be great!
IronRouter renders your template with the result of RouteController.data as the current data context.
<template name="viewPost">
<div>
<h1>{{title}}</h1>
<p>{{content}}</p>
</div>
</template>
var PostsController=RouteController.extend({
template:"viewPost",
waitOn:function(){
return Meteor.subscribe("postsById",this.params._id);
},
data:function(){
return Posts.findOne(this.params._id);
}
});
this.route("viewPost",{
path:"/posts/:_id",
controller:PostsController
});
In this example, IronRouter will render the "viewPost" template with the post having this.params._id as data context.
What is the standard way of giving a template a context in the first place ?
There is 2 ways :
{{#with myContext}}
{{> myTemplate}}
{{/with}}
{{> myTemplate myContext}}
As you can see, the #with control structure sets the current data context.
The #each structure iterates over a Cursor (or an Array) and sets the current data context to the current fetched document (or the current cell).
<template name="postsList">
{{#each posts}}
<h1>{{title}}</h1>
{{/each}}
</template>
Template.postsList.helpers({
posts:function(){
// cursor
return Posts.find();
// array
return [{title:"Title 1"},{title:"Title 2"},{title:"Title 3"}];
}
});
UPDATE : Could you possibly add a note about inheritance ? For instance, if I have nested #each blocks, do variables cascade ?
I came up with this example :
<template name="parent">
<ul>
{{#each parentContexts}}
{{> child}}
{{/each}}
</ul>
</template>
<template name="child">
<li>
<ul>
{{#each childContexts}}
{{> content}}
<p>../this.parentProperty = {{../this.parentProperty}}</p>
{{/each}}
</ul>
</li>
</template>
<template name="content">
<li>
<p>this.childProperty = {{this.childProperty}}</p>
<p>this.parentProperty = {{this.parentProperty}}</p>
</li>
</template>
Template.parent.helpers({
parentContexts:function(){
return [{
parentProperty:"parent 1"
},{
parentProperty:"parent 2"
}];
}
});
Template.child.helpers({
childContexts:function(){
return [{
childProperty:"child 1"
},{
childProperty:"child 2"
}];
}
});
If you run this example, you'll notice that you can't access the parentProperty in "content" because the default #each helper OVERRIDES the parent data context with the new context provided.
You can access the parentProperty in the nested #each block using this syntax : ../this.parentProperty, which is reminiscent of the UNIX parent directory access syntax.
However you cannot use this syntax in the "content" template, because it is agnostic of the nested each structures it was called from : you can only use the ../../parent syntax in a template where the actual nesting happens.
If we want to access the parentPropery in the content template, we must augment the current data context with the parent context.
To do so, we can register a new #eachWithParent helper like this :
Handlebars.registerHelper("eachWithParent",function(context,options){
var parentContext=this;
var contents="";
_.each(context,function(item){
var augmentedContext=_.extend(item,parentContext);
contents+=options.fn(augmentedContext);
});
return contents;
});
Now if you replace the nested #each with this new helper, you will have access to parentProperty in "content".