Meteor templates accessing subtemplate helper functions - meteor

Is it possible for a parent Meteor template to access a subtemplate directly? Ideally, I'd like to use templates a widgets with an API. I was hoping for something like this:
mypage.html
<template name="myPage">
<div class="widgetContainer"></div>
<button>submit</button>
</template>
mypage.js
Template.myPage.rendered = function(){
this.myWidgetInstance = UI.render(Template.myWidget)
UI.insert(this.myWidgetInstance, $('.widgetContainer')[0]);
}
Template.myPage.events({
'click button': function(e, template){
// I don't want this logic to live inside of mywidget.js.
// I also don't want this template to know about the <input> field directly.
var val = template.data.myWidgetInstance.getMyValue();
}
});
mywidget.html
<template name="myWidget">
<input></input>
</template>
mywidget.js
Template.myWidget.getValue = function(){
return this.$('input').val();
}
The above doesn't work because myWidgetInstance.getMyValue() doesn't exist. There doesn't appear to be a way for external code to access template helper functions on an instance.
Is anyone using Meteor templates in the way I'm trying to use them above? Or is this better suited for a separate jQuery widget? If so, it'd be a shame because I still want my widget template to benefit from the features Meteor provides.

It is possible to access subtemplate helper function.
Your example will work once you apply few fixes:
fix 1 : getValue() instead of getMyValue()
Template.myPage.events({
'click button': function(e, template){
// I don't want this logic to live inside of mywidget.js.
// I also don't want this template to know about the <input> field directly.
var val = template.myWidgetInstance.getValue();
console.log(val);
}
});
fix 2 : $('input').val(); instead this.$('input').val();
Template.myWidget.getValue = function(){
return $('input').val();
}
fix 3 : <input> should have no close tag.
<template name="myWidget">
<input type="text" value="sample value">
</template>

Related

Meteor - Reloading template section after variable change

