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

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.

Related

How would I access a field in a collection whos' key contains a space?

Data was imported from a csv into mongo. No practical to remove the space in the data.
Example:
{{#each item}}
{{name}}
{{"Transaction Number"}}
{{/each}}
The "Transaction Number" is the field name. Can place into a helper, but would like to use the #each.
Template.hello.helpers({
item: function() {
return Items.find()
},
counter: function () {
return Session.get('counter');
}
});
Thank you for any help.
Maybe a transform could be helpful here, although ultimately you want to fix the field key to be valid IMO.
Items.find({}, {transform: function(doc) {
doc.transactionNumber = doc['Transaction Number'];
return doc;
}});
Then you should be able to access it within the template correctly:
{{#each item}}
{{transactionNumber}}
{{/each}}
You can specify new variables as doc.whateverYouLike which can be quite useful when doing 'aggregate' queries like adding a variable that represents a field from another collection.
You could rely on this trick :
JS
Template.hello.helpers({
transactionNumber: function(){
return this["Transaction Number"];
}
});
HTML
{{#each item}}
{{name}}
{{transactionNumber}}
{{/each}}

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

In iron router, how to avoid recompute the entire data field when only a subset is changed

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>

Meteor template.rendered and this.data access

I have a template
<template name='order'>
{{vendor.name}}
</template>
rendered with
Template.order.vendor = function () {
return {name: 'Chanel', address: 'Paris' };
};
When I try to access this.data in
Template.order.rendered = function () {
console.log(this.data);
};
I get 'undefined'.
What is the correct way of getting e.g. vendor.name and vendor.address in Template.order.rendered?
Thank you.
In Template.rendered, this.data corresponds to the data the template was "fed" with, either as a parameter or using the {{#with}} construct.
vendor is just a helper function returning data available in the order template, but not binded to "this.data".
SO to solve your problem, you have a number of options :
Defining a parent template and moving the vendor helper to this parent, then you can alternatively call order with vendor as a parameter or use a {{#with block}}
<template name="parent">
{{> order vendor}}
{{#with vendor}}
{{> order}}
{{/with}}
</template>
<template name="order">
{{name}}
</template>
Template.parent.vendor=function(){
return{
name:"Chanel",
address:"Paris"
};
};
Template.order.rendered=function(){
// this.data == vendor object returned in parent helper
console.log(this.data);
};
You can also register a global helper, eliminating the need for an encapsulating parent template :
Handlebars.registerHelper("vendor",function(){
return{
name:"Chanel",
address:"Paris"
};
});
Template.order.rendered = function () {
console.log(Template.order.vendor());
}

Meteor: Passing more than one value to a template

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"
}

Resources