Meteor - handling events in nested templates... without polluting 'Session' variable - meteor

I've come across this situation several times now and I realise I'm not really confident about the 'meteor/right' way to handle it.
Suppose I have a form with several parts - each represented by a template - and within each part there are more templates representing eg. datepickers etc.
<template name='myForm'>
{{>partOne}}
{{>partTwo}}
<button class='submit'>Submit</button>
</template>
<template name='partOne'>
{{>widget}}
{{>widget}}
</template>
<template name='widget'>
<input class='datepicker' />
</template>
I want to keep track of my form as the user fills it out - on the level of the 'myForm' template - but all the events are happening at the level of 'widget'.
One solution I keep seeing (e.g. in this SO answer) is to just put everything in the global Session variable. Like so
Template.widget.events({
'click .select' : function(event, template){
var name = template.data.name;
Session.set(name, $(event.currentTarget).val());
}
});
And then in myForm I should do something like this
Template.myForm.rendered = function(){
Tracker.autorun(function(){
var name = Session.get('name');
// do something
});
}
But as my forms are getting more complicated, I find this is really turning into a mess on the myForm template level, all while filling up my Session variable with data that isn't really application-global.
I'd be really grateful for any ideas on how others deal with this ! Keeping templates and widgets modular while still being able to follow and react to their triggered events from parent templates...

You're not alone in feeling like something just isn't right. This is one of the reasons there's a lot of talk about a Blaze 2. Here's what I do:
Create an app global namespace (e.g. G = {}). I usually use the first letter of the app name & do this in lib/config/_namespace.js
Put your collections in G.Collections or G.C,
Put your shared functions in G.Fx, etc...
Put your template vars in G.T.
Then, save that variable to G.T.varName. In doing so, you can use it in rendered as well as events and helpers. As a perk, it's super easy to find all your "globals" because they're all in the G object. Additionally, you can now 'use strict' again.
Then, to keep it clean:
Template.parentTemplate.destroyed = function() {
G.T = {};
};
So if you need reactivity, just make a ReactiveDict:
Template.parentTemplate.created = function() {
G.T.RD = new ReactiveDict();
};

You can use a file-level ReactiveVar or ReactiveDict, instead of the Session object.

Related

Adding components to existing Ractive instance

I have a somewhat weird question.
In Ractive we can do something like this.
App’s template:
<h1>My app</h1><SubComponent/>
And generally have SubComponent’s template access data from App.
Is there any way to have the same behaviour without mentioning SubComponent in the template?
Something like:
const App = new Ractive({el: ‘#myapp’, …………});
const SubComponent = Ractive.extend({ append: true, ………… });
const example = new SubComponent()
example.render(App.el)
But, with example’s template being able to access App’s data, maybe also giving App the chance to find it by using App.findComponent().
What I’m trying to do, is having unpredictable (= I can’t include them inside templates by default) nested components.
Hopefully this makes sense.
Any idea on how to do it?
I don't know if this will work out for you..
But you can just put a div with a known ID in
<h1>My app</h1><div id="subcomp" />
Then mount your dynamic SubComponent under there.
You can use can event like oncomplete - which will guarentee than div#subcomp has been added to dom.
oncomplete: function() {
this.mycompRef = new SubComponent( { el: '#subcomp' });
// You can skip findComponent and use mycompRef
}

Template empty initially but renders properly on changing and coming back to route

I have a template named profile which contains three other templates. One of these templates is {{> postlist}}
and the helper function for this template is
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : postsArr}});
}
});
The problem is on going to the route, postlist template is empty, since postsArr is calculated later after the dom has loaded on the basis of other two templates. But, if I click on other route and come back to this route, the template renders properly.
What should I do that template renders properly initially itself?
The easiest way would be to us Session, though it's probably the worst option:
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : Session.get('postsArr') }});
}
});
If you now call Session.set('postArr', ...) anywhere in your code the posts helper will update automatically. The second option is to use a shared reactive variable:
var postsArr = new ReactiveVar();
and then inside your helper:
return Posts.find({rph: {$in : posts.Arr.get() }});
Now you can do postsArr.set(...) and everything should work fine. Just remember to meteor add reactive-var do your project.
One last doubt is: where to put that reactive variable declaration? In most cases you can do away with putting in a single "controller" file. It will work as long as:
- you only have one instance of your template a time
- the code which sets ad gets the value of you reactive variable may be put in the same file
If one of the above conditions does not hold, then the only option to go, which is BTW the best possible, is to put your state variable in your template's scope. This is how you do it:
Template.postsList.created = function () {
this.postsArr = new ReactiveVar();
};
Template.postlist.helpers({
posts: function() {
return Posts.find({rph: {$in : Template.instance().postsArr.get() }});
}
});
From helpers you can always access postsArr using the Template.instance() routine which always return the current template instance, for which the helper was called. From event handlers, note that the second argument of your handler is always the template instance, which you're interested in.
If you need to access it from another templates, then you should probably put your state variable on the corresponding route controller. Assuming you're using iron-router, that would be:
Iron.controller().state.get('postsArr');
The Iron.controller routine grants you access to the current route controller. Read this for more details.