i want to refresh/reload a part of my template after a variable change so that if the variable is true it shows a content A or else it will show content B. I'm sure this is a quite simple question but i'm having troubles on finding the solution.
Something like this:
Template.x.created = function() {
this.variable = false;
}
Template.x.helpers({
'getValue': function(){
return this.variable;
}
});
Template:
<template name="x">
{{#if getValue}}
<content A>
{{else}}
<content B>
{{/if}}
</template>
You need to create a reactive data source to get the template helper to re-run when the variable changes, as a normal variable won't let the helper know when it changes value. The simplest solution is to use ReactiveVar:
Template.x.onCreated(function() {
this.variable = new ReactiveVar(false);
});
Template.x.helpers({
'getValue': function() {
// Note that 'this' inside a template helper may not refer to the template instance
return Template.instance().variable.get();
}
});
If you need to access the value somewhere outside this template, you can use Session as an alternative reactive data source.
#Waiski answer is a good one, but I want to share a simple Template helper I build because a lot of Templates need this:
Using registerHelper you can build a global helper like so:
Template.registerHelper('get', function (key) {
let obj = Template.instance()[key]
return (obj && obj.get) ? obj.get() : obj
})
Use it in every template:
Template.x.onCreated(function() {
this.foo = new ReactiveVar(true)
this.bar = new ReactiveVar('abc')
})
Html:
{{#let foo=(get 'foo')}}
{{#if get 'bar'}}
Bar is true. Foo: {{foo}}
{{/if}}
{{/let}}

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).

Best way to prevent a template helper to be rerun when it is unnecessary?

I'm trying to prevent a template helper to be rerun when it is unnecessary. I made a simple application to illustrate this behavior:
Let's say I want to display some items that contain only a title and a description.
<template name="Tests">
{{#each items}}
{{> TestsItems}}
{{/each}}
</template>
<template name="TestsItems">
<div class="title">{{title}}</div>
<div class="description">{{description}}</div>
</template>
I have autopublish enabled.
Template.Tests.helpers({
items: function () {
return Items.find();
}
});
Template.TestsItems.helpers({
description: function () {
// I'm using this helper to do some updates
// on a jQuery plugin when the description field change.
// see example 1: https://github.com/avital/meteor-ui-new-rendered-callback/
console.log("The description is run");
return this.description;
}
});
When a new update is made on the title field only, you can see that the description helper is rerun. What I'm trying to achieve is to only rerun this helper when there is a new value for the description field and not every time a field has changed in the document.
As {{#constant}} and {{#isolate}} are deprecated, how can I get this behavior in the latest Meteor versions?
Note 1: Create a new subtemplate including the description does not fix the problem.
I would avoid side effects in template helpers. Instead I would use an autorun:
Template.TestItems.rendered = function () {
var _id = this.data._id;
this.autorun(function () {
// Select only the description field, so that we only
// trigger a re-run if the description field changes
var description = Items.findOne(_id, {fields: {description: 1}}).description;
// update the JQuery plugin
});
}

Can I pass the this._id value from one template helper to another with Meteor?

I have the following templates (.html) with their respected managers (.js files):
adminManageCategories
adminAddCategory
adminUpdateCategory
Consider the following:
<template name="adminManageCategories">
{{#each category}}
<div class="clickme">{{title}}</div>
{{/each}}
{{> adminUpdateCategory}}
</template>
Notice the {{> adminUpdateCategory}} is outside of the iteration. This is also a form, and I want to keep it on the same page.
And admin_manage_categories.js
Template.adminManageCategories.events({
"click .clickme": function(event) {
event.preventDefault();
console.log(this._id);
}
});
Notice the console.log() function, which works, as the template manager is smart enough to know the ID of the item that was clicked.
What I want to do is load this items values into the form when clicked. My example above is slim, but in my real data I have a title, sort order, among other things.
So my question is, what is the proper way to pass the _id from the adminManageCategories template to the adminUpdateCategory template, which is the form?
I can hack at this with JavaScript and make things happen, but I think I'm missing a "Meteor way" of doing things.
You need to use a ReactiveVar to store the currently clicked item.
First you need to run meteor add reactive-var, as it's not a package added by default in a standard meteor web app.
JS:
Template.adminManageCategories.created=function(){
// instantiate the reactive-var in the created callback
// we store it as a property of the template instance
this.currentItemId=new ReactiveVar(null);
};
Template.adminManageCategories.helpers({
// this helper reactively returns the currently clicked item
currentItem:function(){
// retrieve the reactive-var from the template instance...
var currentItemId=Template.instance().currentItemId.get();
// ...to fetch the correct collection document
return Items.findOne(currentItemId);
}
});
Template.adminManageCategories.events({
"click .clickme": function(event,template) {
event.preventDefault();
// assign the correct item id to the reactive-var attached to this template instance
template.currentItemId.set(this._id);
}
});
HTML:
<template name="adminManageCategories">
{{#each category}}
<div class="clickme">{{title}}</div>
{{/each}}
<p>Current item title is : {{currentItem.title}}</p>
{{! pass the currentItem as a parameter to your child template this will be
accessible as {{item}} in the HTML and "this.item" in JS helpers or
"this.data.item" in created/rendered/destroyed callbacks}}
{{> adminUpdateCategory item=currentItem}}
</template>
EDIT:
When I initialize the reactive-var in the created callback, I set it to null, this means that until one item is clicked, the helper will return null too and when you'll try to access this.item._id in the adminUpdateCategory this will fail.
The simplest way to solve this issue is maybe to not initialize the variable to null but to the first item in the collection.
Template.adminManageCategories.created=function(){
var firstItem=Items.findOne({},{
sort:{
sortedField:1
}
});
this.currentItemId=new ReactiveVar(firstItem && firstItem._id);
};
There may still be a case when you have 0 items in the collection, so you'll probably end up having to guard against the existence of the item in the JS.
Template.adminUpdateCategory.helpers({
itemProperty:function(){
return this.item && this.item.property;
}
});

Meteor with Typeahead: where to call typeahead() on input element?

I'm using Meteor with Iron Router, and can't seem to get typeahead (this version: https://github.com/bassjobsen/Bootstrap-3-Typeahead) to work.
Here's some code:
HomeController = RouteController.extend({
//....
after: function () {
var tags = this.getData().tags;
console.log(tags);
if(tags.length > 0) {
var tags = ['hello', 'world'];
console.log("Adding typeahead for tags to ", $('.input-search')[0]);
console.log("tags: ", tags);
$('.input-search').typeahead({
source: tags,
updater: function(item) {
Router.go('/projects/tag/' + item);
}
});
}
},
I have a header that's part of the application layout, and has an input like this:
<input type="text" class="form-control input-search" data-provide="typeahead" placeholder="Search">
The jQuery in the after: function gets the input correctly. But calling typeahead on the input doesn't seem to activate typeahead properly: when typing in the input, nothing happens.
However if I wrap the typeahead call in a setTimeout, it does work.
Of course, whenever you start wrapping things in setTimeouts, something isn't right.
Where/when is the correct place to initialise typeahead when using Iron Router?
You can initialize the typeahead in the rendered function of your template. For instance:
Template.mytemplate.rendered = function() {
$('.input-search').typeahead({
source: tags,
updater: function(item) {
Router.go('/projects/tag/' + item);
}
});
};
I figured it out.
You need to make sure any inputs used by plugins are "preserved", i.e. not re-rendered, by Meteor. The simplest way to do this is to make sure the input has an ID attribute. So changing my search input to this fixed it:
<input type="text" id="input-search" class="form-control" data-provide="typeahead" placeholder="Search">
Relevant documentation: http://docs.meteor.com/#template_preserve
As of Meteor 1.0.3.1 and iron:router#1.0.7 working solution is to install sergeyt:typeahead and init typeahead like this:
Template.MyTemplate.rendered = function () {
Meteor.typeahead.inject();
}
Init may be done only once for top-level template.
I wrote an article about this, you can read it here.
In summary, your implementation using javascript to select and alter the element is bad practice in a reactive environment. I've tried it that way and it's a huge pain.
What I found is that you can create a helper that returns a JSON string of your typeahead data and use the data-source attribute, like so
JS
Template.myHelper.helper({
typeahead : function(){
return JSON.stringify(Session.get("typeahead"));
}
});
HTML
<template name="myTemplate">
<input data-source="{{typeahead}}" data-provide="typeahead" id="blah" type="text" />
</template>

Resources