How do I access one 'sibling' variable in a meteor template helper when I am in the context of another? - meteor

How do I access one 'sibling' variable in a meteor template helper, when I am in the context of another? I want to determine whether the user that is logged in and viewing the page is the same user that posted the ride offering, so that I can hide or show the "bid" button accordingly.
For example, here is my template (html) file:
<!-- client/views/ride_offers.html -->
<template name="RideOfferPage">
<p>UserIsOwner:{{UserIsOwner}}</p>
{{#with CurrentRideOffer}}
{{> RideOffer}}
{{/with}}
</template>
<template name="RideOffer">
<div class="post">
<div class="post-content">
<p>Details, Author: {{author}}, From: {{origin}}, To: {{destination}}, between {{earliest}} and {{latest}} for {{nseats}} person(s). Asking ${{price}}.
<button type="button" class="btn btn-primary" >Bid</button><p>
<p>UserIsOwner:{{UserIsOwner}}</p>
</div>
</div>
</template>
And here is my JavaScript file:
Template.RideOfferPage.helpers({
CurrentRideOffer: function() {
return RideOffers.findOne(Session.get('CurrentOfferId'));
},
UserIsOwner: function() {
return RideOffers.find({_id: Session.get('CurrentOfferId'), userId: Meteor.userId()}).count() > 0;
}
});
In the "RideOffer" template, I am able access the variables author, origin, ..., price. But I am unable to access the boolean UserIsOwner. I am, however, able to access the boolean UserIsOwner in the "RideOfferPage" template.
Does anyone know how I can access the boolean UserIsOwner in the "RideOffer" template?
Cheers,

Put the userIsOwner function outside the helper as an anonymous function and then call it from both templates.
Template.RideOfferPage.helpers({
CurrentRideOffer: function() {
return RideOffers.findOne(Session.get('CurrentOfferId'));
},
UserIsOwner: checkUserIsOwner()
});
Template.RideOffer.helpers({
UserIsOwner: checkUserIsOwner()
});
checkUserIsOwner= function() {
return RideOffers.find({_id: Session.get('CurrentOfferId'), userId: Meteor.userId()}).count() > 0;
}

There are several ways to do what you're asking.
In your particular example you are not asking about siblings, but about parents, since the RideOfferPage template renders the RideOffer template. You can access variables in the parent data context (but not helpers) like so:
<template name="RideOffer">
<div class="post">
<div class="post-content">
<!--
other stuff
-->
<p>UserIsOwner:{{../UserIsOwner}}</p>
</div>
</div>
</template>
In other cases, you may have a template being rendered as a sibling of this one. In that case, you can't actually know what the sibling is until the template is actually on the page; however, you can find it in the rendered callback:
Template.foo.rendered = function() {
var current = this.firstNode;
var next = $(currentItem).next(); // or .prev()
if (next.length) {
nextData = Spark.getDataContext(next[0]);
}
// Do something with nextData
};
Finally, you can get the parent context of any rendered DOM element by repeatedly iterating through its parents. This isn't super efficient but I've used it in places where there is extensive drag and drop with DOMFragments moving around on the page:
Template.bar.events = {
"click .something": function(e) {
var target = e.target;
var context = Spark.getDataContext(target);
var parentContext = context;
while (parentContext === context) {
parentContext = Spark.getDataContext(target = target.parentNode);
}
// Do something with parentContext
}
};
I'm curious to know if there is a better way to do the last thing, which may potentially have to iterate through many DOM elements. In any case, you may want to check out my meteor-autocomplete package for this and other cool tricks.

Related

meteor simple reactive var

I'm working in meteor trying to use a reactive var to switch the content in the main panel between two tabs. I've been able to test the content successfully on it's own so I'm fairly confident the issue lies in the reactive var code. Specifically I think the issue is with the tab: function() but after many searches and reading documentation I haven't found a solution.
The relevant js:
Template.content.onCreated( function() {
this.currentTab = new ReactiveVar('form');
});
Template.content.helpers({
tab: function() {
return Template.instance().currentTab.get();
}
});
Template.content.events({
'click .nav li': function (event, template) {
var currentTab = $( event.target ).closest( "li" );
currentTab.addClass( "active" );
$( ".nav li" ).not( currentTab ).removeClass( "active" );
Template.currentTab.set();
}
});
The relevant html:
<template name ="content">
<ul class ="nav">
<li data-template="form">Form</li>
<li data-template="results">Results</li>
</ul>
{{ > Template.dynamic template=tab}}
</template>
{{ > Template.dynamic template=tab}}
This is calling the tab helper to get a string that is the name of the template you want to show here. It should work the first time because you start out by setting the value of currentTab to "form".
To change the template that gets shown, you need to change the value of currentTab to a string matching the name of the new template. You're not doing that.
Template.currentTab.set();
This is where you should be doing that. Instead you're calling set() on the currentTab property of Template, which I don't think exists. Template with a capital T is a Meteor object, not the template instance that I think you're trying to refer to. And to set a new value for currentTab, you actually need to provide a value. Like so:
Template.content.events( {
'click .nav li': function(event, instance) {
//logic to decide which template you want to show
//and put the name of that template in templateName
instance.currentTab.set(templateName)
}
});

how to retain (state of) non-reactive DOM elements in meteorjs?

I'm trying to understand options for using a stateful / non-reactive DOM component in a Meteor template, in a way that allows the component to retain its state as Meteor updates the DOM.
One specific example involves Leaflet.js: I have an application that includes a Leaftlet map, and I want the user to be able to switch between a display of the map, and some other content. The map is interactive --- the user can pan and zoom in the map --- and I'd like the current zoom/pan state of the map to be retained if/when the user switches away from the map to other content, and then back to the map.
My first attempt at doing this is to put the map in one template, and the other content in another template, and use conditional logic in the containing template to determine which template is rendered:
HTML:
<body>
<div>
<input type="submit" id="mapbutton" value="Display Map">
<input type="submit" id="otherbutton" value="Display Other Stuff">
</div>
{{#if showmap}}
{{> map}}
{{else}}
{{> otherstuff}}
{{/if}}
</body>
<template name="map">
<div id="map"></div>
</template>
<template name="otherstuff">
<p>Here is some other stuff</p>
</template>
JS:
Template.map.rendered = function() {
var map = L.map('map', {
doubleClickZoom: false
}).setView([38.0, -98.0], 5);
L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
maxZoom: 18,
id: 'examples.map-i875mjb7'
}).addTo(map);
};
Session.setDefault("showmap", true);
Template.body.helpers({
"showmap" : function() {
return Session.get("showmap");
}
});
Template.body.events({
"click input#mapbutton": function() {
Session.set("showmap", true);
},
"click input#otherbutton": function() {
Session.set("showmap", false);
}
});
The problem with this approach is that every time the user switches to the map display, Meteor re-renders the map template, creating a new Leaflet map (and associated DOM component), which is initialized from scratch. This means that whatever pan and/or zoom settings the user had previously made in the map are lost. It also involves a short delay in the display while the Leaflet map is constructed. I'd like the Leaflet map to get created one time only, the first time it is displayed, and then saved somewhere off-screen when the user swiches to other content, so that it can be immediately swapped back in later, without incurring the construction delay, and retaining its previous pan/zoom state.
I know that one way to accomplish this would be to design my HTML templates to keep the map div in the DOM when switching displays,
and to use CSS to hide it when necessary. Something like the following:
HTML:
<body>
<div>
<input type="submit" id="mapbutton" value="Map">
<input type="submit" id="otherbutton" value="Other Stuff">
</div>
<div id="map" class="{{#if showmap}}visible{{else}}hidden{{/if}}"></div>
{{#if showother}}
{{> otherstuff}}
{{/if}}
</body>
<template name="otherstuff">
<p>Here is some other stuff</p>
</template>
JS:
Template.body.rendered = function() {
var map = L.map('map', {
doubleClickZoom: false
}).setView([38.0, -98.0], 5);
L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
maxZoom: 18,
id: 'examples.map-i875mjb7'
}).addTo(map);
};
Session.setDefault("showmap", true);
Template.body.helpers({
"showmap" : function() {
return Session.get("showmap");
},
"showother" : function() {
return !Session.get("showmap");
}
});
Template.body.events({
"click input#mapbutton": function() {
Session.set("showmap", true);
},
"click input#otherbutton": function() {
Session.set("showmap", false);
}
});
CSS:
#map.visible {
display: block;
}
#map.hidden {
display: none;
}
This works fine for this simple example, but in reality my application (and the associated templates and resulting DOM) is much more complex.
What I REALLY want is to be able to move the map component around arbitrarily in the DOM. For example, depending on the context, the
map might appear inside a table, or full-screen, or not at all, and I'd like to retain the map's internal state between all of these contexts. Using a Meteor template for the map with conditional logic that determines where it is included seems like a natural way to structure this kind of thing, but that returns to the above problem that every time the map template is rendered, the map is rebuilt from
scratch and reset to its initial state.
Is there a way to tell Meteor to "cache" its rendering of a particular template, and to hang on to the associated DOM element, so that subsequent times when that template is used in the rendering of other content, the previously constructed DOM element is used? I realize this goes against the grain of the reactive approach, but this is a situation where I'm trying to use a complex non-reactive component, and it seems like support for such things could be useful in many contexts.
This issue isn't specific to Leaftlet.js, by the way. I have other non-reactive, stateful components that I would like to use in my Meteor application, and I'd love to find a graceful way to solve this problem for all of them.
Does anyone know if there is a way to do this, or have ideas for a better approach?
Thanks!
I don't think you can keep a rendered item ready for hiding/displaying without any re-rendering, except if you use CSS.
Blaze (the component taking care of rendering templates) can't do that (yet). Have a look at this topic where they basically say the same, but it comes from a meteor dev: https://github.com/meteor/meteor/issues/4351
Either you rely on CSS, either you keep the values you need in for example a reactive dictionary and use them when you render your map template.
Thanks #Billybobbonnet. Your comment to keep the values you need and re-use them when rendering the template gave me the idea to try this:
HTML:
<body>
<div>
<input type="submit" id="mapbutton" value="Map">
<input type="submit" id="otherbutton" value="Other Stuff">
</div>
{{#if showmap}}
{{> map}}
{{else}}
{{> otherstuff}}
{{/if}}
</body>
<template name="map">
<div id="mapcontainer">
<div id="map"></div>
</div>
</template>
<template name="otherstuff">
<p>Here is some other stuff</p>
</template>
JS:
var $mapdiv = undefined;
Template.map.rendered = function() {
if ($mapdiv === undefined) {
// if this is the first time the map has been rendered, create it
var map = L.map('map', {
doubleClickZoom: false
}).setView([38.0, -98.0], 5);
L.tileLayer('https://{s}.tiles.mapbox.com/v3/{id}/{z}/{x}/{y}.png', {
maxZoom: 18,
id: 'examples.map-i875mjb7'
}).addTo(map);
// and hang on to the map's div element for re-use later
$mapdiv = $("#map");
} else {
// map has already been created, so just empty out the container
// and re-insert it
$("#mapcontainer").empty();
$("#mapcontainer").append($mapdiv);
}
};
Session.setDefault("showmap", true);
Template.body.helpers({
"showmap" : function() {
return Session.get("showmap");
}
});
Template.body.events({
"click input#mapbutton": function() {
Session.set("showmap", true);
},
"click input#otherbutton": function() {
Session.set("showmap", false);
}
});
This seems to be working well. It feels a little kludgy, but I like the fact that it lets me put the map in a template which I can use anywhere, just like any other template, and yet the map is only created once.

Meteor data context to child template

I have a route and a template, that has the right data context as defined in the routes.js:
this.route('entrant_view_race', {
path: '/organise/entrants/:_id/',
yieldTemplates: navOrganise,
waitOn: function() {
return Meteor.subscribe('entrants');
},
data: function() {
return Races.findOne(this.params._id);
}
});
The data context is set to the data function above no problems. Within entrant_view_race template/route we have another template:
<div class="row">
<div class="col-md-4">
{{> chart_entrant_status}}
</div>
</div>
Now within chart_entrant_status is a subscription which passes a param which is defined in the data context:
Meteor.subscribe('entrantStatusCount', this._id);
but this._id is undefined. I believed that whatever the data context of the parent is passed to child templates unless you explicitly define such as {{> chart_entrant_status dataContext}} ? How do I pass the _id of the parent context to the child template (I don't want to use session variables).
EDIT:
chart_entrant_status template looks like this:
<template name="chart_entrant_status">
{{#chart_container title="Entrants" subtitle="Status" class="race-status"}}
{{_id}}
<div id="chart"></div>
{{> table_entrant_status}}
{{/chart_container}}
</template>
Note {{_id}} is rendered fine so the context is alive to that point. And the subscription when the template is rendered:
Template.chart_entrant_status.rendered = function() {
Meteor.subscribe('entrantStatusCount', this._id); // this._id is undefined - if I substitute for the actual id as a string Ba48j2tkWdP9CtBXL I succeed....
}
But no cigar... struggling to find where I lose the data context...
EDIT2: This returns this._id fine...
Template.chart_entrant_status.helpers({
stuff: function() {
return this._id; // returns the right id in {{stuff}} in the template
}
})
So is the data context not available to Template.chart_entrant_status.rendered?
EDIT4: solved. It's this.data._id.... ahhhh
this.data._id was the correct answer

How to make dynamically created Divs with meteor and then store & recall them based on ID from the database

Below I have a basic template that has a numerical input form. When you type a number in the form and click Add a list of Divs get created. The Divs are created with a class of "synth" and an id of "synth" + a number. The numbers go in succession based on a counter.
I want to not only store this information in the database but do so in a manner that (eventually) when a user logs in they will have access to their list of Divs as a "saved state" from their previous log in.
I am not even sure if I am going about this in an appropriate manner. I am simply sticking the createSynth() function in the Collection insert for lists. I have a feeling to do this "correctly" I should have two events that work in parallel - one sending to the lists Collection and the other to the dom/Template. These two blocks would then exchange data (some how) which in conjunction create the illusion of a "saved state".
Below is the code I have thus far.
HTML
<head>
<title></title>
</head>
<body>
{{> start}}
</body>
<template name="start">
<input id ="amount" type ="number" />
<input id ="submit" type="button" value="Add" />
<div id="applicationArea"></div>
</template>
Javascript
var lists = new Meteor.Collection("Lists");
var counter = 0;
counterSynth = 0;
if (Meteor.isClient) {
'use strict';
Template.start.events({
'mousedown #submit' : function () {
var amount = document.getElementById("amount").value;
for(i=0;i<amount;i++) {
lists.insert({SoundCircle:createSynth()}); // I am inserting the entire function call, is this the right path?
}
function createSynth() {
var synth = document.createElement("div");
synth.className = "synth";
synth.id = "synth" + (counterSynth++);
applicationArea.appendChild(synth);
};
},
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
You have to use a slightly different approach to this, basically just insert your stuff into the collection, and use handlebars to get it out. I'm not entirely sure what you were doing but you should get a good idea with the below
Server js
synths = new Meteor.Collection('synths'); //This will store our synths
Client js:
synths = new Meteor.Collection('synths'); //This will store our synths
Template.start.events({
'mousedown #submit' : function () {
var amount = document.getElementById("amount").value;
for(i=0;i<amount;i++) {
lists.insert({class:"synth", id:counterSynth});
}
},
});
Template.start.synth = function() {
return synths.find(); //This gives data to the html below
}
HTML:
{{#each synth}}
<div class="{{class}}" id="synth{{id}}">
Synth stuff here
</div>
{{/each}
It's probably best to dynamically recreate the DIVs every time you need them on the client, so the DIV is not stored on the server. If you really want to hard code/store the DIV on the server you would need to simply save the HTML as a string, to a Meteor collection.

in Meteor, how do i update a property on only one instance of a template?

If I have an {{# each}} binding in Meteor, and I want to update a property on only one instance of the template inside the #each. How would I do that? I've tried setting a value on the "template" object inside the events map, but that doesn't seem to be reactive. I've also tried binding to a Session property, but that will cause every instance to update instead of just the one I want...
for example:
{{#each dates}}
{{> dateTemplate}}
{{/each}}
<template name="dateTemplate">
{{date}}
<span style="color: red;">{{errorMsg}}</span> <--- how do i update errorMsg?
</template>
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = 'not valid'; <--- this doesn't do anything
}
});
EDIT TO ADDRESS ANSWER BELOW:
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid';} <--- this also doesn't do anything
}
});
You don't have to use handlebars for this, because its not something that needs reactivity to pass the message through, reactive variables work best with db data, or data that would be updated by another client over the air.
You could use JQuery (included by default) to update it, it can also get a bit fancier:
<template name="dateTemplate">
{{date}}
<span style="color: red;display: none" class="errorMessage"></span>
</template>
Template.dateTemplate.events({
'click': function(event, template) {
$(template.find('.errorMessage')).html('Your Error Message').slideDown();
}
});
Ive edited it so the error is hidden by default, and slides down with an animation
I'm experimenting handling this by passing a different reactive object to each instance of the template. Then the template can bind to the reactive object (which is unique per instance) and we don't have any extra boilerplate.
It ends up looking like this:
Initial render:
Template.firstTemplateWithPoll(ContextProvider.getContext())
Template.secondTemplateWithPoll(ContextProvider.getContext())
// (I actually pass getContext an identifier so I always get the same context for the same template)
JS:
Template.poll.events = {
'click .yes' : function() {
this.reactive.set('selection', 'yes');
},
'click .no' : function() {
this.reactive.set('selection', 'no');
}
};
Template.poll.selection = function(arg) {
return this.reactive.get('selection');
}
Template:
<template name="poll">
<blockquote>
<p>
Your selection on this poll is {{selection}}
</p>
</blockquote>
<button class='yes'>YES</button>
<button class='no'>NO</button>
</template>
template.errorMsg should be a function that returns your error.
Template.dateTemplate.events({
'click': function(event, template) {
template.errorMsg = function() { return 'not valid'; };
}
});

Resources