Creating your first route

Till now most of our examples in our previous chapters have been using the index or / route. Real-world web applications seldom have only one route. Ambitious web applications, on the other hand, have a separate URL endpoint mapped to each different state of the application.

The Ember.js framework provides the concept of a router, a route, and a resource to manage the mapping between the URL and the state of the application.

Router in Ember.js is the core and the central part of the framework. It maintains the mapping of the URLs to individual routes. It monitors the URL of the web application and then, based on the mapping, it invokes the individual routes.

Let's see how to add this mapping. We will be adding two new routes, products and about, to our application.

The Router definition present at example1/app/router.js is as follows:

import Ember from 'ember';
import config from './config/environment';

var Router = Ember.Router.extend({
  location: config.locationType // "auto"
});

Router.map(function() {
  this.route("products",{ path: "/products" });
  this.route("about",{ path: "/about" });
});

export default Router;

As you can see, we first create the Router object by extending Ember.Router. The location property of the router governs how to build the URLs of the application. We will be talking about the location API later in this chapter.

We use the map method of Ember.Router to create individual routes. We use the this.route method of the router to map URLs with the routes. The route method takes in three arguments: name, options, and callback.

The name corresponds to the name of the route and helps in identifying this route from other parts of the application.

The options argument expects a JavaScript object with your desired options set. In our case, we pass in the path attribute in the option's argument object to map our route to a specific URL.

The last argument is the callback, which is used for nesting the routes.

As you can see in the preceding code, we defined two routes with names products and about.

Note

Please note here that if you want to keep the name of the route and of the URL pattern same, you can omit the additional path property, which is set in the options object. So this.route("products",{ path: "/products" });and this.route("products") will essentially mean the same thing.

Let's now look at the templates for these corresponding routes. As discussed in the previous chapter, all the templates go inside the app/templates/ folder.

