Meteor: Re-use template with different data - javascript

I've got a few templates which list items (log lines, users) and style them accordingly in tables. Now I've added a "search" page which searches for a string in different item types and displays them on one page. In terms of layout, I want to reuse the original templates, but obviously with the data returned by the search.
How can I do that without duplicating the template itself in the HTML file?
Put another way: How can I use the actual data from the top-level template in the sub-templates.
Small example:
<template name="user_list">
{{#each user_list}}
</template>
<template name="users">
{{> user_list}}
</template>
<template name="search">
{{> user_list}}
{{> group_list}}
</template>
In .coffee:
Template.users.user_list = ->
[a,b,c,d]
Template.search.user_list = ->
[b,c]
Maybe this is an easy one, which would show how little I really understood about Meteor.

Just put the template you want to use inside the {{#each}} statement. Then each item in the array will be the context for that inner template.
HTML file
<template name="users">
<ul>
{{#each UserArr}}
{{> userItem}}
{{/each}}
</ul>
</template>
<template name="userItem">
<li>{{username}} {{profile.name}}</li>
</template>
JS file
Template.users.UserArr = function()
{
return Meteor.users.find({});
};

another solution is to put your request in a Session attribute and check for its existence when querying for the users. By default Session.get("userQuery") would return undefined but once you enter something in the search field you change that with Session.set("userQuery", {type: "something"})
Now in the coffeescript you could say:
Template.users.user_list = ->
if(Session.get("userQuery"))
[a,b,c,d]
else
[b,c]
or alternatively use a MiniMongo query because it is much nicer :-)
the nice thing is that your HTML will rerender because it is reactive to the Session's content.

Related

Passing parameters to helper in Meteor's dynamic templates

I realise that this has been brought up before, and that Template.dynamic isn't designed to take in a parameter if its template parameter is a helper.
But here is what I would like to do:
// a global helper that composites the template's name using domain-specific and global parameters
Template.registerHelper('templateName', function (name) {
return name + Session.get('someVariable');
});
<!-- use case: a template calling two dynamic ones -->
<template name="someTemplate">
<div class="some-class">
{{> Template.dynamic template=templateName 'title' }}
</div>
<div class="another-class">
{{> Template.dynamic template=templateName 'content' }}
</div>
</template>
This pattern is extremely DRY and it avoids having to set up nested conditionals and rewrite quasi-identical templates each with minimal changes.
Right now, I've got this:
Template.registerHelper('templateName', function () {
var dt = this.dName || Template.parentData().dName;
return dName + Session.get('someVariable'));
});
<template name="someTemplate">
{{> segment dName="title"}}
{{> segment dName="content"}}
</template>
<template name="segment">
<div class="some-class">
{{> Template.dynamic template=templateName }}
</div>
</template>
It works, but it isn't ideal, because;
confusion-prone need to include the parameter for the dynamic template's name in the parent template's call
only ever being allowed one Template.dynamic per template due to one parameter, leading to scalability issues
putting the dName parameter in the template's data context is mixed in with local data, requiring the hacky check whether it's accessible in the current one or the parent's
any further complexity in the DOM requires lots of nested conditionals for parameters or many slightly different static templates, leading to bloat
Are there plans to add this functionality? Am I going about this the wrong way? Did anyone else run into these issues?
Thanks for reading.
There is a trick to doing this with {{with}}. See here:
https://forums.meteor.com/t/pass-argument-to-helper-in-template-dynamic-call/3971

Is it possible to switch collection focus in Meteor spacebars templates?

I am trying to display some relational data in a Meteor spacebars template. Specifically I two collections, Location and Weather. They look something like this:
Location {
_id: 'full-name',
title: 'Full name',
lat: '123.456',
long: '123.456',
order: 1
}
Weather {
locName: 'full-name', // this name always matches a location _id
temperature: '41.3'
}
I'd like to display information from both of these collections on a single page. So that I can show the latest weather from each location (there are 4-20 of them per page). To do this, I've published a Mongo request of both collections like so on the server side:
Meteor.publish('allLocations', function() {
return [
Locations.find({}, { sort: { order: 1 } }),
Weather.find({}) // The weather
]
});
I then subscribe to this publication in my router (iron-router):
Router.map(function() {
this.route('locations', {
waitOn: function () {
return Meteor.subscribe('allLocations');
}
}
});
However, I get stuck when I get to my spacebars template. I can't figure out the syntax for switching collection focus in spacebars.
Here's the psuedo-code for the template that I'm trying to parse, but I know that this doesn't currently work.
<template name="locations">
<div class="locations-grid">
{{#each locations}}
<div class="location {{_id}}">
This is the location template
<h1>{{title}}</h1>
{{#each weather}}
<!-- Trying to pass the _id along to the weather template for filtering -->
{{> weather _id}}
{{/each}}
</div>
{{/each}}
</div>
</template>
<template name="weather">
This is the weather template
{{#with weather}}
<!-- Ideally, we've now switched contexts to the weather collection -->
<h2>Temperature: <div class="temp">{{temperature}}</div></h2>
{{/with}}
</template>
So my question is, where do I tell spacebars to switch contexts to the weather collection? How can I pass along the _id variable to the weather template so that I can select the right data from the collection? I know I'm missing a big step here, I just can't figure out which portion of the Meteor space to examine. I know I might need to specify a subscription for the weather template, but I'm not sure where to do that since it's not really a route, since it won't have its own page. It just lives as a sub-template within the locations template.
Thanks for any tips, or possible suggestions on restructuring.
Before we begin, please read A Guide to Meteor Templates & Data Contexts - that will correctly orient you on contexts inside of #each blocks.
Your goal is to join the correct weather documents to their corresponding location documents. This is most easily accomplished by introducing sub-templates for both types. Let's begin with the top-level template:
<template name="locations">
<div class="locations-grid">
{{#each locations}}
{{> location}}
{{/each}}
</div>
</template>
Which has a locations helper like this:
Template.locations.helpers({
locations: function() {
return Locations.find();
}
});
Next, the location template:
<template name="location">
<div class="location">
<h1>{{title}}</h1>
{{#each weathers}}
{{> weather}}
{{/each}}
</div>
</template>
Which has a weather helper like this:
Template.location.helpers({
weathers: function() {
return Weather.find({locName: this._id});
}
});
The key insight here is that the context of a location template is a single location document so weather will return only the weather documents for this location instance. Finally, your weather template can look like:
<template name="weather">
<h2>Temperature: {{temperature}}</h2>
</template>
Note we are now in a weather context so the #with is no longer needed.
Side note - using sort in your publisher has no affect in this case.

Accessing the context (or the root object) passed to Handler.js Template

I have a template like so
Handlebars Template
<script id="choose-player-template" type="text/x-handlebars-template">
<label>Entity Name:</label>
<input type="text" value="{{entityName}}"></input>
<ul>
{{#players }}
<li><input type="checkbox" {{#if isSelected}}checked{{/if}}/>
{{name}}
</li>
{{/players}}
</ul>
<input type="button" onclick="saveSelection()" value="Save Selection" style="width:150px;"></input>
</script>
Javascript
<script type="text/javascript">
var playersList = {players: [], entityName: "Name1"}; //I pass this to the handlebar template
</script
Lets say that I pass a object playersList to the Template. What syntax should I use to refer to that object inside the Handlebars Template. In short, how to access the root object passed in to Handlebar template.
Edit :
I actually sound confusing with the question above, so this edit. Actually, the template is working and displaying properly the list of players in the ul li list. That part is fine. There is an object "playersList that is passed to template and all the players in this object are displayed properly. However, I am changing the state of each players through the checkbox. Now, once the user is done with the changes, he clicks the input button, shown in the last line of template code. This invokes saveSelection() javascript function (not shown in above code). I wan't to pass the this object from the template, to this function.
Your context is already set to the base when you pass that object in. I don't know of anyway to access the base object when you are inside of a loop. You may want to create a Helper function to handle some of your processing if you can't get it the way you want.
It looks like you want to iterate your players list.
You can do {{#each players}} {{this}} {{/each}} if players = ['joe', 'bob', 'jim'].

How Do I Properly Make a Meteor Template Reactive?

My app displays a collection of items and I'd like to add an item drilldown view.
I've only been able to get half of the solution working; I find the appropriate document and use that document to render the template:
var item = Items.findOne('my_id');
$('#main').html(
Meteor.render(function () {
return Templates.item(item)
}));
This renders the individual item successfully and the appropriate events are bound.
Here's the rub, the template isn't reactive! If I change its data using the associated event handlers or from the console, the template isn't updated. However, a page refresh will reveal the updated data.
I'm a Meteor noobie, so it's probably something very simple. Any help would be greatly appreciated.
It seems to me that you aren't using the templates in they way they were really intended to be.
A meteor app starts with the main html markup which can only exist once in your app..
<head>
<title>My New Fancy App</title>
</head>
<body>
{{>templateName}}
</body>
Then you add a template..
<template name="templateName">
{{#each items}}
template or relevant html goes here..
{{/each}}
</template>
Now you need a template helper to give you data for your {{#each items}} block helper..
Template.templateName.helpers({
items: function(){ return Items.find({}) }
});
All this gets defined on the client side..
Then you'll need a collection and the collection should be defined on both the client and server.
Items = new Meteor.Collection('items');
This should now work as long as you have records in your collection.
Since you wish to only wish to render a single document you can change the helper and template just slightly..
first the helper becomes:
Template.templateName.helpers({
item: function(){ return Items.findOne() }
});
Then the template can reference the values of the returned document through document, so we change our template to:
<template name="templateName">
{{item.propertyName}}
</template>

Meteor Startup JQuery DOM Elements Not Ready (JQueryUI Draggable)

In the Meteor docs it says that Meteor.startup will be called after the DOM and all templates have been processed. However, my code within Meteor.startup is acting as if the DOM elements aren't there.
In .js:
Meteor.startup(function () {
console.log($('.draggable').length);
});
In .html:
<template name="item">
<div class="draggable ui-widget-content">
</div>
</template>
In the console I see:
0
But on the page I can see my items. And indeed if I include my JQuery in Template.item.rendered or in a mouseover event, I get the correct length of the array. So why would the startup function not have my DOM elements ready to use?
I'm guessing your code looks something like this, but let me know if I'm wrong:
<template name="list">
{{#each items}}
{{> item}}
{{/each}}
</template>
The {{#each ...}} helper works with a cursor object to respond to data changes on the cursor. So, in your case, if that data is coming from the server (e.g. a subscription), at the time of Meteor.startup, the data might not have been loaded yet. So initially your list will be empty. Then, as data comes off the wire a new item template will be rendered for each data item. If you want to make a specific item draggable, you could put that jQuery code in the Template.item.rendered callback.
Does this help?

Categories