I have a couple of utility function in my Meteor app that look like this:
Template.registerHelper('canManagePatients', () =>{
const id = Meteor.userId();
const aa = new AccountAccess(id);
const val = aa.canManagePatients();
return val;
});
My understanding is that Meteor.userId() is a recative data source and hence after login the state of these helpers should change.
Unfortunately however, it does not work like this and unless I reload, these options remain hidden after logging in. What am I missing?
This is how the AccountAccess class uses the userId:
constructor(userId){
if (typeof userId !== "undefined"){
this._userId = userId;
} else {
this._userId = Meteor.userId();
}
}
canManagePatients(){
const practice = this.getCurrentPractice();
if (!practice){
return false;
}
const patientTagId = this._getPatientAdminTag()._id;
const ownerTagId = this._getOwnerTag()._id;
return practice.tags.some((obj)=>{
return (obj === patientTagId || obj === ownerTagId);
});
}
Update
This is an excerpt of what the template looks like. All works fine except for the four {{#if }} statements that only function correctly after a reload after logging in.
<div id="navbar" class="navbar-collapse collapse">
{{#if Template.subscriptionsReady}}
<ul class="nav navbar-nav navbar-right">
{{#if currentUser}}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
{{practice.name}}<br><b>Hello {{userName}}</b> (Not you? <b><u>Logout</u></b>)<span class="caret"></span>
</a>
<ul class="dropdown-menu">
{{#if canManageUsers}}<li>Manage Users</li>{{/if}}
{{#if canManageForms}}<li>Manage Registration Settings</li>{{/if}}
{{#if canManageForms}}<li>Manage Forms</li>{{/if}}
{{#if canManagePatients}}<li>Reporting</li>{{/if}}
<li role="separator" class="divider"></li>
<li>Use compact UI</li>
<li>Use old UI</li>
<li role="separator" class="divider"></li>
<li><a id="linkLogout">Logout</a></li>
</ul>
</li>
{{/if}}
</ul>
{{/if}}
</div><!--/.nav-collapse -->
This kind of state initialization is inappropriate for a helper - it's better if your helpers are stateless since they can be called more often than you expect.
A better approach would be to use a Tracker.autorun() to setup a Session variable (or reactive var) and then have your helper refer to that:
Tracker.autorun(()=>{
const aa = new AccountAccess(Meteor.userId());
Session.set('canManagePatients',aa.canManagePatients());
});
Then your helper can just return the value of the Session variable:
Template.registerHelper('canManagePatients', () =>{
return Session.get('canManagePatients');
});
Related
I am having a silly trouble with nested templates. I want to create a dropdown menu with 4 main categories and about 2-3 subcategories for each main category.
<template name="Warehouselist">
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"> {{warehouse}} <span class="caret"></span></a>
<ul class="dropdown-menu">
{{#each Forms}}
{{>Form}} //Pass {{warehouse}} here
{{/each}}
</ul>
</li>
</template>
<template name="Form">
<li id="EWPacking">{{FormName}}</li>
</template>
The problem is that I don't know how to pass the {{warehouse}} data to the child template's helper in order so I can do something like this.
Template.bonus.helpers({
Userform: function(){
return UserForms.find({});
},
warehouse: function(){
return Warehouse.find({});
},
});
Template.Warehouselist.helpers({
Forms: function(Warehouse){
return Forms.find({Warehousename:Warehouse});
}
});
The point is that the helper of the child template has to return different data, depending on what is the Category of the parent element.
Use parent data context in the child template 'Form':
<template name="Warehouselist">
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">{{warehouse}}<span class="caret"></span>
</a>
<ul class="dropdown-menu">
{{#each Forms}}
{{>Form}}
{{/each}}
</ul>
</li>
</template>
<template name="Form">
<li id="EWPacking">
{{FormName}} - {{../warehouse}}</li>
</template>
And change your javascript for the Warehouselist template helper. Get the warehouse from the data context with Template.currentData() and pass it in the Forms.find().
Like this:
Template.Warehouselist.helpers({
Forms: function(){
var warehouse = Template.currentData().warehouse;
console.log('warehouse:', warehouse);
return Forms.find({Warehousename: warehouse});
}
});
If you want to pass warehouse to the child template like you asked use:
(BUT: this is not needed if you follow the solution above!)
{{#each Forms}}
{{>Form warehouse=warehouse}}
{{/each}}
I am trying to route to the current user's profile page using Iron Router, but the 'pathFor' Spacebars helper is not passing the href attribute for the a tag.
This is my template:
<div class="collapse navbar-collapse" id="navigation">
<ul class="nav navbar-nav">
{{#if currentUser}}
<li>Submit Request</li>
<li>View Profile</li>
{{/if}}
</ul>
<ul class="nav navbar-nav navbar-right">
{{> loginButtons}}
</ul>
</div>
The href for the Submit Request button is passing in just fine, but for some reason the View Profile href is not getting passed in.
My router.js is as follows:
Router.route('/users/:_id', {
name: 'userProfile',
data: function() {
return Meteor.users.findOne(this.params._id);
}
});
In my app, I am routing to single "request" page (I created a collection called requests) by doing
Router.route('/requests/:_id, {
name: 'requestPage',
data: function() {
return Requests.findOne(this.params._id);
}
});
This routes correctly to a single request from the Requests collection, so I am confused why the userProfile route doesn't do the same.
Also, I tried to force this functionality by creating a click event on the template:
Template.header.events({
'click .userProfile': function(e) {
e.preventDefault();
var user = Meteor.user();
Router.go('userProfile', {_id: user._id});
}
});
..and this passed the correct href in to the template.
Any help on this would be greatly appreciated!
The userProfile route needs an id parameter. When you call pathFor in a template with only the route name it will attempt to extract the parameters from the current context. Because the context isn't a user (it's a request?) you will end up with an invalid path.
You can set the context inline:
<li>View Profile</li>
Alternatively, you can set the context outside of the call using with:
{{#with currentUser}}
<li>View Profile</li>
{{/with}}
I need to pass the variable to helper function Please help me.
<div class="sidebar">
<ul class="dropdown-menu" style="display: block; position: static;">
{{#each search_family_list}}
<li class="dropdown-submenu active"><img src="" />{{ description }}
<ul class="dropdown-menu">
{{#each material_list}}
<li>atlanta</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</div>
In the above example i need to pass the family_id to the material_list helper function.
Template.header.helpers({
material_list: function ()
{
return Session.get("search_family_list");
}
});
Please help me.
It is likely you can access family_id as a property of this inside your helper.
Template.header.helpers({
material_list: function () {
var family_id = this.family_id;
return Session.get("search_family_list");
}
});
The function context of a template helper can be used to access the current data context.
I've a few code that I want to run only when there's noUser and a few when there's a currentUser.
All these are inside the navigation template. Like so...
{{#if currentUser}}
<li class="nav">Post
</li>
<li class="nav"><a>Ola, {{thisUser}}!</a>
</li>
<li class="nav">Log Out
</li>
{{/if}}
{{#if noUser}}
<li class="nav">Sign Up
</li>
<li class="nav">Login
</li>
{{/if}}
So the problem is that when there's a currentUser(i.e, I'm logged in) and I refresh the page, the code inside the {{#if noUser}} block shows up first then the {{#if currentUser}} block, while the {{#if noUser}} block was only meant to show up when there is no user.
Here's the helper code for the template..
Template.navigation.helpers({
thisUser: function () {
return Meteor.user().username;
},
noUser: function () {
var user = Meteor.user();
if (!user) {
return true;
};
}
});
Don't know what am I doing wrong here. :(
Please help.
You should use if else conditions instead of noUser helper. And to prevent showing "noUser" block while logging in you have to use {{ loggingIn }} helper. Something like this:
{{#if loggingIn}}
<p>Loggin in...</p>
{{else}}
{{#if currentUser}}
<li class="nav">Post
</li>
<li class="nav"><a>Ola, {{thisUser}}!</a>
</li>
<li class="nav">Log Out
</li>
{{else}}
<li class="nav">Sign Up
</li>
<li class="nav">Login
</li>
{{/if}}
{{/if}}
Because Meteor does not know immediately whether user is logged in or not. Therefore you have to use loggingIn helper.
Why don't you refactor your code like this ?
{{#if currentUser}}
<li class="nav">Post
</li>
<li class="nav"><a>Ola, {{thisUser}}!</a>
</li>
<li class="nav">Log Out
</li>
{{else}}
<li class="nav">Sign Up
</li>
<li class="nav">Login
</li>
{{/if}}
You might want to have a look to http://docs.meteor.com/#meteor_loggingin to show a loading indicator if needed.
Ok, so I found this post: Meteor breadcrumb
But lets say I have the following:
<template name="somePage">
<h1>Page Title</h1>
{{> breadcrumb}}
</template>
<template name="breadcrumb">
<ul class="breadcrumb">
<li>
Home
</li>
{{#each path}}
<li>
{{this}}
</li>
</ul>
</template>
Helper:
Template.breadcrumb.helpers({
path: function() {
return Router.current().path.split( "/" );
}
});
Ok so the linked question at the top got me the basics. I'm trying to understand how to do a few more things here that should be obvious. I want the first to be for the home page, and the result returned from the path: function() includes an empty "", "page", "page", etc. in the beginning of it.
I'd like to be able to incorporate the proper paths. To be clear, I'd love to pull this off:
<template name="breadcrumb">
<ul class="breadcrumb">
<li>
Home
</li>
~ pseudo logic
{{#each path that isn't current page}}
<li>
{{this}}
</li>
{{/each}}
<li>
{{ currentPage }}
</li>
</ul>
</template>
Has anyone done this or found a reference that I haven't stumbled across yet?
I'll give you my own recipe for breadcrumbs using iron:router.
It works by supplying additional options to your routes in order to establish a hierarchy between them, with parent-children relations. Then we define a helper on the Router to give us a list of parent routes (up to home) for the current route. When you have this list of route names you can iterate over them to create your breadcrumbs.
First, we need to define our breadcrumbs template which is actually very similar to your pseudo-code. I'm using bootstrap and font-awesome, as well as some newly introduced iron:router#1.0.0-pre features.
<template name="breadcrumbs">
<ol class="breadcrumb">
<li>
{{#linkTo route="home"}}
<i class="fa fa-lg fa-fw fa-home"></i>
{{/linkTo}}
</li>
{{#each intermediateRoutes}}
<li>
{{#linkTo route=name}}
<strong>{{label}}</strong>
{{/linkTo}}
</li>
{{/each}}
<li class="active">
<strong>{{currentRouteLabel}}</strong>
</li>
</ol>
</template>
The {{#linkTo}} block helper is new in iron:router#1.0.0-pre, it simply outputs an anchor tag with an href attribute which value is {{pathFor "route"}}.
Let's define the helpers from our breadcrumbs template:
Template.breadcrumbs.helpers({
intermediateRoutes: function() {
if (!Router.current()) {
return;
}
// get rid of both the first item, which is always assumed to be "home",
// and the last item which we won't display as a link
var routes = Router.parentRoutes().slice(1, -1);
return _.map(routes, function(route) {
// extract name and label properties from the route
return {
name: route.getName(),
label: route.options.label
};
});
},
currentRouteLabel: function() {
// return the label property from the current route options
return Router.current() && Router.current().route.options.label;
}
});
Notice that we rely on the existence of a special option named 'label' which represents what we're going to put in our anchors, we could also have used the name for testing purpose.
The parentRoutes method is something we need to extend the Router with:
_.extend(Router, {
parentRoutes: function() {
if (!this.current()) {
return;
}
var routes = [];
for (var route = this.current().route; !_.isUndefined(route); route = this.routes[route.options.parent]) {
routes.push(route);
}
return routes.reverse();
}
});
Again, this function assumes that every route (except "home") has a parent property which contains the name of its parent route, we then iterate to traverse the route hierarchy (think of a tree, like a file system structure) from the current route up to the root route, collecting each intermediate route in an array, along with the current route.
Finally, don't forget to declare your routes with our two additional properties that our code relies on, along with a name which is now mandatory as routes are indexed by name in the Router.routes property:
Router.route("/", {
name: "home"
});
Router.route("/nested1", {
name: "nested1",
parent: "home"
});
Router.route("/nested1/nested2", {
name: "nested2",
parent: "nested1"
});
// etc...
This example is pretty basic and certainly doesn't cover every use case, but should give you a solid start in terms of design logic toward implementing your own breadcrumbs.
Inspired by #saimeunt I created a meteor breadcrumb plugin which can be found here: https://atmospherejs.com/monbro/iron-router-breadcrumb. You also specify a parent route and a title for the route itself.
I used saimeunt answer but had to make small changes to the template and the template helpers because I have parameters in some of my route paths. Here are my changes.
Template changes: add data=getParameter to #linkTo for intermediate routes
<template name="breadcrumbs">
<ol class="breadcrumb">
<li>
{{#linkTo route="dashboard"}}
<i class="fa fa-lg fa-fw fa-home"></i>
{{/linkTo}}
</li>
{{#each intermediateRoutes}}
<li>
{{#linkTo route=name data=getParameters}}
<strong>{{label}}</strong>
{{/linkTo}}
</li>
{{/each}}
<li class='active'>
<strong>{{currentRouteLabel}}</strong>
</li>
</ol>
</template>
Template helper changes: add helper function getParameters to get parameters from current route.
Template.breadcrumbs.helpers({
intermediateRoutes: function () {
if (!Router.current()) {
return;
}
var parentRoutes = Router.parentRoutes();
var routes = parentRoutes.slice(1, -1);
var intermediateRoutes = _.map(routes, function (route) {
return {
name: route.getName(),
label: route.options.label
};
});
return intermediateRoutes;
},
currentRouteLabel: function () {
var currentRouteLabel = Router.current() && Router.current().route.options.label;
return currentRouteLabel;
},
getParameters: function(){
var currentRoute = Router.current();
var parameters = currentRoute.params;
return parameters;
}
});