<h2>Welcome to Ember.js</h2>
<div> {{#link-to 'about'}}About{{/link-to}} </div>
<div> {{#link-to 'products'}}Products{{/link-to}}</div>
{{outlet}}

The application template is present at example1/app/templates/application.hbs

<br>
<br>
<div>Products Template</div>

The products template is present at example1/app/templates/products.hbs

<br>
<br>
<div>About Template</div>

The products template is present at example1/app/templates/about.hbs

You can see from what precedes that apart from the application template, we have defined two additional templates, products and about. These templates contain the data to be displayed for the products and about pages.

In application.hbs, we are also using a very useful handlebars helper expression, {{#link-to}}. The link-to helper expression helps us avoid hardcoding the URL address in our application. It fetches the URL pattern from the mappings in the router.

So {{#link-to 'products'}} Products {{/link-to}} would generate something like the following:

<a id="ember258" class="ember-view" href="/about">About</a>.

You can see that the generated HTML link's href points to the correct URL path. The link-to helper makes our application transparent to any changes in the URL pattern of the application.

Let us say that later in development cycle of your application, the SEO (search engine optimization) expert comes in and suggests that by changing the /about endpoint to /about-us, you will improve the search engine ranking for your site.

If you had used the {{link-to}} helper, you would just edit the router code to map to about-us instead of about, and your application would work just fine. Had you hardcoded the URL in your application, incorporating this change would have been error-prone and time consuming.

If you run the above code, you will see a screen that now has About and Products links on the homepage.

If you click on any of the links, you will see that the respective products or about template appears on the screen, as shown in the following figure:

Creating your first route

The products template rendered when a user clicks on the products link

In Ember.js, the routes of your application should inherit the framework's Ember.Route class to provide any custom implementation of the route. One thing that you might have noticed in the preceding example would be that we did not create app/routes/products.js or app/routes/about.js in our application. Whenever a user visits the /products or /about URL, the Ember.js framework tries to find the corresponding routes based on naming conventions. For example, for /products, the router will try and instantiate app/routes/products.js for you, and if the framework is not able to find its definition, then it will generate the route for you. So, in our case, since we don't define the app/routes/products.js or app/routes/about.js, the Ember.js framework generates them for us. Ember.js frameworks rely heavily on naming conventions, and routes are at the core of these conventions. Based on the name of the route supplied to the this.route() function, the framework tries to find the respective controller and template to use.

Let's see this by an example; in the above example, we created the product route which uses the following:

this.route("products",{ path: "/products" });

When the end user visits the /products path, the framework will try to find the route with the matching name and path, and will look for definition exported in app/routes/products.js, and when it finds one, it will instantiate the route for you and execute the hooks associated with the route (we will be covering the initialization of routes in more detail later in this chapter).

Then, the framework will try to find the controller that matches the route, which in our case should be defined in the app/controllers/products.js file. The framework will finally resolve the handlebars.js template it has to render, which should be defined in the app/templates/products.hbs file.

As you can see, this clearly forms a pattern. An example route declaration will map to the /example URL by default, and the framework will look for its matching route definition exported from the app/routes/example.js file, matching controller in app/controllers/example.js, and render the template defined in app/templates/example.hbs.

Now, since all of the application will follow this convention, it becomes really easy to find where the code for a specific functionality resides. It also makes debugging errors in your application very straightforward.

Resources and nested templates

Till now, we have seen very basic usage of the Ember.js routes; we have been using only top-level routes such as /products and /about, but very seldom do real-world applications have such simple routes.

Real-world applications will have resources or nouns, and actions that can be executed on these nouns that are depicted by verbs.

For example, there could be a /products/2 endpoint to show the product details page for a product with ID as 2, or /products/new and /products/2/edit routes to create and edit a product, respectively.

The Ember.js framework encourages using resource for all the nouns and route for all the verbs. This means that if you are creating your application for a specific domain area, all the entities of that domain should map to a resource and all the actions on the domain entities should translate to routes. A resource then becomes a collection of routes.

You can create a new resource using this.resource in the router. It expects two arguments, the first one is the name of the resource and other is a function that defines the nested routes, if any. If you don't have any nested routes in the resource, you can omit this argument. The following code snippet shows how to create a resource with nested routes:

Router.map(function() {
  this.resource("products",function(){
    this.route("new");
  });
  this.route("about");
});

The nested route is defined in example2/app/router.js

Here, we have created a resource products and a nested route, new. To be able to display these routes, we will have to create the corresponding nested templates.

One very important thing to note here is that the nesting of resources/routes also means that their templates should also be nested in a similar fashion. Let's make it more clearer by defining our products , products/new and about templates:

<br>
<br>
<div>Products Template</div>
    {{outlet}}

The products template is present at example2/app/templates/products.hbs

<br>
<br>
<div>About Template</div>
<div> This Template should contains some information about us

The about template is present at example2/app/templates/about.hbs

<br>
<h3>Create a New Product</h3>
<br>
    Product name:<br>
    {{input value=name }}
<br>
    Product Description:<br>
    {{textarea value=description }}
<br><br>
<button {{action 'create'}}>Create</button>

The products.new template is present at example2/app/templates/products/new.hbs

You can see above that we have defined two templates, one for products and the other for the new route that is nested under the products resource.

When you first look at the products template, you will notice that we have now added {{outlet}} at the end of the template. This outlet will enable nesting for this template, and all the nested routes that are present under the products resource will first render the products template present at app/templates/products.hbs, and then render the nested route's template in the outlet provided by the products template. If you remove the {{outlet}} from the products template, you will notice that the products.new template is never rendered, as it could not get the parent outlet to render the child route template.

You might also have noticed by now that we are referring to the new route that is nested inside the products resource by products.new. Ember.js follows this convention to refer to the nested routes in handlebars and other helpers, which is <<parent resource>>.<<nested route>>.

To link this nested route using the {{link-to}} handlebars helper, we would do something like the following:

{{#link-to 'products.new'}}Create a new product{{/link-to}}</div>

The following table shows the mapping of the different routes that you defined in your router to respective controller, route, and template files:

URL

Route name

Controller

Route

Template

/

index

app/controllers/index.js

app/routes/index.js

app/templates/index.hbs

N/A

products

app/controllers/products.js

app/routes/products.js

app/templates/products.hbs

/products

products.index

app/controllers/products.js

app/controllers/products/index.js

app/routes/products.js

app/routes/products/index.js

app/templates/products.hbs

app/templates/products/index.hbs

/products/new

products.new

app/controllers/products.js

app/controllers/products/new.js

app/routes/products.js

app/routes/products/new.js

app/templates/products.hbs

app/templates/products/new.hbs

/about

about

app/controllers/about.js

app/routes/about.js

app/templates/about.hbs

Let us see how the routes, which we have defined in our router above, map and initialize the controller, route, and template. Let us start from products.new route. Now, since the new route is defined inside the products resource, we would refer it by products.new. As we discussed earlier, route nesting also means controller and template nesting. This means that when the user visits the /products/new route, first of all, the products template is rendered from app/templates/products.hbs using the route exported from app/routes/products.js, and this route injects the controller from app/controllers/products.js to back the template.

After rendering the products template, the framework will look for the products.new template in app/template/products/new.hbs to render it in the {{outlet}} provided by the products template.

The controller exported at app/controllers/products/new.js and the route exported at app/routes/products/new.js will back the app/templates/products/new.hbs template.

The general rule of thumb is that first the parent resource is rendered, using its route and controller. Then, the nested child route is rendered in the parent template's outlet.

The nested or child route has its own controller and route, just like an independent route. The location of the nested route should be inside a folder whose name is the name of the resource it is nested in, for example, app/routes/<<resource>>/<<nested route>>.

It may seem a bit odd at first, but the trick here is to think of your application layout as nested templates rather than independent ones.

One thing that you might have noticed in the above table would be the products route, which is not mapped to any URL, and the products.index route, which we did not define anywhere. Lets revisit the products and products.index routes again here:

URL

Route name

Controller

Route

Template

N/A

products

app/controllers/products.js

app/routes/products.js

app/templates/products.hbs

/products

products.index

app/controllers/products.js

app/controllers/products/index.js

app/routes/products.js

app/routes/products/index.js

app/templates/products.hbs

app/templates/products/index.hbs

The products route is not mapped to any URL and is always invoked when a user visits /products or any of its child routes. It is very much like the application route present in app/routes/application.js, but only for all the routes that are nested inside the products resource.

If we want to handle errors or add in a common functionality for all the products, then its app/routes/products.js or app/controllers/products.js routes would be the right place to put in the common behavior.

For example, if we want to handle the errors that originate from the products pages in a specific way, we would put this specific behavior in the products route.

Similarly, whenever you create a nested route, you get resource.index that maps to /resource automatically. You just need to override the default implementation of ResourceIndexRoute and ResourceIndexController by defining them in your application with its custom behavior. This behavior is in line with the event bubbling topic we discussed in Action event bubbling section of Chapter 3, Rendering Using Templates.

This hierarchy of controllers and routes keeps the entities focused toward providing functionality to one section, rather than putting everything in one place and later finding it difficult to maintain that. Another view of the template hierarchy of our application is shown in the following image:

Resources and nested templates

Template hierarchy of our application

Now, you should be in a better position to understand the overall routes and template nesting in Ember.js.

Injecting the model for your template

Till now, almost all of the examples we have seen so far have one thing in common: they all render static data that is returned from the model method of the corresponding route, something like the following:

export default Ember.Route.extend({
  model: function() {
    return ['red', 'yellow', 'blue'];
  }
});

As we saw in Chapter 2, Understanding Ember.js Object-oriented Patterns, one of the very important advantages of using Ember.js framework is that it tries to build an application that uses components that are highly decoupled, yet internally cohesive.

The templates present the data that is fetched from the models. Routes play a very important role in this process. Routes help you to decide which model to fetch and how to customize it. As shown in the preceding snippet, the model can return a static list of colors or it can also fetch a list of colors from a remote server. All of this remains transparent to the templates that focus on displaying the list of item(s) returned from the model method.

Up until now, we have seen the model method in the route returning static data to the templates. But it will seldom be the case where your models return static data, and most of the time the data is fetched from the server and displayed to the user.

For the next example, we will be using GitHub's public API (found at https://developer.github.com/v3/)to fetch the commits in the Ember.js repository.

If you open the https://api.github.com/repos/emberjs/ember.js/commits link in a browser, you will get the commit data in JSON format for the Ember.js repository on GitHub, something like the following:

[{
  "sha": "2da6e0b981ee20d2e2361102fcf7b8cb3ef812c5",
  "commit": {
    "author": {
      "name": "Stefan Penner",
      "email": "[email protected]",
      "date": "2014-12-06T17:38:03Z"
    },
    "committer": {
      "name": "Stefan Penner",
      "email": "[email protected]",
      "date": "2014-12-06T17:38:03Z"
    },
    "message": "Merge pull request #9826 from twokul/brocfile-dup-funct

Removes duplicate function",
    "tree": {
      "sha": "3eaae01753f4a2a919921232013ba32dda658bab",
      "url": "https://api.github.com/repos/emberjs/ember.js/git/trees/3eaae01753f4a2a919921232013ba32dda658bab"
    },
    "url": "https://api.github.com/repos/emberjs/ember.js/git/commits/2da6e0b981ee20d2e2361102fcf7b8cb3ef812c5",
    "comment_count": 0
  },
]

Now, let's consume this data and make it presentable for an end user.

We shall create two routes here: the commits.index route and the application index route.

As we don't have anything at present to show on the homepage of our application, we shall use the redirect method to transition from the index route to the commits.index route when anyone hits the / or the root URL. This is how the commits.index route will look:

export default  Ember.Route.extend({
  model: function() {
    varurl = 'https://api.github.com/repos/emberjs/ember.js/commits';
    return Ember.$.getJSON(url);
  }
});

Commits index route is present at example3/app/routes/commits/index.js

The following code shows how we can redirect the index route of our application to commits.index route:

export default Ember.Route.extend({
  redirect: function(){
    this.transitionTo("commits.index");
  }
});

Application index route is present at example3/app/routes/index.js

As you can see in the preceding code snippet, we are using the JQuery $.getJSON() method to retrieve the data from the server. Now, instead of static text, our model function retrieves the commit data from the server and returns it to the template.

Now let's look at the two templates we will have for our application. One is for the application that will contain our application name. The application template can contain things that are common for the entire application. The other commits.index template can contain the code to display the list of commits of the repository. These commits can be retrieved from the model object set in the commits.index route, as shown in the following:

<h1>Ember.js Repo</h2>
{{outlet}}

Application template is present at example3/app/templates/application.hbs

<h2>Commits</h2>
{{#each c in model}}
<div><em>Sha</em>: {{c.sha}}</div>
<div><em>Author</em>: {{c.commit.author.name}}</div>
<div><em>Message</em>: {{c.commit.message}}</div>
<hr/>
{{/each}}

The commits.index template is present at example3/app/templates/commits/index.hbs

As you can see in the example3/app/templates/commits/index.hbs file, to show the list of commits, we just iterate over the model object and then just output the contents of each commit to the user. If you run the preceding code, you will see the output as shown in the following screenshot:

Injecting the model for your template

Using asynchronous data from the model

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.22.160