Turning a plain JS Object into a reactive one - meteor

I am working on an edit form that has two paths. One is when the user clicks a "New" button, the other is when they click "Edit".
When they click "New", the code sets a form_id Session var to null and a client_id session variable to null, then does a Router.go('formEdit') to load the formEdit template/route.
In the formEdit.js, I do a reactive Template helper (I think that's what they are called, but anyway) like so:
Template.formEdit.form = function() {
var form;
if (Session.equals('form_id', null)) {
// Create empty form
form = {
title: null,
client_id: Session.get('client_id'),
header_fields: [],
form_fields: []
};
} else {
// Load form
form = Forms.findOne({_id: Session.get('form_id')});
}
return form;
}
Basically I check if the form_id was set or not, if so I load it from the Forms collection, if not I create a blank one. I thought this would be pretty simple, really.
The problem is that the created/found form object does not behave in a "reactive" way. If I add header_fields or form_fields the subsequent template code never updates. Both are in a {{#each}} like so:
<template name="formEdit">
...
{{#each header_fields}}
{{> headerFieldOutput}}
{{/each}}
...
{{#each form_fields}}
{{> formFieldOutput}}
{{/each}}
</template>
How do I make it such that I can push header_fields and form_fields onto the form and have the underlying template reactively update the {{#each}}'s?

I think you're going about it a little differently than what the reactive programming methodology in Meteor is expecting.
You're putting the 'display' logic in your template helper, rather than using the template scaffolding itself to do it.
So, declare a very simple template helper, something like this:
Template.formEdit.form = function () {
return forms.findOne(Session.get("form_id"));
};
And then, in your template scaffolding have something like this:
{{#if form}}
{{#with form}}
{{#each header_fields}}
etc...
{{/with}}
{{#else}}
[[insert your blank form scaffolding in here]]...
{{/if}}
Then, as you set your Session form_id variable, you can set it to null to invoke the {{#else}} portion.
There are more details than this (logic in the form submit click handler to identify if you are performing an update or an insert, for example) but hopefully you get the gist of it from this.
You should try to gain a better understanding about how cursors and reactive computations work, as it will help you better understand how to best use the reactive methodology. A good starting place is the parties example (watch the video and walk through the code manually). It's similar to what you're doing, and shows a good way of building your templates for when you don't have a 'selected' object.
Hope this helps!

Related

Do specific operations on collection items

For my app I'm trying to set an operation on each item of a collection of Widgets. A widget Item contains an url (api rest), and a period. The goal is to loop through a widget collection and do something like this :
//Loop through collection
Meteor.setInterval(function(){
Meteor.call('getData', <Collection>.url,function(e,r){
if(e){
console.error(e);
}else{
//Display the data into the template
}
});
},<Collection>.period);
In the template I'd like to do something like this :
{{#each widgets}}
{{widgetItem}}
{{/each}}
I'd like to know what is the best way to do this ? I heard about Dynamics Templates with Telescope App but I don't know if it would be useful in my case.
The {{#each}} Spacebars magic changes the data context of the elements inside it.
Basically, assuming you have the following or a similar data structure for your widget:
{
templateName : 'coolWidget1',
templateData : { ... }
}
You could write something along the lines of:
{{#each Widgets}}
{{> Template.dynamic template=templateName data=templateData}}
{{/each}}
Where template= and data= are the object parameters you pass to Template.dynamic (it will produce { template : templateName, data : templateData}).
Then you can just pass templateName and templateData thanks to the {{#each}} changing the data context (Spacebars understand that you mean "the templateName and templateData of the current item of the loop).
So that was for the HTML. If you want to share the returned value from the call with the template, search for questions about Session or ReactiveVar, there's already a lot of stuff asked about it.

Create/modify Meteor templates at runtime

I am wondering how to solve this problem:
I have a template which contains some text with some template helpers inside:
<template>Hello {{who}}, the wheather is {{weather}}</template>
Now I need to change the content of the template dynamically at runtime, while maintaining the helper functionality. For example I would need it like this:
<template>Oh, the {{weather}}. Good evening {{who}}</template>
The text changes and the helpers are needed at different positions. Think of an application where users can create custom forms with placeholders for certain variables like the name of the user who fills out the form. Basically, the content of the template is stored in a mongo document and needs to be turned into a template at runtime, or an existing template needs to be changed.
How to approach this? Can I change the contents of a template at runtime?
To solve this use case you need to use two techniques.
Firstly you need to be able to change the template reactivel. To do this you can use Template.dynamic. eg:
{{> Template.dynamic template=helperToReturnName [data=data] }}
See here: http://docs.meteor.com/#/full/template_dynamic
Now that you can change template, you need to be able to create new templates on the fly from you database content. This is non trivial, but it's possible if you're willing to write code to create them, like this:
Template.__define__("postList", (function() {
var view = this;
return [
HTML.Raw("<h1>Post List</h1>\n "),
HTML.UL("\n ", Blaze.Each(function() {
return Spacebars.call(view.lookup("posts"));
},
function() {
return [ "\n ", HTML.LI(Blaze.View(function() {
return Spacebars.mustache(view.lookup("title"));
})), "\n " ];
}), "\n ")
];
}));
That code snippet was taken from this article on Meteorhacks, and the article itself goes into far more detail. After reading the article you'll be armed with the knowledge you need to complete the task...
Just have a helper dynamically build the entire string (remembering that this refers to the current data context):
Template.foo.helpers({
dynamicString: function(switch){
if ( switch == 1) return "Hello "+this.who+", the wheather is "+this.weather;
else return "Oh, the "+this.weather+". Good evening "+this.who;
}
});
Then in your template:
<template name="foo">
{{dynamicString}}
</template>
Alternatively, just use {{#if variable}} or {{#unless variable}} blocks to change the logic in your template. Much, much simpler.
<template name="foo">
{{#if case1}}
Hello {{who}}, the wheather is {{weather}}
{{else}}
Oh, the {{weather}}. Good evening {{who}}
{{/if}}
</template>
You can always have a template helper that computes the necessary boolean variables (e.g. case1).

Template.instance() and context messed with with Blaze block helpers

I'm using a simple template block helper that displays the a loading animation, hiding whatever is within it. This is very helpful for submitting forms and preventing the button from being pressed twice.
<template name="waiting">
{{#if isLoading}}
{{> loader}}
{{else}}
{{> UI.contentBlock}}
{{/if}}
</template>
I decided to put it in a block helper because I've never tried it before and I wanted to keep my code DRY -- I'm using this pattern in several places.
I'm running into issues using this block helper to access the Template.instance() not of the block helper but of the template it is within. I'm also running into problems with the template context. Here are the details of my problem:
I'm setting up a confirm-delete button like so:
{{#waiting isLoading=loadingDelete}}
{{#if confirmDelete}}
<button class="confirm">Are you sure?</button>
{{else}}
<button class="delete">Delete</button>
{{/if}}
{{/waiting}}
I'm also putting the loadingDelete and confirmDelete reactive variables in the template instance so I don't contaminate the Session:
Template.editRecord.created = ->
#loadingDelete = new ReactiveVar(false)
#confirmDelete = new ReactiveVar(false)
Template.editRecord.helpers
loadingDelete: () -> Template.instance().loadingDelete.get()
confirmDelete: () -> Template.instance().confirmDelete.get()
Then I create some events like this:
'click .delete': (e,t) ->
t.confirm.set(true)
'click .confirm': (e,t) ->
t.loadingDelete.set(true)
_id = #_id
Meteor.call 'deleteRecord', _id, (err) ->
t.loadingDelete.set(false)
if err
console.log err
else
Router.go "home"
The problem with this implementation is that the Template.instance() refers to the waiting template in the confirmDelete helper here:
confirmDelete: () -> Template.instance().confirmDelete.get()
The second problem is that the data context in the event refers to the context of the waiting template and thus I need to use _id = Template.parentData()_id instead of _id = #_id. But I am not able to access the editRecord template instance or any of its variables from here which is frustrating.
I changed the template to this which fixes first problem.
{{#if confirmDelete}}
<button class="confirm">Are you sure?</button>
{{else}}
{{#waiting isLoading=loadingDelete}}
<button class="delete">Delete</button>
{{/waiting}}
{{/if}}
But this is ugly and not what I was intending. I was hoping just to replace my #if block with this block helper but I've ended up writing much more code while trying to be DRY with my templates. This just doesn't seem right.
I noticed that #if blocks don't cause these context and Template instance problems. Is there something I am missing?
So it turns out that the Meteor team has recognized this error and are working to fix it. See this PR.

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

Template is re-rendered even though there is no data change

I'm struggling with an issue that I will explain giving a simple demo.
There's following very simple document in People Collection.
{
"_id" : "vxmLRndHgNocZouJg",
"fname" : "John" ,
"nicks" : [ "Johnny" , "Jo"]
}
Now let's consider following templates. Basically I display username and a list of nicknames with input field for adding more nicknames.
<head>
<title>test</title>
</head>
<body>
{{> name}}<br/>
{{> nicks}}
</body>
<template name="name">
<input type="text" value="{{fname}}"/>
</template>
<template name="nicks">
{{#each nicks}}
<div>{{this}}</div>
{{else}}
no nicks yet
{{/each}}
<input type="text" name="nicks"/>
<input type="submit"/>
</template>
My client javascript code is as follows:
Template.name.fname = function() {
return People.findOne({"fname" : "John"},{
transform : function(doc) {
return doc.fname;
}
});
}
Template.name.rendered = function() {
console.log('Template "name" rendered!');
}
Template.nicks.nicks = function() {
var john = People.findOne({"fname" : "John"});
if(john) return john.nicks;
}
Template.nicks.events({
'click input[type="submit"]' : function () {
var johnId = People.findOne({"fname" : "John"})._id; // demo code
People.update(johnId,{
$addToSet : {
nicks : $('input[name="nicks"]').val()
}
})
}
});
My problem is that after adding nickname (update of nicks field in a document) template name is re-rendered (I know because I console.log it). When I query People collection to provide data for name template I use transform option so changes in nicks field shouldn't have impact on name template.
Meteor docs supports this:
Cursors are a reactive data source. The first time you retrieve a cursor's documents with fetch, map, or forEach inside a reactive computation (eg, a template or autorun), Meteor will register a dependency on the underlying data. Any change to the collection that changes the documents in a cursor will trigger a recomputation.
Why template name is re-rendered then?
The template is re-rendered because you change the People collection.
When you alter the People collection, Meteor automatically assumes that everything that it provides data to needs to be recalculated. (Which your name template does via Template.name.fname.
Even though you transform the output of the cursor, the People collection has changed in some way. The query is done before the transform is used, in other words, its not the transform that is looked at but the whole collection.
Meteor thinks that perhaps your document with {'fname':'John'} may have some other field that might have changed and it needs to requery it to check (which the nicks field has been altered). The transform is then applied after the requery.
Your HTML might not actually change at this point, only if the cursor returns something different will the html be changed.
If it becomes an issue in any scenario (i.e forms being cleared or DOM being changed when it shouldn't be) you can use the {{#isolate}} {{/isolate}} blocks to ensure that only everything inside them is re-rendered and nothing outside.

Resources