meteor,Dynamically change template helper data - meteor

I have a small doubt,Is it possible to change the template helper data dynamically.
Here is my template
{{#each post}}
<h4>{{postyname}}</h4>
<h4>{{Headline}}</h4>
<h4>{{Location}}</h4>
{{/each}}
and this is my helper data
post:function()
{
posts.find({},{sort: {createdAt: -1}}).fetch();
}
and it is displaying results in my homepage and i have search bar at the top of this page and whenever user clicks search button the same template must be render but with different data depend on user search.
I've tried like this on my onclick event,looks like it is not working
'onclick #searchBtn':function()
{
var all_posts=posts.find({'Headline': search},{sort: {createdAt: -1}}).fetch();
Template.postDisplay.post=function(){
return all_posts;
}
How to do this?

I believe using Dep Dependency is what you want to use:
Create a variable on the top of your js file
var _deps = new Tracker.Dependency;
var searchCriteria = {};
In your helper:
post:function() {
_deps.depend(); // creates a dependency between the accessor of "post" and the _deps
posts.find(searchCriteria,{sort: {createdAt: -1}}).fetch();
}
On your button click:
'onclick #searchBtn':function() {
searchCriteria = {'Headline': search};
_deps.changed(); // notifying everyone that is dependent on _deps that it has changes
}
Based on what I see from your question I believe this should handle your situation. Let me know if I misunderstood something in your problem.

Why not just use a Session variable? They are reactive, just what you need.
Instead of manually handling a dependency, using Session.set and Session.get will register and fire dependencies automatically..
post:function()
{
var searchCriteria = Session.get("searchCriteria") || {};
posts.find(searchCriteria,{sort: {createdAt: -1}}).fetch();
}
'onclick #searchBtn':function()
{
Session.set("searchCriteria", {'Headline': search});
}
The Session variable would also be preserved after a hot code reload. A variable would not.

Related

How can I be updated of an attribute change in Meteor?

I have a template that subscribes to a document. Everything works fine in the DOM and Blaze updates as soon as an attribute used in the template helpers is changed.
I also have some custom logic that doesn't appears in the DOM and depends on the document attributes. How can I call a function to change that logic when an attribute is updated?
I'm looking for something like this.data.attr.onChanged where this would refer to the template and this.data is the data send to the template, as usual; or a Meteor function that is rerun on change where I could put my callback in.
I hoped that template.onRendered would be recalled, but that's not the case.
I've read a lot about reactive variables, but could not find how they could be useful here.
[edit] the change is coming from the server that is communicating with another service
I've tried Tracker.autorun like this:
Template.editItem.onRendered(function() {
var self = this;
Tracker.autorun(function () {
console.log("tracker", self.data.item.socketId);
});
});
And the corresponding route is:
Router.route('editItem', {
path: '/edit/:_id',
waitOn: function () {
var sub = Meteor.subscribe('item', this.params._id);
return [sub];
},
data: function () {
return {item: Items.findOne(this.params._id)};
},
action: function () {
if (this.ready())
this.render();
}
});
At some point, the property socketId gets removed from the corresponding document by the server and I'm sure of that since I've checked in the shell, but the tracker doesn't rerun.
Use Template.currentData().item.socketId instead of self.data.item.socketId, this will give you reactivity.
And in templates generally, use self.autorun instead of Tracker.autorun (unlike Tracker.autorun, this will ensure that the autorun is stopped when the template is destroyed). Likewise, if you want to subscribe in a template, use self.subscribe instead of Meteor.subscribe.
Code to see if Template.currentData() works for you:
Template.editItem.onRendered(function() {
var self = this;
self.autorun(function () {
console.log("tracker", Template.currentData().item.socketId);
});
});
I'm not sure if I got you right, you just want to observe your html inputs and apply the new value to your helper method(s) on change?!
If so, you could use session variables to store your temporary UI state:
// observe your input
Template.yourTemplate.events({
"change #inputA": function (event) {
if(event.target.value != "") {
Session.set("valueA", event.target.value);
}
}
}
// apply the changed value on your helper function
Template.yourTemplate.helpers({
getSomeData: function() {
var a = Session.get("valueA");
// do something with a ..
}
}
In meteor's official todo app tutorial this concept is also used.
If you need to re-run something which is not part of DOM/helper, you can use Tracker.autorun. According to meteor docs, Run a function now and rerun it later whenever its dependencies change.
here's the docs link
Try moving the subscription into Tracker.autorun
Template.editItem.onRendered(function() {
var self = this;
Tracker.autorun(function () {
Meteor.subscribe('item', this.params._id);
console.log("tracker", self.data.item.socketId);
});
});
Of course you can't use this.params there so you can store this as a Session variable

Know when child templates have been rendered

<template name="FrameItems">
<div class="frame-items">
{{#each frames}}
{{> FrameItem}}
{{/each}}
</div>
</template>
In the above example, I want to know when all FrameItem templates inside FrameItems template have been rendered. I thought onRendered of the parent would be invoked when all the child templates have been rendered, but it was just called right away. What's the conventional way of making sure all the child templates are rendered?
One way to do it is to use a counter and increment it until it reaches a certain value.
Here the counter would in Session and incremented until it reaches the length of your Frames iterable thing:
Template.FrameItems.onRendered(function() {
Session.set('frameCounter', 0);
});
Template.FrameItem.onRendered(function() {
Session.set('frameCounter', Session.get('frameCounter') + 1);
});
Then you simply use a tracker:
//Where template is your template instance, for example 'this' in an onCreated callback
template.autorun(function doStuffWhenFramesRendered(computation) {
if(Session.get('frameCounter') === template.frames.length) {
doStuff();
//Stop observing
computation.stop();
}
});
Note that it takes into account the fact that FrameItem may render at weird times (avoiding race conditions if any), but it doesn't take into account new frames. To take those into account you would not stop the computation.
Here is how I would proceed:
You create a pageSession reactive variable or reactive dictionary entry. Let's call it lastRendered.
You update it in the onRendered function of your FrameItem template using the _id of the related frames item. This way, each time a FrameItem template is rendered, you now which one it is.
You create an helper in your parent template watching your lastRendered reactive variable and checking if it matches your last frames item. It could look like that (untested code):
lastFrameIsRendered: function() {
var lastId = frames.find().limit(1).sort({$natural:-1}).fetch()._id;
return pageSession.get ("lastRendered") === lastId;
},
Alternatively, if you need to get a feedback in your parent template onRendered function, you can wrap this code into a this.autorun(function() { (tracker) like this:
var lastId = frames.find().limit(1).sort({$natural:-1}).fetch()._id;
this.autorun(function() {
if (pageSession.get ("lastRendered") === lastId) {
//do your stuff
}
});
It will be executed each time there is a change in your parent template.

Is there any way to insert a callback before/after a templates context is updated?

I'm aware of Template.onRendered, however I have the need to destroy and setup some plugins that act on the dom when the actual context is updated.
So saying I have content template, I'd need something similar to the following:
Template.content.onBeforeChange(function () {
$(".editor").editable("destroy");
});
Template.content.onAfterChange(function () {
$(".editor").editable();
});
Is there any current way I can achieve this with the existing Template api?
You should be able to detect a context change within a template autorun by looking at currentData like this:
Template.content.onRendered(function() {
this.autorun(function() {
if (Template.currentData()) {
// the context just changed - insert code here
}
});
});
I'm unclear if that works for your particular case because this technique only gets you the equivalent of onAfterChange.

Meteor: Access Template Helper (or variable) from another helper

How can I reference a template helper from another one? For example...
Template.XXX.helpers({
reusableHelper: function() {
return this.field1 * 25 / 100; //or some other result
},
anotherHelper: function() {
if (this.reusableHelper() > 300) //this does not work
return this.reusableHelper() + ' is greater than 300';
else
return this.reusableHelper() + ' is smaller than 300';
}
});
I have also tried Template.instance().__helpers.reusableHelper - all with no luck.
Alternatively is there a way to define reactive Template instance variables?
XXX is a sub-template that renders multiple times on the same page.
You can but only with global template helpers.
Blaze._globalHelpers.nameOfHelper()
Here is an example calling Iron:Router's pathFor global helper.
Template.ionItem.helpers({
url: function () {
var hash = {};
hash.route = path;
hash.query = this.query;
hash.hash = this.hash;
hash.data = this.data;
var options = new Spacebars.kw(hash);
if (this.url){
return Blaze._globalHelpers.urlFor(options)
} else if( this.path || this.route ) {
return Blaze._globalHelpers.pathFor(options)
}
}
});
EDIT: To your second question. You can call the same template as many times as you like on a page and pass different data attributes directly into it and/or use #each block template wrapper to iterate over data. #each will call a template many times giving it a different data context each time.
#each Example
<template name="listOfPosts">
<ul>
{{#each posts}}
{{>postListItem}} <!--this template will get a different data context each time-->
{{/each}}
</ul>
</template>
Attributes Example
<template name="postDetails">
{{>postHeader title="Hello World" headerType="main" data=someHelper}}
{{>postHeader title="I am a sub" headerType="sub" data=newHelper}}
{{>postBody doc=bodyHelper}}
</template>
This like using of common code, you can make another javascript function which contains the your reusable code and call it from wherever you required.
Like in your code-
function calcField(field){
return field * 25 / 100
}
and in you template helper-
Template.XXX.helpers({
reusableHelper: function() {
return calcField(this.field1);
},
anotherHelper: function() {
if (calcField(this.field1) > 300)
return calcField(this.field1) + ' is greater than 300';
else
return calcField(this.field1) + ' is smaller than 300';
}
});
and
Alternatively is there a way to define reactive Template instance
variables?
you can use Session variables or Reactive variable
Disclaimer: This may not answer your question directly, but it might be helpful for people stuck with a similar use case:
Sometimes it's easy to get locked into the "Meteor way", that standard Javascript rules are forgotten.
Two use cases that sound similar to what you're trying to do:
1. For helpers/events that you can access anywhere on the client-side, simply set a global helper.
Put this in, say, client/helpers.js:
Helpers = {
someFunction: function(params) {
/* Do something here */
}
}
Now Helpers.someFunction() is available to all templates.
If you want to bind the local template instance to it for some reason, again, it's standard JS:
var boundFunction = Helpers.someFunction.bind(this);
2. To create reusable Blaze helpers inside of templates, use Template.registerHelper
For example, this function uses the "numeral" library to format numbers:
Template.registerHelper('numeral', function(context, opt) {
var format = (opt.hash && opt.hash.format) || '0,0.00';
return numeral(context || 0).format(format);
});
You can use this in any template like so:
{{numeral someNumberVariable format='0,0'}}
I found a better solution with collection hooks:
Item = new Mongo.Collection('Items');
Item.helpers({
isAuthor: function(){
return this.authorId == Meteor.userId();
},
color: function(){
if(this.isAuthor())
return 'green';
else
return 'red';
}
});
I then becomes functions of this, usable in both helpers and templates.
i had something similar -- i had 2 helpers in the same template that needed access to the same function. however, that function 1) needed access to a reactive var in the template, and 2) is a filter function, so i couldn't just pass in the data of that reactive var.
i ended up defining the filter function in the templates onCreated() and stored it in a reactive var, so the helpers could access it.
Template.Foo.onCreated(function () {
this.fooData = new ReactiveVar();
function filterFoo(key) {
var foo = Template.instance().fooData.get();
// filter result is based on the key and the foo data
return [true|false];
}
this.filterFoo = new ReactiveVar(filterFoo);
});
Template.Foo.helpers({
helper1: function() {
var filterFn = Template.instance().filterFoo.get();
return CollectionA.getKeys().filter(filterFn);
},
helper2: function() {
var filterFn = Template.instance().filterFoo.get();
return CollectionB.getKeys().filter(filterFn);
},
});
Since this answer is currently missing - I wanted to add an update
In the current meteor version, you should be able to call:
var TEMPLATE_NAME = //the name of your template...
var HELPER_NAME = //the name of your helper...
Template[TEMPLATE_NAME].__helpers[' '+HELPER_NAME]
You should call it like this, if you want to make sure the helper has access to this:
var context = this;
Template[TEMPLATE_NAME].__helpers[' '+HELPER_NAME].call(context,/* args */);
But be careful - this could break in future Meteor versions.
Adding on to Nils' answer, I have been able to access Template level helpers in events using the following code:
'click a#back': (event, instance) ->
if instance.view.template.__helpers[' complete']() && instance.view.template.__helpers[' changed']()
event.preventDefault()
this just came up again at work, and this time we used modules. in this case, we had a number of large, related functions that had to maintain data across calls. i wanted them outside the template file but not totally polluting the Meteor scope. so we made a module (polluting the Meteor scope 1x) and called the functions therein from the template.
lib/FooHelpers.js:
FooHelpers = (function () {
var _foo;
function setupFoo(value) {
_foo = value;
}
function getFoo() {
return _foo;
}
function incFoo() {
_foo++;
}
return {
setupFoo: setupFoo,
getFoo: getFoo,
incFoo: incFoo
}
})();
FooTemplate.js:
Template.FooTemplate.helpers({
testFoo: function() {
FooHelpers.setupFoo(7);
console.log(FooHelpers.getFoo());
FooHelpers.incFoo();
console.log(FooHelpers.getFoo());
}
});
console output is 7, 8.

meteor and textareas

Ok so I'm not sure why I can't render the code. First if I console.log users.content I get the content I want but I'm some how not able to pass it to a textarea so that it show's it...
Users = new Meteor.Collection("users");
if(Meteor.is_client){
Template.inputUser.code = function(){
var el = Users.find({name:"oscar"});
el.forEach(function(users){
console.log(users.content);
})
}
}
And then on my html template I have
<body>{{> inputUser}}</body>
<template name="inputUser">
<textarea>{{content}}</textarea>
</template>
And I would have a record on the db suck as so
if(Meteor.is_server)
Users.insert({name:"oscar",content:"hello world"})
Thanks for your help guys.
Firstly your method Template.inputUser.code should return something, you should also note that it wouldn't be called with that template either as it needs a {{code}} call in it rather than {{content}}
The second point is database contents are not always available if you have disabled the autopublish package, if so check out using publish(in the server code) and subscribe(in the client code): http://docs.meteor.com/#meteor_subscribe you can use this to check when the client has all the data to display. Something like:
Meteor.subscribe('allusers', function() {
Template.inputUser.code = function(){
var user = Users.findOne({name:"oscar"});
return user.content;
}
});
...
Meteor.publish('allusers', function() {
return Users.find();
});

Resources