How do I create multi-page applications with Meteor? - javascript

I am new to Javascript and just started fiddling around with Meteor out of curiosity. What really surprises me, is that it seems that all HTML content gets combined into a single page.
I suspect there is a way to introduce some handling of URLs directing to special pages. It seems that the "todo" example is capable of doing this via some kind of Router class. Is that the "canonical" way of URL handling?
Assuming I can handle URLs, how would I structure my HTML code to display separate pages? In my case they could each have completely separate sets of data, so no HTML code needs to be shared at all.

Jon Gold's answer used to be correct, but as of Meteor 0.5.4:
Work has now shifted to Iron Router. Please consider using IR instead of Router on new projects!
Thus, the current "canonical" way to do this is probably to use IronRouter.

As far as I am aware, there is currently no out of the box way to do this.
What I suggest to do, is to use Backbone.js smart package.
Backbone.js comes with the push-state Router, and if the user's browser doesn't support that it will fallback to hash urls.
In your meteor app directory type this meteor add backbone.
Then somewhere in your client-side code create a Backbone.js Router like so:
var Router = Backbone.Router.extend({
routes: {
"": "main", //this will be http://your_domain/
"help": "help" // http://your_domain/help
},
main: function() {
// Your homepage code
// for example: Session.set('currentPage', 'homePage');
},
help: function() {
// Help page
}
});
var app = new Router;
Meteor.startup(function () {
Backbone.history.start({pushState: true});
});
Then somewhere in your Handlebars template, you can create a helper that will render a page based on the value set in Session's "currentPage".
You can find more information about backbone.js router here: http://backbonejs.org/#Router
Also relevant information on how to create a Handlebars helper method in Metoer here: http://docs.meteor.com/#templates
Hope this helps.

Meteor-Router makes this really easy. I've been using it in some apps I've been building with Telescope as a good reference. Have a look at Telescope's router.js
To use it…
mrt add router
In client/router.js:
Meteor.Router.add({
'/news': 'news', // renders template 'news'
'/about': function() {
if (Session.get('aboutUs')) {
return 'aboutUs'; //renders template 'aboutUs'
} else {
return 'aboutThem'; //renders template 'aboutThem'
}
},
'*': 'not_found'
});
In your template…
<body>{{renderPage}}</body>

