How to render templates in one region with Iron Router (Meteor) - javascript

My goal is to dynamically create a navigation menu on a Meteor website.
I have a classic menu with static links and the yield where I want the other links to be inserted when needed :
<nav id="menu">
Link 1
Link 2
{{> yield "navLinks"}}
</nav>
-
// module_posts.js
Router.onAfterAction(function(){
if (userIsAdmin()) {
this.render('postsMenu', { to: 'navLinks' });
}
});
// module_users.js
Router.onAfterAction(function(){
if (userIsAdmin()) {
this.render('usersMenu', { to: 'navLinks' });
}
});
The problem is for example, when an admin is connected, each module (posts, roles, categories...) should insert its own links into that region, but what happens is that each module is rendering over the previous one and removing the links added by another module, so the region seems to only accept one template to be rendered in.
Maybe I am doing it wrong, so please give me a clean solution.
-- EDIT --
Okay, maybe a better explanation :
I have several modules (posts, users, roles, etc) that can be enabled or disabled, each of them has a navigation menu to manage it.
Now I want to display their menu in one unique region named {{nav}}.
So I would have to write code somewhere in each module to append their menu in the {{nav}} region if the module is enabled.
I really want to use templates to do that because I could build logics directly into them instead of using collections to store links.

The reason why you are seeing the issue that you are is because of the fact that you are using the Router.onAfterAction() function to render your menu templates. The problem with this is that the Router.onAfterAction() function applies globally to all routes and runs whenever a new route is run. This is why you are only seeing the navigation links from the final defined navigation links definition, which in the case of your question above would be the usersMenu navigation links. Instead, you want to define your navigation menu template rendering logic for each route that you want it to differ on. Also, you do not want to use the onAfterAction() function, because this is intended to be used for post-processing once your route has actually been run. In order to achieve what you would like, you would do the following:
Router.route('/admin/posts', {
...
yieldRegions: {
'postsMenu': {to: 'navLinks'}
}
...
});
Router.route('/admin/users', {
...
yieldRegions: {
'usersMenu': {to: 'navLinks'}
}
...
});
So, as you can see, we have defined a route definition for each of your unique routes as specified in your question, one for admin posts and one for admin users. In order to specify specific templates to be rendered into named yields for a given route, you should use the yieldRegions route option. In it, you specify the name of each template that you would like to render into which named yield region. This is where you specify your templates from your question.
If you would like to read about all of the route specific options that are available when defining a route definition, please take a look here.

Related

Change / unregister Handlebars helper (Meteor)

