Setting different routes dependent on if user logged in Meteor - meteor

I am trying to get my head around setting different master route templates for account holders and guests. I cannot seem to render different templates (two different nav bars) dependent on whether a user has logged in or not. I have set up a template for guests
//guest.js
<template name="unregistered">
{{>navUnreg}}
<div class="container">
{{>about}}
</div>
</template>
<template name="navUnreg">
<nav class="navbar navbar-default">
<div class="container">
<ul class="nav navbar-nav">
<a class="navbar-brand" href="#">CabStack</a>
</ul>
<ul class="nav navbar-nav navbar-right">
{{>loginButtons}}
</ul>
</div>
</nav>
</template>
This is the generic about page for users that are not logged on. I then have the template for Account users that have logged in which is -
//UserLoggedIn.js
<template name="cabMaster">
{{>cabNav}}
<div class="container">
{{>yield}}
</div>
</template>
I initially tried to place an if/else statement into a Router configure -
//config.js
Router.configure({
if(Meteor.user()){
layoutTemplate: "cabMaster"
}
else{
layoutTemplate: "unregistered"
}
});
However I keep getting thrown an error. I have already used the Router.onBeforeAction on the userLoggedIn.js page to restrict access to routes until a profile page is filled out which is
//Access rights for logged in users
function userNoAccess(){
if(!Meteor.userId()){
alert("restricted until profile page completed");
this.render(“profile”);
return pause();
} else {
this.next();
}
}
//Do not allow access to nav links until user has filled out ‘profile’ page.
Router.onBeforeAction(userNoAccess, {
only: ["dashboard", "edit_dashboard", "help"]
});
So
I did think about placing the userLoggedIn route in the Accounts.onLogin() function that I have in my client.js file but I want to keep all routes in one place. Does anyone have any suggestions as to how I might achieve this. Thanks

