May 09, 2021 Meteor
3. The vocabulary of the router
4. Route: Map the URL to the template
5. The name of the routing rule
8. First contact responsiveness
Now that we've created a post list page (which was ultimately submitted by the user), we'll also need to add a separate post page to provide users with comments on the corresponding posts.
We want
to be able
to access each individual post page via a fixed link, in the form of http://myapp.com/posts/xyz
http://myapp.com/posts/xyz
xyz
is
_id
identifier), which is unique for each post.
This means that we need some routes to see what the path is in the browser's address bar and display the correct content accordingly.
Iron Router is a routing package developed specifically for Meteor Apps.
Not only does it help routes (set paths), it also helps filter (assign jumps to those paths), and even manage subscriptions that control what data the path can access. ( Note: Iron Router was developed by Tom Coleman, one of the authors of this book, Discover Meteor.) )
First, let's install this package from Atmosphere:
meteor add iron:router
Terminal terminal
This command is to download and install the Iron Router package into our app so we can use it.
Note that you may need to restart your Meteor app before you can use the package smoothly (you can stop the
ctrl + c
and
meteor
to start it again).
In this chapter we will be exposed to the different functions of many routers. I f you have some practical experience with rails-like frameworks, you may already be familiar with most of these lexical concepts. But if not, here's a quick vocabulary for you to learn about:
/terms_of_service
or dynamic
/posts/xyz
or even contain query
/search?keyword=meteor
/
For more information about Iron Router, check out the full documentation above GitHub .
So far, we've used some fixed templates ( such as .
{{> postsList}}
to lay out for us.
So while the content of our app can be changed, the basic structure of the page has changed: a header, which is below the list of posts.
Iron Router is responsible for what
<body>
HTML and body tags, and let's get rid of this shackle.
So instead of defining the contents of the label ourselves, we'll specify the router to a layout
{{> yield}}
that contains the label.
This
{{> yield}}
automatically render the appropriate template corresponding to the current line (from now on, we will specify this particular template called "route templates"):
Layouts and templates
We'll start building our
{{> yield}}
and adding the hashtags.
First,
main.html
from the main.html
<body>
put its contents in their common
layout.html
client/templates/application
folder).
main.html
and cut the content and it should look like this:
<head>
<title>Microscope</title>
</head>
client/main.html
The newly created
layout.html
will now contain the app's outer layout:
<template name="layout">
<div class="container">
<header class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" href="/">Microscope</a>
</div>
</header>
<div id="main" class="row-fluid">
{{> yield}}
</div>
</div>
</template>
client/templates/application/layout.html
You'll notice that
yield
replaced the
postsList
helper.
When you're done, our browser tab displays Iron Router's default help page.
This is because we haven't told the
/
do with the / URL, so it just renders an empty template.
Next, we can restore the previous root
/
map to
postsList
template.
Then we create a
/lib
the root and create
router.js
it:
Router.configure({
layoutTemplate: 'layout'
});
Router.route('/', {name: 'postsList'});
lib/router.js
We have accomplished two important things.
First, we've told the router to use the layout template we
layout
as the default layout for all routes.
Second, we have defined a routing
postsList
and mapped to
/
All the files you put in the
/lib
folder make sure they are loaded first (perhaps except for the smart package) while your app is running.
This is a good place to place auxiliary code that you need to be ready to use.
Note, however, that because the
/lib
/client
/server
this means that its code will exist on both the client and the server.
Here we first clear some ambiguity. W
e have a routing rule called
postsList
and we also have a template
postsList
What's going on here?
By default, Iron Router specifies a template with the same name for this routing rule. I
f the path parameter is
path
specified, it also specifies a path with the same name based on the name of the
routing rule.
For example, in the settings above, if we
path
parameter,
/postsList
automatically get the
postList
template.
You may wonder why we need to start with routing rules. T
his is because some of Iron Router's features require routing rules to generate link information for the app.
One of the most common is
{{pathFor}}
Spacebars helper, which needs to return the URL path of the routing rule.
We want the home page to link to the post list page, so in addition to
/
static / URL, we can also use Spacebars helper.
Although they work the same, this gives us more flexibility, and if we change the mapping path of the routing rules, the helper can still output the correct URL.
<header class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" href="{{pathFor 'postsList'}}">Microscope</a>
</div>
</header>
//...
client/templates/application/layout.html
If you're deploying the current version of the app (or start using the link above), you'll notice that the list will be empty for a while until all posts appear completely.
This is because the first time you load a page, you don't have
posts
is complete, when you've crawled the post's data from the server, for the post to appear on the page.
This should have a better user experience, such as providing some visual feedback to let the user know that the data is being read so that the user can continue to wait.
Fortunately, Iron Router gave us an easy way to implement it.
Let's put the subscription on the return of
waitOn
Let's
posts
subscription from .js
main.js
the routing file:
Router.configure({
layoutTemplate: 'layout',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.route('/', {name: 'postsList'});
lib/router.js
We're talking about every route to the
site
(we only have one now, but we'll add more right away!).
We've all subscribed
posts
subscription.
The key difference between this and what we did
main.js
which should now be empty and removable, is that Iron Router can now tell when the route is ready -- that is, when the route gets the data it needs to render.
If we just show an empty template, we know that
postsList
route is ready and can't do anything.
Fortunately, Iron Router brings its own method of slowing down the display of the template, displaying a
loding
load template before the route call template is ready:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.route('/', {name: 'postsList'});
lib/router.js
Note that because we have a
waitOn
level, this only happens once the user first accesses your app.
After that, the data has been loaded into the browser's memory and the router does not need to wait for it again.
The last piece of puzzle is to load the template. W
e'll use the
spin
package to create a handsome animated loading screen.
Add
meteor add sacha:spin
and then create the loading template in the
client/templates/includes
folder:
loading
<template name="loading">
{{>spinner}}
</template>
client/templates/includes/loading.html
Note
{{> spinner}}
is a template
spin
in the spin package.
Although this part comes from outside of our app, we can use it just like any other template.
This is a great way to wait for your subscription, not only for the user experience, but also because it ensures that the data is immediately reflected on the template. This eliminates the problem that the underlying data must be available before the template that needs to be processed is rendered, which often requires complex solutions.
Wait for a subscription to the post
Responsiveness is a core part of Meteor, and while we don't really have access, our loading templates give us the opportunity to get in touch with the concept.
Redirecting to a load template is fine if the data has not been loaded, but how does the router know when the data is loaded and then the user should redirect back to the original page?
What we just said is a reflection of responsiveness, but don't worry, you'll soon learn more about it.
Now that we've seen how
postsList
template, let's establish a route to display the details of a post.
Here's a problem: We can't continue to define routing rules and path mapping separately, because there may be thousands. So we need to establish a dynamic routing rule and have the routing rules show the posts we want to see.
First, we'll create a new template that simply renders the same template we use in the post list.
<template name="postPage">
{{> postItem}}
</template>
client/templates/posts/post_page.html
We'll add more elements to this template in the future (such as comments), but now it's just going to be the shell for placing the . . .
{{> postItem}}
We're going to create another routing rule, this time the URL path
/posts/<ID>
to
postPage
template:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.route('/', {name: 'postsList'});
Router.route('/posts/:_id', {
name: 'postPage'
});
lib/router.js
This special
:_id
tells the router two things: First, to match any route that conforms to the
/posts/xyz/
("xyz" can be any character).
Second, whatever is inside the "xyz", it is placed
params
_id
the property.
Please note that we only use
_id
for convenience only.
The router has no way of knowing whether you
_id
through an actual computer or just through random characters.
We're now routed to the correct template, but we're still missing one
_id
show through the _id of this post, but the template doesn't have a clue.
So what if we solve this problem?
Thankfully, the router has a clever built-in solution: it allows you to specify a data source. T hink of the data source as a delicious cake filled with templates and layouts. Simply say, it's your template to fill in:
In our case, we can get the _id from the
_id
and use it to find our posts to get the right data source:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.route('/', {name: 'postsList'});
Router.route('/posts/:_id', {
name: 'postPage',
data: function() { return Posts.findOne(this.params._id); }
});
lib/router.js
So every time a user visits this road by the rules, we find the right post and pass it on to the template.
Keep in
findOne
returns a post that matches the query, and
id
provide an id as an argument, which can be
{_id: id}
In the
data
method of the routing
this
to the currently
this.params
path
in path
:
prefix).
By setting the
template's data source,
you can control the value of
this
template helper.
This work is usually done implicitly
{{#each}}
automatically sets the corresponding data source to each current item that is iterating:
{{#each widgets}}
{{> widgetItem}}
{{/each}}
Of course, we can also use the #with to explicitly operate, it is like simply saying "take this object and provide it to the template application below". For example, we can write something like this:
{{#with myWidget}}
{{> widgetPage}}
{{/with}}
Therefore, the same effect can be achieved by passing the data source as an argument to the template call, so the previous block of code can be rewritten as:
{{> widgetPage myWidget}}
To learn more about data sources, we recommend reading our blog posts.
Finally, we'll create a new Comment button and point to the correct post page.
We can do something like
<a href="/posts/{{_id}}">
pattern, but it's a little more reliable to use route Helper.
We've named the
postPage
so we can use the helper for the hashtag:
{{pathFor 'postPage'}}
<template name="postItem">
<div class="post">
<div class="post-content">
<h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
</div>
<a href="{{pathFor 'postPage'}}" class="discuss btn btn-default">Discuss</a>
</div>
</template>
client/templates/posts/post_item.html
But wait, how exactly does the router know exactly where to get the xyz path from
xyz
/posts/xyz
After all, we didn't
_id
to it.
Iron Router proved smart enough to discover it on its own.
We tell the router to
postPage
routing rule, and the router knows that some parts of this
_id
that's how we
path
Therefore, the router will look for this information in
this
the context of the context of the
{{pathFor 'postPage'}}
_id
And in this example,
this
object corresponds to a post, which is where we're looking for
_id
property.
Alternatively, you can clearly specify where to look for by passing
_id
F
or example,
{{pathFor 'postPage' someOtherPost}}
In practice, we'll use this mode if we want to get a link to the previous one or the latest in the list of posts.
To see if it's working properly, let's browse the post list page and click on one of the "Discuss" links. You should see something like this:
A separate post page
What we need to know here is that these URL changes are due to the fact that HTML5 pushState is being used .
The router accesses the interior of the site by processing URLs clicks, which prevents the browser from jumping out of our app, not just to change the state of the app as necessary.
If everything works, the page should change instantaneously. I n fact, sometimes things change too quickly and may require some type of transition page. This is beyond the scope of this chapter, but it is an interesting topic.
Let's not forget that routing works in two ways: changing the URL of the page we visit can also show a new page where we change the URL. So we need to fix the situation when a user enters the wrong URL.
Fortunately, Iron Rounter can solve
notFoundTemplate
option.
First, let's set up a new template to display a simple 404 error message:
<template name="notFound">
<div class="not-found jumbotron">
<h2>404</h2>
<p>Sorry, we couldn't find a page at this address. 抱歉,我们无法找到该页面。</p>
</div>
</template>
client/templates/application/not_found.html
We then point Iron Rounter to this template:
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: 'notFound',
waitOn: function() { return Meteor.subscribe('posts'); }
});
//...
lib/router.js
To verify this error page, you can try randomly entering a URL
http://localhost:3000/nothing-here
But wait a minute, what if someone
http://localhost:3000/posts/xyz
a URL in a format
xyz
_id
post?
Although it is a legitimate route, it does not point to any data.
Fortunately, iron Rounter .js solve this problem intelligently enough if we add a special
dataNotFound
hook at the end of the
route.js
//...
Router.onBeforeAction('dataNotFound', {only: 'postPage'});
lib/router.js
This tells Iron Router not only in the case of illegal routing, but
postPage
routing,
data
displays a "cannot find" page whenever the data function returns a "falsy" (such as
null
false
false,undefined, or empty) object.
undefined
Added a template that the page could not find
You may want to know the story behind the name "Iron Router". According to Iron Router author Chris Mather, because meteors are mainly made up of iron elements.