I found the same problem. When the code gets bigger it is difficult to keep the code clean.
Here goes my approach to this problem:
I separate the different html pages as I would do with another web framework. There is an index.html where I store the root html page. And then for each big functional part I create a different template and place it in one different html. Meteor then merges them all. Finally I create a session variable called operation where I define what to show at each time.
Here goes a simple example
index.html
<head>
<title>My app name</title>
</head>
<body>
{{> splash}}
{{> user}}
{{> debates}}
</body>
then in splash.html
<template name="splash">
{{#if showSplash}}
... your splash html code goes here...
{{/if}}
</template>
then in user.html
<template name="user">
{{#if showUser}}
... your user html code goes here...
{{/if}}
</template>
and so on ...
In the javascript code then I check when to print each template using the Session variable, like this:
Template.splash.showSplash = function(){
return Session.get("operation") == 'showSplash';
}
Finally the Backbone Router manages this Session variable
var DebateRouter = Backbone.Router.extend({
routes: {
"": "showSplash",
"user/:userId": "showUser",
"showDebates": "showDebates",
// ...
},
splash: function () {
Session.set('operation', 'showSplash');
this.navigate('/');
},
user: function (userId) {
Session.set('operation', 'showUser');
this.navigate('user/'+userId);
},
// etc...
});
I hope this pattern is helpful for other Meteor developers.

This is my hacky solution to routing :
https://gist.github.com/3221138
Just put the page name as the template name en navigate to /{name}

Related

Global Variable in Javascript for Laravel Routes - Is this a good idea?

I've created some code using a View Composer where I am passing my Route Collection through to the front end on all views, so I can access all of my laravel routes in Vuejs via the route named associated with them.
For example, to upload an image using a vue component, instead of passing my upload route into the Vue Component, it is listed as a part of a global variable:
var uploadRoute = _.find(globalRoutes, function(route) { return route.name == 'route-name.image.upload' });
$.post(uploadRoute, data) ... etc
My question is...is this sensible? I'm publically publishing my entire app's routes.
Thanks
I think your hunch about exposing your entire apps routes is legit. IMO you should explicitly pick out the routes that you need. So in thise case, you should only expose route-name.image.upload. You could create a tiny helper function to look up routes and output them along with the URL as JSON.
function json_routes(array $routes)
{
$return = [];
foreach($routes as $route)
{
$return[$route] = route($route);
}
return new \Illuminate\Support\HtmlString(json_encode($return));
}
And the, in your main view:
var routes = {{ json_routes(["route-name.image.upload"]) }};
Getting a route is simple:
routes['route-name.image.upload'];
This is the most basic exaple I can think of. You can optimize it quite a bit. Just some ideas:
Place the routes in a central place, fx. a config element: json_routes(config('app.json_routes'))
Build a command that generates a static .json file so that you don't iterate through the routes on each page load. Remember to re-generate when you add more routes.
Create a function instead of an object to get the route. That allows you to build in logic and gives a more Laravel-like feel in your js: function route(path){ return window.routes.hasOwnProperty(path) ? window.routes[path] : null ;}
(Advanced) Re-write Laravels router logic and hook into the options array, allowing you to do something like Route::get('dashboard', '...', ['as'=>'dashboard', 'expose'=>true]);, then dynamically generate the before mentioned json-file on all routes with the expose option.

Ember.js - Setting a model and routing dynamically via API data

So I'm working on building a dynamic model for a project that reacts to data sent from an API. The api will return, among other things, what your location should be and this in turn becomes the url. So, eg:
{
location: 'xyz'
(...)
}
So currently my router will transition to the right route dynamically. But I still have to hardcode each route ( IndexRoute, LocationXYZRoute, LocationABCRoute, etc).
My goal is to create a single route that handles things dynamically. We'll call it App.LocationRoute and my routes would look something like:
App.Router.map(function() {
this.resource(':location', function() {
this.route(':subLocation')
}
}
Now, I have two architectural questions:
1) Whats a good way to handle this sort of dynamic routing? (I've read through the guide about dynamic routing using the ':post_id' type example, but I think I need a more holistic example to really grasp it.
2) The API sends back a whole host of other data as well. I want to add this to the route's model but I also have some other static models. Doing...
this.controllerFor(location).set('content', APIdata);
... works, but it does not set for routes currently using static models. I tried something like:
this.controllerFor(location).set('apiData', APIdata);
and...
this.controllerFor(location).set('model:apiData', APIdata);
... but neither worked.
Any suggestions?
1) Yes, you should use dynamic segment
this.resource('location', { path: '/location/:location_id' }, function() {
this.resource('sublocation', { path: '/sublocation/:location_id' });
});
2) Are you using ember-data? You could check sideloaded data. Anyway, you could read the json and set the payload of each entity for each specific route.
this.controllerFor('location').set('content', APIdata.location);
this.controllerFor('user').set('content', APIdata.user);
People could help you better, if you separate your questions and create a http://emberjs.jsbin.com/ with isolated each specific case?

EmberJS optional parameter

I'm working on a rather large EmberJS app and I've come to the part where I want to be able to retrieve a tracking ID in the URL from any source. I've looked into Ember.Route.serialize, and a couple of similar questions here on SO, but no reply seems to properly solve the problem (i.e., I have been unable to implement them on my site). The serialize-hook for example is only called on transitions, and to make sure the parameter is read, you'd have to expressively specifiy child-routes to pick it up.
I would like to have a route like this:
mysite.com/#!/ --> start page.
mysite.com/#!/19 --> start page with tracking ID 19.
mysite.com/#!/about --> about page.
mysite.com/#!/about/19 --> about page with tracking ID 19.
It seems that currently, to be able to fetch a parameter from any URL, you'd have to manually create a child route for every single route to retrieve it in the router:
App.Router.map({
this.resource("index", { path: "/" }, function() {
this.resource("indexTid", { path: ":tId" });
});
this.resource("about", { path: "/about" }, function() {
this.resource("aboutTid", { path: ":tId" });
});
[ ... ]
However, this seems incredibly tedious and with so many routes, having to add individual route handlers (App.AboutTidRoute = Ember.Route.extend [...])...
I'm figuring someone's had this problem before. How do you best tackle this problem?
Alternative solutions are welcome, but note that the URLs should be possible do give to partners and the tracking should follow without further work put in from their side.
Thanks for your time.
Best regards,
dimhoLt
PS. My current solution uses this URL mysite.com/?trackingId=19#!/about, which solves the problem-ish by setting a cookie on the server, but isn't very pretty. A better solution is greatly appreciated.
Beta/canary versions of Ember.js(1.4.0-beta.2) include support for query params. The guides page can be found here.
You'll have to use a beta or canary version and manually enable the feature to use it:
ENV = {FEATURES: {'query-params-new': true}};
That guide includes an example of setting/accessing a globally available query param:
App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_VIEW_LOOKUPS: true
});
App.ApplicationController = Ember.Controller.extend({
queryParams: ['iamglobal'],
iamglobal: 'foo'
});
App.IndexController = Ember.Controller.extend({
needs: 'application',
iamglobal: Ember.computed.alias(
'controllers.application.iamglobal'
)
});
jsbin example

Backbone Js Load application in parts

I am building a small application in backbone.js
It consists of a router, a number of basic views and models etc, and a blog.
With regards to the blog, the contents of the posts will be held in a db, for the other pages though, the content is contained within underscore templates. This is what my router looks like:
$(function() {
var Router = Backbone.Router.extend({
routes: {
'': 'home',
'home' : 'home',
'portfolio' : 'portfolio'
}
});
var homeView = new HomeView({ el: $("#container") });
var portfolioView = new PortfolioView({ el: $("#container") });
var router = new Router();
router.on('route:home', function () {
homeView.render();
});
router.on('route:portfolio', function () {
portfolioView.render();
});
Backbone.history.start();
});
Currently I just have the home view and the portfolio view. My concern is, will the entire app be loaded when going to my site address? Although its a small application is there a way to break it up into pieces so for example, the portfolioview and all the data (the images are my main concern) associated with it, will only be downloaded when navigating to 'portfolio'? What is the best way to handle this, and is it even necessary for a relatively small app (a few pages and a blog)? Or am I in fact mistaken and backbone already does this?
Thanks
Totally unnecessary to worry about this for a small app, or even for most large apps. Just send all the JS/templates down the wire (ideally minified and concatenated in production). The point of single-page apps is that you don't have to hit the network to pull in HTML/JS/other assets when navigation occurs. You definitely shouldn't be trying to lazy-load your templates across the network.
As far as images go, they'll already come down the network only when you actually add an <img> tag to the DOM.

Verbose Logging in Ember

I'm trying to wrap my head around Ember at the moment, but all the magic is making this difficult.
I've set LOG_TRANSITIONS: true and Ember.LOG_BINDINGS = true; which gives me some minimal logging to the console, but I really need more than that.
I'm particularly struggling with seeing what's going on when Ember is automagically creating Controllers, Views and Templates.
Is there a way to log this aspect of the framework - to see where Ember is looking for Templates/Views/Controllers and when it is creating one on its own volition.
For example, I have the following routes set up:
App.Router.map(function() {
this.route("example_items", {path: "/"});
});
with:
App.ExampleItemsRoute = Ember.Route.extend({
model: function() {
return App.ExampleItem.find();
}
});
Ember renders my ApplicationController and its application.handlebars template:
<header class="page-header">
<h1>Application Template</h1>
</header>
{{outlet}}
But fails to render my example_items.handlebars template. I get no exception or warning, and if I check the DOM, I can see ember has created a generic view in its place.
The bindings logging shows me that Ember has transitioned to example_items, but it seems it hasn't used either my ExampleItemsController, ExampleItemsView or template.
How can I debug a situation like this if I receive no errors or messages?
Edit:
App.ExampleItems View:
App.ExampleItemsView = Ember.CollectionView.extend({
templateName: 'example_items'
});
And App.ExampleItemsController:
App.ExampleItemsController = Ember.ArrayController.extend({
});
I'm particularly struggling with seeing what's going on when Ember is automagically creating Controllers, Views and Templates.
Is there a way to log this aspect of the framework - to see where Ember is looking for Templates/Views/Controllers and when it is creating one on its own volition.
Yes. With the latest ember you can now LOG_ACTIVE_GENERATION to see console.log output whenever ember generates something for you.
Another new setting that might be helpful is LOG_VIEW_LOOKUPS
Here's your problem: CollectionView won't use your template. It takes an array as its content property (usually set up as a binding to the controller) and creates childViews manually. Without a content set it'll appear as a blank view.
If you add classNames: ['my-view'] to your view definition, you should see that the view it's instantiating and inserting is actually your view class, just empty. Add contentBinding: 'controller' and it should render itemViews for each item in the array, as well.

Categories