There are several ways to go about this. You don't have to do everything in the router for example. The choice of navbar can happen right in your template:
<template name="navBar">
{{#if currentUser}}
{{> navReg}}
{{else}}
{{> navUnreg}}
{{/if}}
</template>
This is quite convenient when you're trying to alter little parts of your layout without switching out the whole layout.
I suggest you look at using controllers for dealing with authenticated and unauthenticated routes. They make your routing code much more maintainable. I've written a basic tutorial that you might find helpful.

Related

FullpageJs and Meteor : Capture event from template

I'm struggling with fullpage.js in Meteor.
When I add a button inside a template, the Template.templatename.events function does not fire on event.
For example:
Template HTML (messages.html)
<template name="messages">
<ul>
{{#each mess}}
<li>{{ messContent}}</li>
{{/each}}
<button class="addMessage">Add Message!</button>
</ul>
</template>
Template JavaScript (messages.js)
Template.messages.helpers({
mess: function(){
return Messages.find();
}
});
Template.messages.events({
'click .addMessage' : function(event){
console.log("clicked")
}
})
Main HTML
<head>
<title>fulltest</title>
</head>
<body>
{{> full}}
</body>
<template name="full">
<div id="fullpage">
<div class="section active">
<h1>First slide</h1>
{{> messages}}
</div>
<div class="section">
<h1>Second slide</h1>
</div>
</div>
</template>
My fullpage initialisation:
Template.full.rendered = function(){
$('#fullpage').fullpage();
}
If I remove the fullpage initialisation then the click event gets logged. Still new at Meteor, I didn't manage to grasp what's going wrong here.
All help much appreciated,
Joris
Use delegation or use verticalCentered:false and scrollOverflow:false.
From the fullPage.js FAQs:
My javascript/jQuery events don't work anymore when using fullPage.js
Short answer: if you are using options such as verticalCentered:true or overflowScroll:true in fullPage.js initialization, then you will have to treat your selectors as dynamic elements and use delegation. (by using things such as on from jQuery). Another option is to add your code in the afterRender callback of fullpage.js
Explanation: if you are using options such as verticalCentered:true or overflowScroll:true of fullPage.js, your content will be wrapped inside other elements changing its position in the DOM structure of the site. This way, your content would be consider as "dynamically added content" and most plugins need the content to be originally on the site to perform their tasks. Using the afterRender callback to initialize your plugins, fullPage.js makes sure to initialize them only when fullPage.js has stopped changing the DOM structure of the site.

Why does my click event not fire when I add each blocks into my template to iterate through a collection (Meteor)

The event fires only when I remove the each blocks. What the event does is apply a vertical accordion slide down transition on an element. What I want to do is add the same slide down transition to all my documents when they are displayed in my views.
Right now, when I add an each block to iterate through my collection and display all the documents from my collection, the slide down event stops working.
Here's my template:
<template name="auctionsList">
<div class="container">
<div id='cssmenu'>
<ul>
{{#each auctions}}
{{>auction}}
{{/each}}
</ul>
</div> <!-- end cssmenu -->
</div><!-- end container -->
{{#if isReady}}
{{#if hasMoreauctions}}
<a class="load-more btn btn-default center-block text-uppercase" id="loadMore" href="#" style="margin-top:10px;">Load More</a>
{{/if}}
{{else}}
<div class="loading">{{>spinner}}</div>
{{/if}}
</template>
<template name="auction">
<li class='has-sub'>
<a href='#'>
<div class="auction-image">
<img src="brand_logos/DominosPizza.png" class="img-responsive" height="200" width="200">
</div>
{{> durationLeft}}
</a>
<ul>
<li><a href='#'>
<span>Sub Product</span></a></li>
</ul>
</li>
</template>
Here's my rendered/helper
Template.auctionsList.rendered = function () {
$('#cssmenu li.has-sub>a').on('click', function(){
$(this).removeAttr('href');
var element = $(this).parent('li');
if (element.hasClass('open')) {
element.removeClass('open');
element.find('li').removeClass('open');
element.find('ul').slideUp();
}
else {
element.addClass('open');
element.children('ul').slideDown();
element.siblings('li').children('ul').slideUp();
element.siblings('li').removeClass('open');
element.siblings('li').find('li').removeClass('open');
element.siblings('li').find('ul').slideUp();
}
});
}
Template.auctionsList.helpers({
auctions: function () {
return Template.instance().userauctions();
}
});
Template.auctionsList.events({
'click #cssmenu li.has-sub>a' : function(event, template) {
$(this).removeAttr('href');
var element = $(this).parent('li');
if (element.hasClass('open')) {
element.removeClass('open');
element.find('li').removeClass('open');
element.find('ul').slideUp();
}
else {
element.addClass('open');
element.children('ul').slideDown();
element.siblings('li').children('ul').slideUp();
element.siblings('li').removeClass('open');
element.siblings('li').find('li').removeClass('open');
element.siblings('li').find('ul').slideUp();
}
}
});
You should move this from rendered to events. $('#cssmenu li.has-sub>a').on('click', function(){ }
Put the event within the auction template where your <li> tag exists.
Updated
Couple of things based on the gists you published:
1) you should try not use script tags directly in the template - i havent seen this often if at all. Move all this code to
Template.accordion.rendered = function(){ //here };
or better make it work in auctionsList because then its not duplicated x times for every each iteration.
But what would be even better is not to have the 'on' event at all. You should use a meteor event... like you mentioned in your earlier post. Meteor events have access to 'this' which is the current context item.
I would try put it in a template event on the master auctionsList template and later on worry about how it will work with the sub templates.
2) I dont think you need to add everything into another template like you have, the accordion template doesnt really need to exist, you can probably get away with putting it in the auction template (consider using even auctions to do the click event because if you do it that way your js snippet wont be repeated x times per post listing.You only need this once..
3) When you create the li in the auction template give it an ID at that point
<li id={{new accordion code in auction template}}>
You can then reference this id on your template click event. If you are new to meteor do yourself a favor and do the following in a click event to better understand what is going on (you will most likely find the ID you need within one of the values - most likely this:
Template.YourTemplate.events({
'click any button in your each': function(event,bl,value)
{
console.log(event);
console.log(bl);
console.log(value);
//and most importantly
console.log(this);
}
});
I know its not "an answer" but i hope it leads you in the right direction.

Proper way to handle multiple "pages" in meteor

What is the "formal" way of handling multiple "pages" in meteor? I say "pages" I've seen people do it a couple of different ways. I've seen people create actual full pages, (index.html, about.html, contact.html) and then when links are clicked, you'd write a route to render those pages. But I've also seen people essentially put the code for each of those pages inside <template> tags and then do nifty show/hide type stuff based of what they've clicked, login credentials, etc.
There are several ways to handle multiple pages in meteor
1.iron-router
using iron router you can create a layout template and inject all other templates inside it using {{> yield}}. Check the iron-router guide to know more about layout template.
2.{{> Template.dynamic}}
if you do not want to add iron-router you can use {{> Template.dynamic}} to achieve the same.
<body>
<ul>
<li><a href="#" class="index">Home</li>
<li><a href="#" class="about">About</li>
<li><a href="#" class="contact">Contact</li>
</ul>
{{> Template.dynamic template=template_name }}
</body>
template_name can be changed reactively using a template helper
Meteor.startup(function () {
Session.setDefault("templateName", "index")
});
Template.body.helpers({
template_name: function(){
return Session.get("templateName")
}
});
Template.body.events({
"click .home": function() {
Session.set("templateName", "index");
},
"click .about": function() {
Session.set("templateName", "about");
}
// ..
});
If the Session returns "about" then,
{{> Template.dynamic template="about"}} which is equivalent to {{> about}}.

How do i get an onload function on the body for a specific template using meteor.js?

I'm trying to get the following behavior for a certain template:
<body onload="someInitFunction();">
Let's say i have the following markup (i'm using mrt router, not iron-router, for {{renderPage}}):
// Main Template
<head>
<title>meteorite-knowviz</title>
</head>
<body>
{{> header}}
<div class="container-fluid">
<div class="row">
{{renderPage}}
</div>
</div>
{{> footer}}
</body>
That renderPage is the secondTemplate:
<template name="secondTemplate">
{{#if currentUser}}
<div class="col-md-2">
<div class="list-group">
<a class="list-group-item" href="{{render thirdTemplate please...}}">Third Template</a>
<a class="list-group-item" href="{{render fourthTemplate please...}}">Fourth Template</a>
</div>
</div>
// In this case let's say thirdTemplate gets rendered
{{render the choice taken above please...}}
{{/if}}
</template>
And within this template, depending on which link was clicked on, (in this case the third) there will finally be a thirdTemplate, which will show a data visualization with some help by a javascript framework, which will be in need of a <body onload="initFunction();">in order to display the data:
<template name="thirdTemplate">
<div class="col-md-5">
<h2>THIS!! section needs a "<body onload="initFunction();"> in order to work" ></h2>
</div>
<div class="col-sm-5">
<h2>Some other related content here</h2>
</div>
</template>
To sum up i have three questions:
1a. How could i get the third template to get a <body onload="initFunction();">
2a. In which way can i render different templates within the secondTemplate?
2b. Can i use a {{renderPage}} within this template even though this template is the renderedPage in the main template or should i do it in some other way?
In order to get the <body onload="initFunction();"> i had to do the following:
First add the following function to a .js file in the client folder:
Template.thirdTemplate.rendered = function() { // Template.thirdTemplate.created - also worked.
$('body').attr({
onload: 'init();'
});
}
This however got me an error saying that initFunction is not defined. In an standard html page my could work just fine, but in meteor i had to change my function from:
function initFunction(){
//what ever i wished to do
}
To:
init = function() {
//what ever i wished to do
}
Regarding the rendering of pages, iron-routing is the way to go since the router add on is not under development any more.
1a. How could i get the third template to get a <body
onload="initFunction();">
You probably want to call initFunction when the third template has been rendered, so just put your call to it in the rendered callback.
Template['thirsTemplate'].rendered = function(){
initFunction()
}
2a. In which way can i render different templates within the
secondTemplate?
2b. Can i use a {{renderPage}} within this template even though this
template is the renderedPage in the main template or should i do it in
some other way?
Listen for clicks on the links, and when one happen you manually render the desired template (possible with Meteor.render, if you need reactivity) and add it to the right node in the document. See this question.
It may be possibly to achieve with router (I don't know that package).
I think that what you want to use is the created callback, that will be called each time your template is created, and not the rendered callback, that would be called each time a change has caused the template to re-render.
Template.thirdTemplate.created = function(){
initFunction()
}
See the documentation for templates for other types of callbacks: http://docs.meteor.com/#templates_api

Meteor js - how to hook into the 'rendered' event of a recursive template?

I've got a situation where I'm rendering a Handlebars partial recursively based on a Mongodb tree structure, something like this :
<template name='menu'>
<ul class='menu'>
{{#each topLevelChildren}}
{{>menu-item}}
{{/each}}
</ul>
</template>
<template name='menu-item'>
<li>{{name}}
{{#if listChildren.count}}
<ul>
{{#each listChildren}}
{{>menu-item}}
{{/each}}
</ul>
{{/if}}
</li>
</template>
where the mongodb docs look like this :
{ _id : ObjectId("525eb7245359090f41b65106"),
name : 'Foo',
children : [ ObjectId("525eb60c5359090f41b65104"), ObjectId("525eb6ca5359090f41b65105") ]
}
and listChildren just returns a cursor containing the full docs for each element in the children array of the parent.
I want to do a bit of jquery makeup on the rendered tree, but I can't seem to hook into the 'rendered' event for the entire tree, something like
Template.menu-completed.rendered = function(){
// console.log('done!');
}
Trying this
Template.menu.rendered = function(){
console.log($('menu li'));
}
Not only doesn't this return the right results (brackets filled with commas), it also freezes web inspector (but not the app...).
Any help would be much appreciated !
This is an interesting problem :)
In the current version of Meteor (0.7.x) the rendered callback is called once when the template is first rendered, and then again once any partial inside the template is re-rendered. (This behavior will change when Meteor 0.8 - the shark branch or "Meteor UI" - lands.)
I've never tried doing things recursively, but it's going to be very hard to tell which callback you get is actually for the parent. One thing you might want to try is to start the recursive rendering in a template called menu-parent or something. That way, when the rendered callback is called for the first time (and as long as your data is loaded), you know the whole tree is rendered.
However, I suspect there might be a better way to do this. Generally, you should not be using jQuery to modify classes and attributes on DOM elements, as you'll confuse yourself as to what Meteor is doing and what jQuery is doing. Can you elaborate on what you are trying to achieve with the following, and I'll update my answer?
I need to find a specific anchor tag within the fully rendered template and add a class to it... and then add a class to each of its parents
Petrov, your code actually works for me, with just some minor modification to the jquery part. But perhaps I'm misunderstanding. Here is what I did:
Created a brand new project.
Added your template in the .html with minor change: <li><span class="name">{{name}}</span>
Created a new collection List.
Template.menu.topLevelChildren = function() { return List.find(); };
Then I added this handler:
Template.menu.rendered = function(e){
$('.name').each(function(i, e) {
console.log(e);
});
}
Now, upon render, for instance when new data is inserted into the List collection, I get a printout on the console like this:
<span class=​"name">​second​</span>​
<span class=​"name">​second2​</span>​
<span class=​"name">​second21​</span>​
<span class=​"name">​second22​</span>​
<span class=​"name">​third​</span>​
<span class=​"name">​third2​</span>​
<span class=​"name">​third21​</span>​
<span class=​"name">​third22​</span>​
<span class=​"name">​third​</span>​
<span class=​"name">​third2​</span>​
<span class=​"name">​third21​</span>​
<span class=​"name">​third22​</span>​
This is just a flat list of the tree elements I had added (where second2 is nested in second, and second2x is nested in second2, etc.). So from what I understand you could just take the selection coming back from jquery and find the tag you are looking for and change its class, as you wanted. Please let me know if I misunderstood something.
Complete code below.
test.html:
<head>
<title>test</title>
</head>
<body>
{{> menu }}
</body>
<template name='menu'>
<ul class='menu'>
{{#each topLevelChildren}}
{{>menu-item}}
{{/each}}
</ul>
</template>
<template name='menu-item'>
<li><span class="name">{{name}}</span>
<ul>
{{#each children}}
{{>menu-item}}
{{/each}}
</ul>
</li>
</template>
test.js:
List = new Meteor.Collection("List");
if (Meteor.isClient) {
Template.menu.topLevelChildren = function() {
return List.find();
};
Template.menu.rendered = function(e){
$('.name').each(function(i, e) {
console.log(e);
});
}
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}

Resources