Once I register a helper function for Handlebars using Handlebars.registerHelper(), is it possible for me to change and/or remove the helper? Can I just use registerHelper() again to overwrite the current helper, or is there such a thing as Handlebars.unregisterHelper()? Or should I use a different approach if I need a helper to change during an application?
The use case for me is with the Iron Router plugin for Meteor. I am using a layoutTemplate as the general structure of my page. I wanted to use a helper in the layout template right before I yield the main content of the page body (via a <template>, per se) so that each individual template can define its own page title but not have to specify the location in the page every time. For example, my layout template could look like this:
{{pageTitle}}
{{yield}}
And then in the .js file for the rendered template, I would use the following to fill in the {{pageTitle}} placeholder:
Handlebars.registerHelper("pageTitle", function() {
return "My Page Title";
};
Perhaps there is an alternative way to solve this problem.
What you can do is something like this
Handlebars.registerHelper("pageTitle", function() {
return Session.get('pt');
};
function changePageTitle(str){
Session.set('pt', str);
}
Meteor, being reactive, should update the page when a session variable changes. When you switch to another page, simply run changePageTitle.

Backbone.js: Routing for nested views

I'm trying to figure out following scenario:
Lets say that I have two views: one for viewing items and one for buying them. The catch is that buying view is a sub view for viewing.
For routing I have:
var MyRouter = Backbone.Router.extend({
routes: {
'item/:id': 'viewRoute',
'item/:id/buy': 'buyRoute'
}
});
var router = new MyRouter;
router.on("route:viewRoute", function() {
// initialize main view
App.mainview = new ViewItemView();
});
router.on("route:buyRoute", function() {
// initialize sub view
App.subview = new BuyItemView();
});
Now if user refreshes the page and buyRoute gets triggered but now there is no main view. What would be best solution to handle this?
I am supposed that the problem you are having right now is that you don't want to show some of the stuff inside ViewItem inside BuyView? If so then you should modularized what BuyView and ViewItem have in common into another View then initialize it on both of those routes.
Here is a code example from one of my apps
https://github.com/QuynhNguyen/Team-Collaboration/blob/master/app/scripts/routes/app-router.coffee
As you can see, I modularized out the sidebar since it can be shared among many views. I did that so that it can be reused and won't cause any conflicts.
You could just check for the existence of the main view and create/open it if it doesn't already exist.
I usually create (but don't open) the major views of my app on booting up the app, and then some kind of view manager for opening/closing. For small projects, I just attach my views to a views property of my app object, so that they are all in one place, accessible as views.mainView, views.anotherView, etc.
I also extend Backbone.View with two methods: open and close that not only appends/removes a view to/from the DOM but also sets an isOpen flag on the view.
With this, you can check to see if a needed view is already open, then open it if not, like so:
if (!app.views.mainView.isOpen) {
//
}
An optional addition would be to create a method on your app called clearViews that clears any open views, perhaps with the exception of names of views passed in as a parameter to clearViews. So if you have a navbar view that you don't want to clear out on some routes, you can just call app.clearViews('topNav') and all views except views.topNav will get closed.
check out this gist for the code for all of this: https://gist.github.com/4597606

How to make a nav-bar in logic-less template engine like mustache

I am trying to understand the concept behind logic-less temlpates, but I'm finding myself hitting a wall.
I want to implement a simple navigation bar, e.g. "Home, About, Contact" links at the top of every page, and the "current" one should be highlighted with a different class (I'm using bootstrap). But how can I do this in a sensible fashion? So far I have:
Move the nav to every template, and copy the whole thing (not DRY, ugly).
Use keys instead of values, i.e. render('home', { on_home_page: true }); with <a href="/" {{#on_home_page}}class="active"{{/on_home_page}}>Home</a>. This is better, but still annoying that I have to create N variables to hold 1-variable worth of data.
create the nav in the controller, i.e. pass in { 'Home': {link: '/', active: false}, 'About: {link: '/about', active: true} } or similar. I dislike this because it has the opposite problem of logic-less templates. Now I have HTML-ful controllers...
Given the above options, I like (2) the best. But what I would prefer is some way to have a single variable to check, like:
// controller
render('about', {active: 'about'});
render('home', {active: 'home'});
// mustache nav
<a href="/" {{#if active == 'home'}}class="active"{{/if}}>Home</a>
<a href="/about" {{#if active == 'about'}}class="active"{{/if}}>About</a>
I'm sure this comes up all the time for mustache experts --- what's the best way to deal with it?
There's no way to deal with this using vanilla Mustache.
You have two options that let your JSON data and template stay clean:
1- using Mustache, write a helper function so that you can use it like this:
var selected_item = function(param) {
if (param == this.active) {
return 'active';
}
};
(disclaimer: I wrote this helper out of the top of my head, might not work as-is but I think you get the point)
Home
About
then mix-in that helper into your JSON data, best way probably being overloading Mustache.render so that every call to render adds your helper into the mix. Notice that leaving the class="" part outside of the helper allows you to have multiple different classes on each menu item while still having your "logic" for the active part.
2- switch to Handlebars which allows for this kind of basic logic. Handlebars is a superset of Mustache, stuff that works on vanilla Mustache will directly work in Handlebars so upgrading is easy. Be careful though, that once you have upgraded and modified your templates to work with Handlebars there's no turning back.
I just wrote a post on this. Click here or on the banner:
The basic idea for doing this with vanilla moustache and NodeJS is like so:
app.get('/Portfolio', function(req, res) {
res.render('portfolio.html', {
Portfolio: 'class="current"',
});
});
app.get('/Blog', function(req, res) {
res.render('blog.html', {
Blog: 'class="current"',
});
});
Notice how each separate route sends a different moustache variable. If a user goes to /Portfolio the {{{Portfolio}}} variable will exist but the {{{Blog}}} variable won't.
Using this idea, set up your navigation links like so:
<div id="nav">
<ul>
<li><a href="/Portfolio" {{{ Portfolio }}}>Portfolio</a></li>
<li><a href="/Blog" {{{ Blog }}}>Blog</a></li>
</ul>
</div>
Create the .current class
#nav li a.current {
font-weight: 900;
}
Now the link will be highlighted dependent on the routing call made.

Backbone showing/hiding rendered views best practices

New to using Backbone and have a very simple application. Basically there are Clients and ClientItems. I have a view to show all Clients and if you click on a Client you get taken to their ClientItems. Going to this ClientItems view should just hide the Clients view and going back to Clients should hide ClientItems. Now, in my render() function for each view, it is going through the collections and dynamically adding stuff to the page. When I go back and forth between the two (using the back button) I don't really need to fully render again as all the data is there in the page, just hidden. Where should this logic go? Right now I have it in the render() function but it feels sloppy, what is the preferred way of handling this?
We are using a global variable App with several common function used across application:
var App = {
initialize : function() {
App.views = {
clientView : new ClientsView(),
clientItemView : new ClientsItemsView()
}
},
showView: function(view){
if(App.views.current != undefined){
$(App.views.current.el).hide();
}
App.views.current = view;
$(App.views.current.el).show();
},
...
}
And then I use this App from other parts of application:
App.showView(App.views.clientView);
IntoTheVoid's solution is good – it's nice to have a single place to hide/show views. But how do you activate the logic?
In my experience, routers are the best place for this. When a route changes and the appropriate function is called, you should update the active, visible view(s).
What if you need multiple views to be visible at once? If you have a primary view that always changes when the route changes, and multiple subsidiary sticky views, you need not worry. But if it's more complex than that, think of creating a ComboView that neatly packages all the relevant views into one containing el node. That way the above logic still works, and your router functions are not littered with logic for managing what views are visible at the moment.

Routing for flexible JavaScript Single-Page-App?

I'm building a single page web app (because I want flexibility and speed when moving across pages/states) but I'm struggling with routing / urls ...
In the traditional paradigm I would have urls such as:
example.com/tools/population-tool/#currentYear=1950
example.com/tools/income-tool/#country=usa
example.com/nice-story/
example.com/nice-chapter/nice-story/
Now I'd like to replace this with a Router (for example using Backbone) that loads templates and controllers for the corresponding routes.
I'm thinking about having a pages object that stores the necessary page information:
pages : {
tools : {
template : "#tools",
breadcrumb : ["Home","Tools"]
}
nice-story : {
template : "#nice-story",
breadcrumb : ["Home","Stories","Nice Story"]
}
}
With a router, I'd now like load the right content and page state, given a url like:
example.com/#!/tools/population-tool/?currentYear=1950
or like this if not using Hashbang:
example.com/tools/population-tool/?currentYear=1950
How would you organize this routing so that the url scheme makes sense while still being flexible and allow for redirects and new query string paramaters?
This is not a complete answer to your question, but a few tips on Backbone...
You may want to define a method like loadPage() on your router which can empty and replace your main page container with a view that corresponds to each "page" in your app. Each route action can call that to load up the right view.
If you will be using pseudo query strings, make sure to add a matcher for them explicitly in your Backbone routes. For example:
'/tools/population-tool/?*params'
That will call your route action with the entire params string as the first parameter. You'll need to parse that...

Categories