Turning a plain JS Object into a reactive one

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!

sending information through events in meteor

Ok so I'm working with meteor! Woo hoo I love it so far, but I've actually run into an architecture problem (or maybe its super simple and i just dont know it yet).
I have a list of names that belong to a user. And a delete button that is aligned next to the name
name - x
name - x
name - x
and I want a functionality to click the 'x', and then proceed to clearing the name from the database using the meteor event handler. I'm finding trouble thinking about how I'm going to pass the name along with the click to proceed to delete it from the database.
I can't use a unique id in the template to call a document.getElementById() (unless I came up with an integer system that followed the database.)
Does anyone have a good thought on this?
Here is a complete working example:
html
<body>
{{> userEdit}}
</body>
<template name="nameChoice">
<p>
<span>{{name}}</span>
x
</p>
</template>
<template name="userEdit">
{{#each nameChoices}}
{{> nameChoice name=this}}
{{/each}}
</template>
js
Users = new Meteor.Collection(null);
if (Meteor.isClient) {
Meteor.startup(function () {
Users.insert({nameChoices: ['foo', 'bar', 'baz']});
});
Template.userEdit.nameChoices = function () {
return Users.findOne() && Users.findOne().nameChoices;
};
Template.nameChoice.events({
'click .remove': function () {
_id = Users.findOne()._id;
Users.update(_id, {$pull: {'nameChoices': this.name}});
}
});
}
This actually does a bunch of stuff you wouldn't do in a real application (defined a client-only Users collection, assumes there is only one user, etc). But the main takeaway is that you can use the data context in each nameChoice template to respond to the remove event. This approach can nearly always replace the need for coming up with your own artificial id system. Feel free to ask questions if any of this is unclear.

Pass named function to an events map

I have a Meteor template that includes the following:
{{#with selected_recipe}}
{{>recipe}}
{{/with}}
In my code (Coffeescript), I want to call a function by name from my event map (Backbone-style):
Template.recipe.events = {
'click #btn-edit-recipe': 'editRecipe'
}
editRecipe = (event) ->
console.log # #should log the selected_recipe object
#edit recipe
However, this fails. When I click on my button in the recipe template, I get Uncaught TypeError: Object editRecipe has no method 'call' (liveui.js:651) I learned event maps from Backbone, and maybe Meteor is different. I can get it to work with:
Template.recipe.events = {
'click #btn-edit-recipe': -> editRecipe.call(#, event)
}
Is this the right way to do this? Or am I making some simple error? I've always liked using event maps this way because it summarizes the behaviors of the rendered template in just a few lines. Anonymous functions can spread the list out, making it harder to read, and of course they are not reusable.
What you are doing (later one, where event definition points to a function) is right.
Event map with value as function name (string) is pattern specific to backbone. Meteor doesn't support it.
I've always liked using event maps this way because it summarizes the
behaviors of the rendered template in just a few lines.
But you can acheive similar functionality by doing something like this:
Template.recipe.doLogin = function(){};
Template.recipe.requestData = function(){};
// OR Another way
_.extend(Template.recipe, {
"openFile":function(){},
"editRecipe":function(){}
});
// now Events
Template.recipe.events {
'click #btn-edit-recipe': Template.recipe['editRecipe'],
'click #btn-create-recipe': Template.recipe['createRecipe']
}
Personally, I don't like event-maps. cause its a mapping, which developer has to maintain manually.
Edit: Working code # https://gist.github.com/3010818

Resources