Build a Blog Front-end with Ember-CLI

This is a walkthrough tutorial on how to get started with building a blog website using Ember-CLI. As we go along, you'll be getting your hands dirty with a real-world scenario of building a multi-page website for the fictitious travel company named Iconic Locations. This article is intended to be less high-level and more hands-on as it is meant to be followed in conjunction with the Ember.js Guides.

Prerequisites: I'm assuming you have already installed Ember-CLI on your machine along with the Chrome Ember Inspector.

If you get stuck at any point, feel free to browse my copy of the code at GitHub. For your convenience, I made a branch for each section of this article so you can jump in at any point.

Start at the Finish Line

Before we begin, let's look at a mockup of what we'll be building:

Index page:
Index page

Post page:
Post page

Here's a JSFiddle with the template HTML and CSS we'll be starting with:

What we have here is a blog page template made responsive by Bootstrap. We have an app bar, a header, an article section, and a footer that will have some dynamic controls on it. The process I have in mind here is that we'll start with this mockup and iteratively make parts of it work until we have a fully-working site. This way, we'll always have something to preview to make sure things are still working along the way.

Creating a project in Ember

Open up a terminal with node.js installed and type the following:

$ ember new iconic-locations

This instructs Ember-CLI to install a basic set of files you'll need to start your project. The part we will be doing most of the work is in the app folder. Once it's done, go to the root directory of your application.

$ cd iconic-locations

From here you can start the app.

$ ember serve

It will output something like this:

version: 0.1.12 Livereload server on port 35729 Serving on http://0.0.0.0:4200/

Okay, it told us it's serving at http://0.0.0.0:4200. Load that in your browser and you'll see:
Welcome to Ember.js

It looks pretty plain, doesn't it? Let's add in our template next because we always want the project to look like the finished product. This will allow us to be able to turn stuff into the components, views and templates we need with minimal effort.

Add the Prototype Layout

Open app/templates/application.hbs. This is a handlebars file that will contain our template. Let's start by replacing everything in this file with the contents of the HTML pane of the JSFiddle editor. (Don't worry about the {{outlet}} tag just yet, we'll add it back once we need it.) Since we're running Ember-CLI at the moment, if you save the file the browser will automatically update with your changes. We've still got a few missing styles, so let's add them next.

Open the file app/styles/app.css and copy the remaining template styles from the CSS pane of the JSFiddle editor. After the browser refreshes, it should now look like the template.

As a matter of tidying things up a bit, go back to the application template and move the script tags to just below the other script tags in app/index.html. Now we're ready to start converting it to an Ember app.

Adding Routes and Templates

Let's add an index route template, which will be viewable from the starting directory and will list all the recent posts. Open up a second terminal window and change its current directory to the root iconic-locations path. Now type the following:

$ ember generate route index

Now simply move the bottom section of the template from the app/templates/application.hbs into app/templates/index.hbs, replacing file's contents. The entire file should now look like this:

<div class="container foreground"> ... </div>

And at the bottom of the application.hbs file, add an {{outlet}} tag, so the template you just moved will continue to show when you are at your website's root URL.

At this point, your browser should still look like the starting template when you reload the page.

Now let's add a route for viewing an individual post. Ember.js generated a default route for the index page, so we haven't needed to add one until now. Open app/router.js and add a resource inside the router's map method, like so:

... Router.map(function() { this.resource('post', { path: '/:post_id' }); }); ...

This adds a route named post, which will be accessible near the application's root directory at '/:post_id'. If we didn't specify a path, it would be located at /post/:post_id instead by default.

Since we don't want to display the full article text and header on every page, we should move those blocks into a separate template. In your terminal, type the following:

$ ember generate route post

This created a few files, one of them located at app/templates/post. Go back to app/templates/application.hbs and replace everything below the <div id="navbar"> section with an {{outlet}} tag, while replacing the contents of the app/templates/post.hbs file with the contents you removed.

To recap, your application template should now look like this:

<div id="navbar" class="navbar navbar-default navbar-page-title"> ... </div> {{outlet}}

And the post template should look like this:

<header id="page-title" class="jumbotron background"> ... </header> <div class="container foreground"> ... </div> <footer> ... </footer>

At this point, your web browser should look like this after it refreshes:

Index page

Configuring the Content Security Policy

In your browser, open up the developer tools. You may notice in the console that you're getting several red-colored [Report Only] warning messages that look like this:

Content Security Policy Reports

This is a security feature built into modern web browsers to prevent cross site scripting (XSS) attacks, and Ember-CLI comes preinstalled with support for it. It generally works by having Ember tell your browser to get "permission" to use these particular files, URLs, or CSS styles, and to consider them as unsafe until told otherwise.

Open up config/environment.js and modify it as follows:

... var ENV = { ... }; ENV.contentSecurityPolicy = { 'default-src': "'none'", 'script-src': "'self' https://code.jquery.com https://maxcdn.bootstrapcdn.com https://cdnjs.cloudflare.com", // Allow scripts from https://code.jquery.com, https://maxcdn.bootstrapcdn.com, and https://cdnjs.cloudflare.com 'font-src': "'self' https://maxcdn.bootstrapcdn.com https://cdnjs.cloudflare.com https://fonts.gstatic.com", // Allow fonts from https://maxcdn.bootstrapcdn.com, https://cdnjs.cloudflare.com and https://fonts.gstatic.com 'connect-src': "'self'", 'img-src': "'self'", 'style-src': "'self' 'unsafe-inline' https://maxcdn.bootstrapcdn.com https://cdnjs.cloudflare.com https://fonts.gstatic.com https://fonts.googleapis.com", // Allow styles from https://maxcdn.bootstrapcdn.com, https://cdnjs.cloudflare.com and https://fonts.googleapis.com 'media-src': "'self'" } if (environment === 'development') { ... ENV.contentSecurityPolicy['img-src'] += " http://lorempixel.com http://s3.amazonaws.com"; // Allow images from http://lorempixel.com and https://s3.amazonaws.com (Used for UIFaces) } ...

This will configure subdomain-level permissions for different setting types. Note that I'm allowing the prototype profile photos from UIFaces only when the site is in development. When it goes live, we'll want to display something else.

NOTE: The bootstrap library requires the use of inline scripts and styles. This is why I'm allowing the 'unsafe-inline' attribute on the style-src property, and the 'unsafe-eval' attribute on the script-src property.

If you reload your browser, the content security policy reports should all be gone from the console at this time.

Creating Models with Fixture Data

In your terminal, type the following:

$ ember generate model post title:string subtitle:string image:string content:string date-published:string author:belongs-to tags:has-many

This created a model file at app/models/post.js that contains both its properties and relationships. However, we're going to be accessing authors and tags through their posts, so we'll want to modify the post model's relationships to load asynchronously:

export default DS.Model.extend({ title: DS.attr('string'), subtitle: DS.attr('string'), image: DS.attr('string'), content: DS.attr('string'), datePublished: DS.attr('date'), author: DS.belongsTo('author', { async: true }), tags: DS.hasMany('tag', { async: true }) });

Now let's add the fixture data. We'll be using the reopenClass method to handle this, so by the time we're done it should now look like the following:

import DS from 'ember-data'; export default DS.Model.extend({ ... }).reopenClass({ FIXTURES: [ { id: 1, title: 'Snapper Rocks Surfing', subtitle: 'Surfing Away on Pennies a Day', content: '<p>HTML-formatted article text</p>', image: 'http://lorempixel.com/1000/570/sports/4/', datePublished: new Date(Date.parse("2015-02-12T13:15:00Z")), author: 1, tags: [1, 2, 3] }, { id: 2, title: 'The Best Sushi in St. Louis', subtitle: '', image: 'http://lorempixel.com/1000/570/food/8/', content: '<p>HTML-formatted article text</p>', datePublished: new Date(Date.parse("2015-02-07T16:21:00Z")), author: 2, tags: [1] }, ... ] });

Note that the model says it belongsTo an author, and it hasMany tags, let's create those models as well so we can load those in. Type the following:

$ ember generate model author name:string email-address:string profile-image-url:string posts:has-many

Next, set up the file to look like the following:

import DS from 'ember-data'; export default DS.Model.extend({ name: DS.attr('string'), emailAddress: DS.attr('string'), profileImageUrl: DS.attr('string'), posts: DS.hasMany('post') }).reopenClass({ FIXTURES: [ { id: 1, name: 'Brian Barrett', emailAddress: 'BrainSBarrett@jourrapide.com', profileImageUrl: 'http://s3.amazonaws.com/uifaces/faces/twitter/cacique/73.jpg', posts: [1] }, { id: 2, name: 'Janice Collins', emailAddress: 'JaniceRCollins@dayrep.com', profileImageUrl: 'http://s3.amazonaws.com/uifaces/faces/twitter/visionarty/73.jpg', posts: [2] }, ... ] });

Once again, enter this into the console to create a tags model:

$ ember generate model tag name:string posts:hasMany

Now open the app/models/tag.js file and set it up to resemble the following:

import DS from 'ember-data'; export default DS.Model.extend({ name: DS.attr('string'), posts: DS.hasMany('post') }).reopenClass({ FIXTURES: [ { id: 1, name: 'Travel', posts: [1,2,3,4,5] }, { id: 2, name: 'Surfing', posts: [1] }, ... ] });

If all went well, the website should still load.

Loading the Fixture Data

Since we're going to be using fixtures for our data, we need to create a special application adapter so our website knows how to load them. In your console, type the following:

$ ember generate adapter application

Then, open up the file at app/adapters/application. Note that Ember generated a DS.RESTAdapter object for you. By default, adapters are primed for making Web requests to an external API. All we need to do here is change the type from DS.RESTAdapter to DS.FixtureAdapter.

Now that we have some fixtures available, let's start using them to dynamically display content in our templates. Since we'll be needing a list of recent posts in most pages (According to our mockup, it will be on both on our index page and on the footer on the post page), we'll just load that once from the application route. In your console, type the following:

$ ember generate route application

It will then ask you whether you want to overwrite the application template:

[?] Overwrite /Users/mattt/iconic-locations/app/templates/application.hbs? (Yndh)

Just type n, which means no, since we want to keep our changes so far. This will create a file at app/routes/application.js which will be loaded into memory before anything else. Open the file and modify it as follows:

import Ember from 'ember'; export default Ember.Route.extend({ model: function() { return this.get('store').find('post').then(function(posts) { return posts.sortBy('datePublished').reverseObjects(); }); } });

Now we have access to a list of posts ordered from newest to oldest from anywhere in our application. We want our index route to be able to use it, so let's add that at app/routes/index.js:

import Ember from 'ember'; export default Ember.Route.extend({ model: function() { return this.modelFor('application'); } });

The post route will be more complicated since it needs a list of recent posts as well as an individual post to display. In cases like these we make use of RSVP.hash, which returns only when all of its contained promises are fulfilled. Modify app/routes/post.js as follows:

import Ember from 'ember'; export default Ember.Route.extend({ model: function(params) { return this.get('store').find('post', params.post_id); } });

Please take a moment to reload the website and make sure the index page still loads normally. We can also now go to http://localhost:4200/1, which will load our post template:

Post Template Page

Rendering the Fixture Data

Now it's time to start making our templates show some fixture data. Let's start with the index page. Modify it as follows:

<div class="container foreground"> {{#each post in model}} <article class="article-preview"> <div class="row"> <div class="col-sm-2"> <h2 class="publish-day">12</h2> <div class="publish-month">February</div> </div> <div class="col-sm-10"> <h2 class="post-title">{{#link-to "post" post}}{{post.title}}{{/link-to}}</h2> <h3 class="post-subtitle">{{post.subtitle}}</h3> <div class="post-preview"><p>Are sentiments apartments decisively the especially alteration. Thrown shy denote ten ladies though ask saw. Or by to he going think order event music. Incommode so intention defective at convinced. Led income months itself and houses you. After nor you leave might share court balls.</p></div> <div class="author"> <a href="#"> <img {{bind-attr src="post.author.profileImageUrl"}} class="author-image"><span class="author-name">{{post.author.name}}</span> </a> </div> </div> </div> </article> {{/each}} </div>

Here we loop through each article, print whatever we can about them, and link to the post route by passing in the Id of the one we want.

We've still got a couple more things yet to do here. We don't have a way to format the publish date yet, and we don't want to write the entire post in the preview, maybe just the first paragraph or so. Since that will take a bit of additional work, and I want to focus on just getting the templates to render what we have available in the models, so let's ignore that for now. We'll build that in the Building Custom Helpers section.

Open up the app/templates/post.hbs file and modify the header section:

<header id="page-title" class="jumbotron background"> <div id="title-image" class="bg"> <img {{bind-attr src=model.image}} alt=""> </div> <div class="container"> <div class="horizontal-center vertical-center"> <h1 class="article-title">{{model.title}}</h1> {{#if model.subtitle}} <h2 class="article-subtitle">{{model.subtitle}}</h2> {{/if}} </div> </div> </header>

Next, we can load the article:

<article> {{{model.content}}} <div class="horizontal-center"> <div class="author"> <a href="#"> {{#if model.author.profileImageUrl}}<img {{bind-attr src="model.author.profileImageUrl"}} class="author-image">{{/if}}<span class="author-name">{{model.author.name}}</span> </a> </div> <div class="date-published"> February 12, 2015 </div> </div> </article>

Note that the {{{model.content}}} tag is wrapped with a triple bar syntax. This tells Ember that it's safe to render HTML tags. Also note that we're not converting the dates just yet. If we did, they'll just look like Thu Feb 12 2015 07:15:00 GMT-0600 (CST) instead of the much prettier February 12, 2015 that we want. We'll take care of that shortly.

In the footer, our template doesn't yet have access to the list of recent posts because it's not being provided in the model. The solution for that is to load them through the controller, which we'll get to later. The only section left to consider that we need to modify is the tags list. Let's do that now:

<div class="tag-cont"> {{#each tag in model.tags}} <button type="submit" class="btn">{{tag.name}}</button> {{/each}} </div>

Adding a Controller

Controllers are a great way to add business logic to our application. We have the "Older" and "Newer" buttons on our post page, that likely won't be used anywhere else on the site, so this could be considered worthy of a controller implementation.

In the console, enter the following:

$ ember generate controller post

This generates a controller at app/controllers/posts.js. Open the controller file and modify it as follows:

import Ember from 'ember'; export default Ember.ObjectController.extend({ needs: ['application'], recentPosts: Ember.computed.alias('controllers.application.model'), previousPost: Ember.computed('model', 'recentPosts.@each', function() { var recentPosts, index; recentPosts = this.get('recentPosts'); index = recentPosts.indexOf(this.get('model')); return recentPosts.objectAt(index - 1); }), nextPost: Ember.computed('model', 'recentPosts.@each', function() { var recentPosts, index; recentPosts = this.get('recentPosts'); index = recentPosts.indexOf(this.get('model')); return recentPosts.objectAt(index + 1); }) });

Note that we converted it into an Ember.ObjectController. Another type we have available is an Ember.ArrayController. Now we have properties we can use to wire up the pager buttons in the post template:

<ul class="pager"> {{#if previousPost}} <li class="previous">{{#link-to "post" previousPost}}← Older{{/link-to}}</li> {{else}} <li class="previous disabled"><a>← Older</a></li> {{/if}} {{#if nextPost}} <li class="next">{{#link-to "post" nextPost}}Newer →{{/link-to}}</li> {{else}} <li class="next disabled"><a>Newer →</a></li> {{/if}} </ul>

Additionally, since we have a list of recentPosts given to us now, we can modify the recent posts section:

<div class="recent-posts-cont"> <ul class="image-list"> {{#each post in recentPosts}} <li class="table"> {{#link-to "post" post classNames="table-row"}} <div class="vertical-center"> <img {{bind-attr src="post.author.profileImageUrl"}} class="author-image"> </div> <div class="vertical-center article-title"> {{post.title}} </div> {{/link-to}} </li> {{/each}} </ul> </div>

Here we loop through each one and link to the post page again by passing in the model of the target post.

Building Custom Helpers

Helpers are a tool for converting logic into compiled HTML or text for your output. They work best when turned into something generic and reusable, such as a date formatter. As it stands, there's a JavaScript library called Moment.js that's very close to what we need. In your console, type the following:

$ bower install moment --save

This will install the moment.js library as a bower dependency. We can reference this in our app from the Brocfile.js file. Open it up and add the following import statement near the end of the file:

... app.import('bower_components/moment/moment.js'); module.exports = app.toTree();

NOTE: You'll need to restart the ember server that's been running in your first console window because it doesn't watch for changes in your dependencies. You may stop it simply with the Ctrl + C shortcut, and then rerunning the command ember serve.

Next we'll create the helper:

$ ember generate helper date-formatter

Now modify the file app/helpers/date-formatter.js as follows:

/* globals moment */ import Ember from 'ember'; export function dateFormatter(format, date) { if (typeof(format) !== 'string') { format = 'MMMM DD, YYYY'; } if (date == null) { date = Date.now(); } return moment(date).format(format); } export default Ember.Handlebars.makeBoundHelper(dateFormatter);

This method takes an optional date format string, and a date value. We can now modify our index template to use it, like so:

... <h2 class="publish-day">{{date-formatter 'DD' post.datePublished}}</h2> <div class="publish-month">{{date-formatter 'MMMM' post.datePublished}}</div> ...

Now if you reload the index page at http://localhost:4200 the dates should be loading correctly from your fixtures.

We still need to modify the date on the post page. Open the post template and modify the article date:

<div class="date-published"> {{date-formatter "MMMM DD, YYYY" model.datePublished}} </div>

We now have all the dates formatted as they are in the mockups. Let's create another helper for the article previews on the index page:

$ ember generate helper text-preview

Open app/helpers/text-preview.js:

import Ember from 'ember'; export function textPreview(text, minCutoff, maxCutoff) { var preview, cutoff; // Strip HTML from the article preview = Ember.$(text).text(); if (preview.length > maxCutoff) { // Cut off text near the end of a word cutoff = preview.lastIndexOf(' ', maxCutoff); if (cutoff < minCutoff) { cutoff = maxCutoff; } preview = preview.substr(0, cutoff) + '...'; } return preview; } export default Ember.Handlebars.makeBoundHelper(textPreview);

This attempts to find a decent spot in the article to cut off a sentence, rather than mid-word, if possible. It takes a text string, and a cutoff range. In our app/templates/index.hbs file, we can now write:

<div class="post-preview">{{text-preview post.content 200 250}}</div>

If your text preview helper is working, it should look something like this:

Index page

I hope that by now you've got a good grasp on how to convert a web page prototype layout into a functional Ember-CLI website. I know there is a lot to do yet on the website, but after having gone through the Ember.js Guides and this walkthrough, you should be able to take it from here. Give yourself a pat on the back, and thanks for making it this far!