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}}.
Related
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.
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.
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.
I am using Meteor 0.7.0.1 with Spark and I have the following template that used to popover content, and the template is inserted at each post items.
<template name="postLinks">
<a href="#" id="ui-popover-container-{{id}}" class="popover-list-trigger dropdown-toggle" data-toggle="dropdown">
<i class="icon-calendar"></i>
</a>
{{#if poppedUp}}
<div id="link-popover-wrapper" style="display:none">
<ul class="link-popover">
{{#each linkOptions}}
<li><a tabindex="-1" class="link-action" id="link-{{value}}" href="#">{{label}}</a>
</li>
{{/each}}
</ul>
</div>
{{/if}}
</template>
I want to print the div "link-popover-wrapper" only once the first time the template loaded. Subsequent template inserts in post items the div won't inserted so only one hidden div is in the page. I handled it using a template variable in following way.
var _poppeUp = true;
Template.postLiks.helpers({
poppeUp : function () {
if (_poppeUp) {
_poppeUp = false;
return true;
}
return false;
}
});
It is working for the first time the page is loaded and only one instance of the div "link-popover-wrapper" is in the page. The problem is when a post is updated and page is re-rendered the template variable still set to false and the div is not printed in the page. I wanted to reset that template variable when a page is re-rendered.
Is there a way to overcome this issue in Meteor ?
Just add code that does the opposite on page render:
Template.postLinks.rendered = function() {
if (!_poppedUp)
_poppedUp = true;
You didn't ask, but this strikes me as an odd way to implement this. Rather than {{#if poppeUp}}, why not {{#with getFirstLinkPopoverWrapper}} and then define a getFirstLinkPopoverWrapper helper that returns just the one link-popover-wrapper div that you want shown? Then you never need to worry about re-renders or tracking some variable.
EDIT: Alternate implementation:
You could simply use jQuery to tell you whether or not an element of id link-popover-wrapper already exists on the page:
Template.postLinks.helpers({
poppedUp: function() {
return $("#link-popover-wrapper").length !== 0;
}
}
That's it. If no such element exists, the length of the jQuery object is zero and the statement evaluates as false and false is returned. Otherwise true is returned and your template adds the element.
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
});
}