Meteor: Passing more than one value to a template - meteor

I want to create a template/js combo similar to the ones below. What I would like is to have two variables available to the 'collection' template
<template name="collection">
Title: {{title}}
<UL>
{{#each items}}
{{> item}}
{{/each}}
</UL>
</template>
<template name="collection_items">
<LI>{{item_title}}</LI>
</template>
Where the javascript function would be something like:
Template.collection.data = function() {
var record = Record.findOne({whatever:value});
return { title: record.title, items: record.items }
}
I've tried using Handlebars' {{#with data}} helper and return an object as above, but that just crashed the template. I've tried creating a 'top level' function like:
Template.collection = function () {... }
but that also crashed the template.
What I'm trying to avoid is having two separate functions (one Template.collection.title, and one Template collection.items) where each of them calls a findOne on the Record collection where really its the same template and one call should suffice.
Any ideas?

Template.collection = function () {... }
Template.collection is not a function, it's an instance and thus an object.
You can type Template.collection in the console to see something essential as well as Template.collection. and autocomplete that to see its methods and fields.
For a #with example, the Todos indeed doen't seem to contain one as you have outlined in your comments. So, an example use of it can be found here:
https://github.com/meteor/meteor/blob/master/packages/templating/templating_tests.js#L75
https://github.com/meteor/meteor/blob/master/packages/templating/templating_tests.html#L92
Here is another example that I tried that works on both the current master and devel branch:
<head>
<title>test</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
{{#with author}}
<h2>By {{firstName}} {{lastName}}</h2>
{{/with}}
</template>
And the JS part of it:
if (Meteor.is_client) {
Template.hello.author = function () {
return {
firstName: "Charles",
lastName: "Jolley"
};
};
}

Any specific reason why you're hoping to avoid two functions?
From your code sample I see one issue: the first template is calling a second template with this line:
{{> item}}
But your second template is not called 'items'. I believe that your second template should be called this way:
<template name="item">
Seems that it would be simple enough to have helper functions for the first and the second. Although I haven't gotten it to work with my own code, I believe the second helper function would want to use the 'this' convention to refer to the collection you're referring to.
Cheers - holling

Tom's answer is correct. I want to just chime in and add that in my scenario the reason why #with was failing was because due to the 'reactive' nature of meteor my first call to load the model resulted in 'undefined' and I didn't check for it. A fraction later it was loaded ok.
The moral is to do something like
var record = Record.findOne({whatever:value})
if (record) {
return record;
} else {
// whatever
return "loading"
}

Related

Meteor {{#if}} helper if object or field exists

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.

Using a helper with arguments AS a helper argument in Spacebars

So I am trying use a helper as an argument of another helper in Spacebars. In the example below, 'getResultInfo' is a helper that gets data specific to the arguments passed, and 'formatResult' is a helper that formats its result and the results of other helpers.
<template name="example">
{{#each collectionResults}}
Label: {{formatResult getResultInfo this._id 'text'}}
{{/each}}
</template>
The issue I'm having is that Spacebars thinks that the arguments for 'getResultInfo' are the just second and third arguments for 'formatResult'. I'd really like to keep the helper's functions separate (ie. not having to format the result at the end of the 'getResultInfo' and every other helper that I have). Is there any alternate syntax or method of doing what I'm trying to achieve?
I think that you cannot chain two helpers with parameters on the second one like you did. Subsequent parameters will still be interpreted as parameters from the first helper.
I see two ways for solving this problem:
1) you could get rid of the parameters and use the data context provided by each.
each bind the data context to this so in getResultInfo you could just use this._id directly. But you have to remember that you need this data context each time you use this helper. And this pose a problem with the 'text' parameter which does not depend from the data context.
2) you could create a function corresponding to your getResultInfo helper and use it directly in the formatResult helper like this:
getResultHelper = function(id, text) {
//do what you want
};
//bind the function to your getResultInfo (example with a global helper)
Template.registerHelper('getResultInfo', getResultHelper);
//use the function in your template helper
Template.example.helpers({
formatResult: function(format) {
return getResultHelper(this._id, format);
}
});
//and finally your template
<template name="example">
{{#each collectionResults}}
Label: {{formatResult 'text'}}
{{/each}}
</template>
FYI, this is now possible in Meteor 1.2, via Spacebars sub expressions in Blaze.
<template name="example">
{{#each collectionResults}}
Label: {{formatResult (getResultInfo this._id 'text')}}
{{/each}}
</template>

Why this works: Meteor-Blaze #with and data context?

I wonder why the following thing outputs 'hello' instead of 'bye'???
Template:
<template name="example">
{{#with dataContext}}
{{say}}
{{/with}}
</template>
Template Helper:
Template.example.helpers({
dataContext: function() {
return {
say: 'bye'
};
},
say: function() {
return 'hello';
}
});
(Meteor 1.1.0.2)
The shortest answer to this is the helpers have a preference over the data context.
If you rename one of them to something else it should solve your problem.
The order the lookup goes is:
The data context (if it contains a .). {{say}} does not.
The template's helper. {{say}} has a helper for say.
A template
A global helper such as those defined with Template.registerHelper.
The data context
So if the first isn't found, it goes down the list until it finds something
[1]https://github.com/meteor/meteor/blob/90b356061ff2464f11749dc8b43d1a139b233980/packages/blaze/lookup.js#L100-L139

Is there are more elegant way to walk template nesting?

I'm trying to access a parents data context
To get to it, I have a line that looks like :-
template.view.parentView.parentView.parentView.parentView.dataVar.curValue
Which in terms of UI, I have
template[dataIwant] renders another template with a modal dialog which uses autoform
I then use an autoform hook to get a before save event, which I want to use to add an extra value to the document being saved.
I then walk the template that's passed in the hook back to the top template. Seems like I should be able to do this in a more elegant way?
Came up with this code today because I needed it also :
_.extend(Blaze.View.prototype,{
closest: function(searchedViewName){
currentView = this;
while (currentView && currentView.name != searchedViewName){
currentView = currentView.parentView;
}
return currentView;
}
});
<template name="parent">
{{> child}}
</template>
Template.parent.created = function(){
this.reactiveVar = new ReactiveVar(false);
};
<template name="child">
{{parentName}}
{{parentVar}}
</template>
Template.child.helpers({
parentName:function(){
return Template.instance().view.closest("parent").name;
},
parentVar:function(){
return Template.instance().view.closest("parent")._templateInstance.reactiveVar.get();
}
});
So far so good, but I've already spotted use cases where this won't work (using Template.contentBlock in your template definition is breaking the whole thing for some unknown reason).

Pass data to a dynamic template

With meteor updates up to 0.8 my old code stopped working.
Handlebars.registerHelper('getTemplate', function(id, context) {
return Template[id](context);
});
<template name="main">
....
{{{getTemplate templateName context}}}
....
</template>
//somewhere in other template
Template.main.context = {name:value};
This way I was able to render a custom template with custom data. Now I can't find the way to pass context to the dynamic template. With blaze both templateName and context is undefined. Any advice?
Meteor >= 0.8.2
You can use the UI.dynamic helper render a template with a context which are both specified dynamically. For more details, check out this issue.
Meteor < 0.8.2
Both of these issues are addressed on this page in the meteor wiki.
Handlebars.registerHelper is now UI.registerHelper as seen here.
Examples of how to dynamically render templates are shown here.
update
Actually, given the requirements, a solution doesn't seem very obvious to me. If you are willing to use session variables to set the template name and context, AND only have one dynamic template in your main template. You could do something like this:
<body>
{{> main}}
</body>
<template name="main">
{{> getTemplate context}}
</template>
<template name="dogs">
<p>There are {{animals}} dogs!</p>
</template>
<template name="cats">
<p>There are {{animals}} cats!</p>
</template>
Session.setDefault('templateName', 'dogs');
Session.setDefault('templateContext', {animals: 10});
Template.main.getTemplate = function() {
return Template[Session.get('templateName')];
};
Template.main.context = function() {
return Session.get('templateContext');
};
This was brought up on the meteor-core list and #dgreensp, MDG core dev working on Blaze, opened Ticket #2007 - How to render a template to HTML with data so they definitely know about this and I'd expect a fix to land soon after 0.8.0.
He also included the following workaround:
var toHTMLWithData = function (kind, data) {
return UI.toHTML(kind.extend({data: function () { return data; }}));
};
The github ticket has further discussion and alternate code snippets that you may find useful.

Resources