134

emberjs_applications.pdf

Embed Size (px)

Citation preview

Page 1: emberjs_applications.pdf
Page 2: emberjs_applications.pdf

Ambitious Ember ApplicationsA comprehensive Ember.js tutorial

Ruslan Yakhyaev

This book is for sale at http://leanpub.com/emberjs_applications

This version was published on 2014-03-01

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishingprocess. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools andmany iterations to get reader feedback, pivot until you have the right book and build traction onceyou do.

©2014 Ruslan Yakhyaev

Page 3: emberjs_applications.pdf

Tweet This Book!Please help Ruslan Yakhyaev by spreading the word about this book on Twitter!

The suggested hashtag for this book is #emberjs.

Find out what other people are saying about the book by clicking on this link to search for thishashtag on Twitter:

https://twitter.com/search?q=#emberjs

Page 4: emberjs_applications.pdf

Contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iNeeded Knowledge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iSo What Are We Building? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii

How to read this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii

1 Setting Things Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1 Turning the Starter Kit Inside Out . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2 Where the Magic Happens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.3 Installing Ember Inspector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2 First Templates, First Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.1 Gentle Introduction to the Templates . . . . . . . . . . . . . . . . . . . . . . . . . . 112.2 Adding our First Route . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122.3 Providing Content for Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

3 Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173.1 Our First Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173.2 Properties as Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213.3 Ember Controller Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

4 Routing, View Tree and Naming Conventions . . . . . . . . . . . . . . . . . . . . . . . 234.1 Inspecting the View Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284.2 Naming Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

5 First Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305.1 Testing all the JavaScripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

6 Working with Real Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366.1 Defining Fixtures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366.2 Navigating Through the Application . . . . . . . . . . . . . . . . . . . . . . . . . . . 406.3 Inspecting Promises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436.4 Updating Our Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

7 Relating Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467.1 Preparing Fixtures and Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

Page 5: emberjs_applications.pdf

CONTENTS

7.2 Creating Relations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

8 Managing the Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528.1 Big Authentication Theory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 528.2 Let the Authentication Begin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 538.3 Signing the User Out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618.4 Binding Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618.5 Testig Signing-in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

9 Asking Our First Question . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 659.1 Testing What We Did . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

10 Answering Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7810.1 Modelling Our Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7810.2 Introducing Mixins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8410.3 Empty states . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8610.4 Testing our Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

11 Cleaning Templates Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9111.1 Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9111.2 Partials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9811.3 Views to the Rescue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10011.4 Mighty Render . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

12 Nesting Views, Editing Records, Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . 10812.1 Making Things Neat With Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11212.2 Editing Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11412.3 Final Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

13 Summing Things Up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12013.1 Naming Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12013.2 Ember Under Microscope . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

Final Words . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123Acknowledgement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

Appendix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124Stubbing Out Fixture Querying . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124Sending Authentication Token with Every Request . . . . . . . . . . . . . . . . . . . . . . 125Setting Model Out of the Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125Make localStorage Observable by Ember . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

Page 6: emberjs_applications.pdf

IntroductionIt is fascinating to see how quickly the web is evolving these days. It took us only few years to makea full transition from boring, ugly websites to the beautifully crafted applications written directlywithin a browser. It all has been possible thanks to complex client side frameworks such as Ember.js,Backbone or Angular.

Ember.js is a framework for creating ambitious web applications. It main focus is to give you tools,powerful enough to reduce the amount of code youwrite. Ember incorporates many common idiomsand frees you from reinventing the wheel. Similar to other opinionated frameworks, Ember valuesconvention over configuration.

The API of the Ember is clean and developer friendly. Coming from the world of Rails (and havingboth a great distaste for JavaScript) I was surprised with how close to Rails writing the code inEmber.js felt.

So if you are deeply frustrated with your web application slowly becoming a huge unorganised ballof jQuery spaghetti callbacks, your JavaScript files are growing over 100kb or thinking about codebehind your front-end gives you a knee-jerk reaction; maybe it is a sign to think about switching toclient side JavaScript framework.

Before we start, you need to know that Ember.js is hard. It’s concepts may be very confusing at firstand if you have a background in any server side MVC framework (or any other client side JavaScriptframework) things won’t make much sense at first. That’s why with every new concept introducedduring the course of the book I will try to dive in and explain it in as much detail as possible.

This book is a crash course on Ember.js. After finishing it you should have enough knowledge todecide if Ember.js is the right choice for you and if you do — you should be proficient enough tostart building your own Ember.js applications.

Needed Knowledge

Unfortunately this book doesn’t explain the basic concepts of HTML, CSS, JavsScript or jQuery.These are the four technologies you need to know at least on beginners level to get through thisbook and to understand it. Ember.js is a framework built in JavaScript but fortunately for you, youwon’t need to have deep knowledge of the language. Some basic knowledge of jQuery is requiredthough since we are going use some basic things such as selectors or jQuery methods. And of coursewe are building a web application — that’s why we will need HTML and CSS.

Page 7: emberjs_applications.pdf

Introduction ii

So What Are We Building?

As you’ve may guessed we are going to build simple web application using which I will try todemonstrate as much Ember’s concepts as possible. We will break a convention here and instead ofbuilding a web shop (since everybody is building web shops these days), we are going to builda simple Q&A application called Emberoverflow. Here is a short specification (or call it list offunctionality if you like) for our application:

• visitors of our application can log in,• logged in users can ask questions and answers them,• questions can have multiple answers,• users can edit questions they’ve asked.

Pretty simple yet it will be enough. Also, we won’t be building a backend, so our application willnot depend on any other technologies. Already excited? Good, lets start without any further delay.

You will find the source code of finished application on Github¹.

¹https://github.com/ryakh/emberoverflow

Page 8: emberjs_applications.pdf

How to read this bookIf you’ve read any other technical book you should have no problem reading this one. But just to besure let me summarise the conventions used in this book.

Each chapter has a list of topics covered in the chapter right at the beginning — this should giveyou general overview about the it’s content. After the list there will be a link which will lead to thesnapshot of application with all changes introduced to the code during the course of the chapter.

Code samples are displayed in monospaced font, with the title of the file from which the sample wastaken displayed above the sample. Also code samples have line numbers adjusted to mimic the linenumbers in real files. Before each code sample there is a link leading to the commit with the codehosted on Github.

Code sample²:

code_snippet.js

17 App = Ember.Application.create({

18 awesomeApplication: true

19 });

Tips are highlighted with the icon of the key. Tip is something you don’t need in the contextof the current chapter but it will give you some extra knowledge.

Information blocks extend or explain the knowledge you’ve gained from the currentchapter.

²https://github.com/ryakh/emberoverflow/commit/a6a7b98e5898f65f13429f5a53c2d986fcf21a8e

Page 9: emberjs_applications.pdf

1 Setting Things UpTopics covered in this chapter:

1. preparing scaffolds for development,2. anatomy of the Ember Starter Kit,3. quick introduction to Ember’s anatomy,4. setting up Ember Inspector.

Snapshot of the application¹.

Ember provides a Starter Kit. It is a simple scaffold to jump-start developing a new Emberapplication. Ember is not backend dependent, so you only need a text editor and a browser to startdeveloping an Ember application. Download Ember Starter Kit² and extract it. It has really simplefolder structure:

¹https://github.com/ryakh/emberoverflow/tree/ca3b39a5c034e036a17538ed599228c38c0b5a27²http://emberjs.com/

Page 10: emberjs_applications.pdf

Setting Things Up 2

Ember Starter Kit folder structure

Lets start by dropping in the Twitter Bootstrap³ framework⁴. Open your editor of choice and addfollowing line into the head section of index.html.

Code sample⁵:

index.html

3 <head>

4 <meta charset="utf-8">

5 <title>Ember Starter Kit</title>

6 <link rel="stylesheet" href="css/normalize.css">

7 <link rel="stylesheet" href="css/style.css">

8 <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css\

9 /bootstrap.min.css">

10 </head>

³http://getbootstrap.com/⁴Twitter Bootstrap is a front-end CSS and JavaScript framework which includes pre-defined web components (such as grid or modals) to make

a prototyping of a web application as painless as possible⁵https://github.com/ryakh/emberoverflow/commit/40f78d9bee63a50b02ac02bce57f4c5181a71220

Page 11: emberjs_applications.pdf

Setting Things Up 3

Then add the following at the end of the same file.

Code sample⁶:

index.html

25 <script src="js/libs/jquery-1.10.2.js"></script>

26 <script src="js/libs/handlebars-1.1.2.js"></script>

27 <script src="js/libs/ember-1.4.0.js"></script>

28 <script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js\

29 "></script>

30

31 <script src="js/app.js"></script>

32 <!-- to activate the test runner, add the "?test" query string parameter -->

33 <script src="tests/runner.js"></script>

34 </body>

⁶https://github.com/ryakh/emberoverflow/commit/4195147f8b780c79929e3d3bb148fa3899c1c66a

Page 12: emberjs_applications.pdf

Setting Things Up 4

Open up your browser and load index.html from the Ember Starter Kit. Open the console of yourbrowser, and if everything is going as expected, this is what you will see:

Empty Ember Starter Kit application

The debug message printed out in console states that our Ember application is loaded and ready togo.

Page 13: emberjs_applications.pdf

Setting Things Up 5

1.1 Turning the Starter Kit Inside Out

Look into the source of index.htmlwhich is a part of the Starter Kit — there are bunch of script tagsin there and nothing else. Where does the content we see in our browser comes from? If you are alittle bit familiar with Ember or any other client side framework, you probably know the answer.But just in case you are not lets go through this file, line by line. Head contains pretty much commonstuff — some meta data and stylesheets. Then we have a body which contains two script tags of typetext/x-handlebars and then we load external javascript files. Lets start with the latter:

index.html

25 <script src="js/libs/jquery-1.10.2.js"></script>

26 <script src="js/libs/handlebars-1.1.2.js"></script>

27 <script src="js/libs/ember-1.4.0.js"></script>

28 <script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js\

29 "></script>

30

31 <script src="js/app.js"></script>

32 <!-- to activate the test runner, add the "?test" query string parameter -->

33 <script src="tests/runner.js"></script>

34 </body>

Here we load all the requirements for Ember. They are: jQuery and Handlebars (a JavaScripttempting library). Then we load Ember itself, app.js file which will contain our custom applicationcode and runner.js file which is a gateway to our tests.

Lets add another Ember dependency, Ember Data. It is still not a part of Ember because it is beingactively developed. Discourse⁷ for example is not using Ember Data at the moment. You could chosenot to use it later while developing your own applications but during the course of this book we willrely on it.

⁷Discourse is an open-source discussion platform written in Ember. Currently it is one of the largest open-source Ember application available

Page 14: emberjs_applications.pdf

Setting Things Up 6

Add the following line to you script files.

Code sample⁸:

index.html

25 <script src="js/libs/jquery-1.10.2.js"></script>

26 <script src="js/libs/handlebars-1.1.2.js"></script>

27 <script src="js/libs/ember-1.4.0.js"></script>

28 <script src="http://builds.emberjs.com/beta/ember-data.js"></script>

29 <script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js\

30 "></script>

31

32 <script src="js/app.js"></script>

33 <!-- to activate the test runner, add the "?test" query string parameter -->

34 <script src="tests/runner.js"></script>

35 </body>

We have another two script tags — the handlebar templates. They contain layout of our application:

index.html

11 <script type="text/x-handlebars">

12 <h2>Welcome to Ember.js</h2>

13

14 {{outlet}}

15 </script>

16

17 <script type="text/x-handlebars" id="index">

18 <ul>

19 {{#each item in model}}

20 <li>{{item}}</li>

21 {{/each}}

22 </ul>

23 </script>

⁸https://github.com/ryakh/emberoverflow/commit/ca3b39a5c034e036a17538ed599228c38c0b5a27

Page 15: emberjs_applications.pdf

Setting Things Up 7

1.2 Where the Magic Happens

As you’ve probably guessed, all themagic here happens thanks to the two script tags of text/x-handlebarstype and the app.js file. Lets open app.js file. You will see the following code:

js/app.js

1 App = Ember.Application.create();

2

3 App.Router.map(function() {

4 // put your routes here

5 });

6

7 App.IndexRoute = Ember.Route.extend({

8 model: function() {

9 return ['red', 'yellow', 'blue'];

10 }

11 });

Again, if you are familiar with the framework, bear with me as I will try to explain this file lineby line. First we are creating our Ember application. It is responsible for creating a new instance ofEmber.Application and making it available within our web page; which thanks to that single linebecomes web application!

Second block is responsible for creating router inside our application. You are probably familiar withthe basic concepts of routers in other frameworks — Ember is no exception and router in Ember isresponsible for taking URLs you provide within your browser and sending them into deeper layersof our application.

In Ember, similar to server side based web frameworks, different states of our applicationare represented by URLs.We interact with our application and save states of our applicationin the URLs. So we can simply remember the state by saving URL or we can even sharethe state with other users by sharing the URL with them.

Last part is a Route block which may be a little bit confusing at this point, but I promise that onceyou finish reading this book, you will understand the difference between Ember objects (I am notcalling them components because one of the objects is called a component) and philosophy behindeach one of them. Here is a list of all key Ember objects with a brief description.

Page 16: emberjs_applications.pdf

Setting Things Up 8

RouterIs the connecting point between browser’s address bar and our application. It translates theaddress into the Route.

RouteIs where a user request will land after it was translated by a Router. Route decides what datashould be provided to the Template.

ModelDefines the structure and the logic behind the data that is presented to our user.

StoreIs a place where records are saved (cached) once they are retrieved from the server. Store willalso keep the new records before they are synchronised to the server.

ControllerTemplates query controllers for values that are to be displayed to the user. Controllers alsodecorate (transform, alter, change) the data from models before it is displayed to the user.Controllers have a view and a template.

ViewView is responsible for setting a template and encapsulating all the HTML. It can also respondto various types of user generated events.

ComponentComponent is very similar to the View. It is used to create reusable parts for our application.

TemplateTemplates are the parts of Ember application that other people see. Components, Views andControllers all have a template.

Feeling already lost? Good. Go ahead and re-read the last part. Don’t try to understand it ormemorise it, it will make you even more confused at this point. Remember I’ve said that Emberis hard? That was the living proof of it. But don’t worry, you will have solid knowledge of Embersoon enough.

Page 17: emberjs_applications.pdf

Setting Things Up 9

1.3 Installing Ember Inspector

I encourage you to use Google’s Chrome web browser for the duration of the book because it hasEmber Inspector extension available. Install it from Chrome Marketplace, open Chrome and Visitthe chrome://extensionsURL. Find the Ember Inspector among installed extensions andmake sureAllow access to file URLs is checked. Now open Developer Tools and if you did everything correctyou should see the Ember tab as the last tab in the row of tools available:

Ember Inspector

We will dig deeper into the inspector later, now just make sure that it works.

Page 18: emberjs_applications.pdf

2 First Templates, First RoutesTopics covered in this chapter:

1. initiating the application,2. theory behind templates and Handlebars,3. creating new routes,4. providing templates for the defined routes,5. using Ember inspector to get information about routes.

Snapshot of the application¹.

Lets start where we left off. Load the index.html in your browser and view the source code of thepage. Find the body tag:

index.html source code viewed in browser

<body style="" class="ember-application" data-ember-extension="1">

</body>

If you open up the source code of same index.html file in the editor you will notice that the bodytag has no additional attributes. These were added by the Ember to mark the root element of ourapplication (the element, content inside which is controlled by our Ember application).

Remember our app.js file? There we had the following line of code:

js/app.js

1 App = Ember.Application.create();

With this single line we’ve created our application and put it into the namespace of App. Later on,to add more functionality we will simply define it as properties of our App object. Note that youneed to create an application only once. Also, you can name your application whatever you like; itdoesn’t have to be App or anything in particular. Just make sure it starts with the capital letter.

¹https://github.com/ryakh/emberoverflow/tree/4c4d250bd2c7df5a8fb2bbbe15d1eddafe6260f0

Page 19: emberjs_applications.pdf

First Templates, First Routes 11

You can pass in different options into the create() method of Ember.Application objectrepresented by a plain JavaScript object. Here are some of the options you can provide:

1 Ember.Application.create({

2 // sets application root element

3 rootElement: '#element-id',

4

5 // logs out a message to the console once the URL changes

6 // which is useful for debugging

7 LOG_TRANSITIONS: true

8 });

But what else does that single line of code do under the hood? It will add event listeners to ourdocument which will be handled by Ember. Then it will render an application template. And finallyit will set up a router to listen to URL changes and respond in a correct way.

2.1 Gentle Introduction to the Templates

So creating our applicationwill render an application template for us. In our applicationwe currentlyhave 2 templates defined:

index.html

11 <script type="text/x-handlebars">

12 <h2>Welcome to Ember.js</h2>

13

14 {{outlet}}

15 </script>

16

17 <script type="text/x-handlebars" id="index">

18 <ul>

19 {{#each item in model}}

20 <li>{{item}}</li>

21 {{/each}}

22 </ul>

23 </script>

Page 20: emberjs_applications.pdf

First Templates, First Routes 12

The template without an ID is an application template. It will be rendered on each page by default.All other templates will be rendered inside the application template. Ember expects every templatename to be unique. Note the {{outlet}}² expression. Ember will always render other templates intothe {{outlet}} expression.

So whenever you request a route by changing a URL in a browser, Ember will render the templatethat belongs to the route requested. The second template has ID equal to index. That means that itwill be rendered in the index route which is always defined by default.

Last thing you need to know at this point about templates is that Ember uses Handlebars templatinglibrary. Handlebars templates are just like regular HTML, but they also give us the ability to embedexpressions into our templates and dynamically change what is displayed on our templates.

2.2 Adding our First Route

As we’ve learned in the previous part, Ember generates index route for us. But how can we defineour own route?

We need to tell our users more information about our web application. Lets add route to access thatpart of our application. Inside our app.js change the router to the following.

Code sample³:

js/app.js

3 App.Router.map(function() {

4 this.route('about');

5 });

Lets halt for a moment here and open Ember Inspector. Go into the Routes section and there youwill see our newly created route:

²Text inside two curly braces is called Handlebars expression. It will be picked up by the Handlebars library and parsed³https://github.com/ryakh/emberoverflow/commit/c314f3ffe57ebaf3fef7d9ad8c4afd3deca5c31f

Page 21: emberjs_applications.pdf

First Templates, First Routes 13

Routes Inside our Application

Note the routes Ember created for us, there are currently 5 of them:

1. application,2. loading,3. error,4. about,5. index.

Four routes were generated by Ember. About route is the one we’ve added manually. Note the RouteName column and remember the value which is written within it for the about route — we will useit to access the route in templates.

To navigate through our application we will use links. To create a link inside our template we willuse {{#link-to}}{{/link-to}} handlebars block expression.

There are two types of expressions in Handlebars library that are important for us in thecontext of this book. You have already seen an expression (a text between two curly braces;i.e. {{outlet}}). Expression will be evaluated by Ember and rendered into the template.Block expressions look something like this — {{#block}}Content{{/block}}. Commonuse case for block expression is to decorate content within it, iterate over a list of items orevaluate a true-false expression.

Page 22: emberjs_applications.pdf

First Templates, First Routes 14

Lets update our application template to hold a navigation using which our user will be able tonavigate through the application.

Code sample⁴:

index.html

11 <script type="text/x-handlebars">

12 <nav class="navbar navbar-default" role="navigation">

13 <div class="navbar-header">

14 {{#link-to 'index' classNames='navbar-brand'}}

15 Emberoverflow

16 {{/link-to}}

17 </div>

18

19 <ul class="nav navbar-nav">

20 <li>

21 {{#link-to 'about'}}

22 About site

23 {{/link-to}}

24 </li>

25 </ul>

26 </nav>

27

28 {{outlet}}

29 </script>

{{link-to}} block expression accepts different arguments. First one is the route into which a linkwill resolve. Remember the routes we viewed using Ember Inspector? Here we are passing in thename of the route (the one I’ve told you to remember). Handlebars will take the link-to block,parse it into regular <a></a> tag, apply all of the provided arguments to link and insert it into ourtemplate.

There are many other different arguments available. For example: activeClass, disabled,tagName and others. Last one will actually make Handlebars transform {{link-to}} blockexpression into the provided tag; so if you pass in tagName='li', instead of creating<a></a> tag Handlebars will create <li></li> tag and place content of your {{link-to}}into the tag.

⁴https://github.com/ryakh/emberoverflow/commit/1da47c96f610a848c6273abb467086df128c09db

Page 23: emberjs_applications.pdf

First Templates, First Routes 15

Now before we proceed, there is one more thing I want to show you. Add the following lines to ourstyle.css file:

Code sample⁵:

css/style.css

6 .active {

7 font-weight: bold;

8 }

Go ahead and try try to click on the links we have in our application. You will notice that thecurrently active link gets a class of active (and is displayed in bold). Ember gives you that featurefor free — currently active links will always get the class of active.

2.3 Providing Content for Context

We can navigate by clicking on our links but we have no content for the About page so far. Lets fixthis. Change index template and add another one called about:

Code sample⁶:

index.html

31 <script type="text/x-handlebars" id="index">

32 <div class="row">

33 <div class="col-md-8">

34 <h2>Welcome to Emberoverflow</h2>

35

36 <ul>

37 {{#each item in model}}

38 <li>{{item}}</li>

39 {{/each}}

40 </ul>

41 </div>

42 </div>

43 </script>

44

45 <script type="text/x-handlebars" id="about">

46 <div class="row">

47 <div class="col-md-8">

⁵https://github.com/ryakh/emberoverflow/commit/acc623c1046da32c29ac1efd679af67530f6c921⁶https://github.com/ryakh/emberoverflow/commit/4c4d250bd2c7df5a8fb2bbbe15d1eddafe6260f0

Page 24: emberjs_applications.pdf

First Templates, First Routes 16

48 <h2>About Emberoverflow</h2>

49

50 <p>

51 Emberoverflow is a question and answer site for programmers and

52 hamsters. We are a little bit different because we are written in

53 Ember.js

54 </p>

55 </div>

56 </div>

57 </script>

Go ahead and reload our application. Now if you click on the link, then content of the page willchange. What happens here illustrates one of the key ideas behind Ember — convention overconfiguration. We will be tripping over this aspect of Ember over and over again. Now just notethat for Ember it is enough to provide a route (an URL) and a template. Ember will match the URLto the template’s name and render the needed content.

Open up Ember Inspector once again, go into the Routes section and search for the column namedTemplate; there you will see the name of the template Ember expects you to provide for the givenroute, which in our case is about.

Page 25: emberjs_applications.pdf

3 ControllersTopics covered in this chapter:

1. creating our first controller,2. working with properties,3. controller types in Ember.

Snapshot of the application¹.

If you have experience with another web framework such as Rails or Django you may think thatyou already know what a controller in Ember is and what it will be responsible for. Let me tell youright away — you are wrong. Forget everything you know about controllers from server sidewebframeworks.

Although you may have heard other people calling Ember an MVC framework, that is notaccurate. Yes, Ember has objects that are called models, views and controllers but thatswhere the similarity to MVC frameworks ends. My biggest concern while learning Emberwas to wrap my head around naming conventions. Once I’ve let the Rails MVC modelgo it was much easier for me to understand the internals of Ember. Ember is not a MVCframework. Ember is a framework for building client side web applications.

Controller in Ember is responsible for decorating data before presenting it to the user. Think of acontroller as of a middleman sitting between your data source and your user, translating languageof one into another.

3.1 Our First Controller

But how do we tell Ember which controller is responsible for which route? Ember is highly opin-ionated and values convention over configuration. We have two routes defined in our application— index and about. According to Ember conventions, Ember will expect our application to havetwo controllers defined: IndexController and AboutController. If Ember doesn’t find these twocontrollers (or any other specific controller or even any other object such as route or view), it willgenerate the one behind the scenes and keep it in memory for later use.

¹https://github.com/ryakh/emberoverflow/tree/d1c89c707ab3d418bbcc5db0a7951bd7f76c2e55

Page 26: emberjs_applications.pdf

Controllers 18

You are probably familiar with code generators. The ones that create a basic code scaffoldand put that scaffold into your project folder. Ember code generator works in a differentway. You don’t have to tell it in advance to generate a piece of code. Instead it will generatethe code when it needs it (which in most cases means that you didn’t provide the code)and instead of creating code file on your hard drive Ember will create one in memory anddestroy it once it is not needed anymore. This technique is great because it frees you fromwriting boilerplate code and maintaining it later.

Lets create our first controller — IndexController.

Code sample²:

js/app.js

13 App.IndexController = Ember.Controller.extend({

14 siteTitle: 'Welcome to Emberoverflow'

15 });

siteTitle is called property. Properties will be available inside our templates. To access propertieswe use Handlebars expressions.

Code sample³:

index.html

31 <script type="text/x-handlebars" id="index">

32 <div class="row">

33 <div class="col-md-8">

34 <h2>{{siteTitle}}</h2>

35

36 <ul>

37 {{#each item in model}}

38 <li>{{item}}</li>

39 {{/each}}

40 </ul>

41 </div>

42 </div>

43 </script>

To see what we did go ahead and reload our application. You will see no visual change but onceyou change the value of siteTitle property inside our controller it will be changed inside ourapplication as well.

²https://github.com/ryakh/emberoverflow/commit/d66111735a0243738d8105d710a323782ebbb577³https://github.com/ryakh/emberoverflow/commit/9c82333375fb5e8307e6d624345d7b5601103f21

Page 27: emberjs_applications.pdf

Controllers 19

View the source of our page and look for the header which contains siteTitle property. Here iswhat you will see:

What’s with weird script tags?

Note the script tags that Ember generated around the header:

Script tags generated by Ember

<script id="metamorph-2-start" type="text/x-placeholder"></script>

Welcome to Emberoverflow

<script id="metamorph-2-end" type="text/x-placeholder"></script>

Ember uses metamorph script tags to wrap properties we set in our controller so it can easily identifythem and update if needed.

Page 28: emberjs_applications.pdf

Controllers 20

But what about attributes that are parts of another tag? Ember will insert the script tags around ourattribute, so something like this won’t work:

Inserting inline attribute, the wrong way

<img src="{{imageSource}}">

This will generate the following code:

Inserting inline attribute, the wrong way

<img src="&lt;script id='metamorph-7-start' type='text/x-placeholder'&gt;&lt;/scr\

ipt&gt;image.jpg&lt;script id='metamorph-7-end' type='text/x-placeholder'&gt;&lt;\

/script&gt;">

Ember just wrapped the value of imageSource property into the script tags. So how can we solvethat? Here we will use bound attributes (they are called this way because the value of the boundattribute is bound to the controller). To render an image from the imageSource attribute we willhave to use the following syntax:

Inserting inline attribute using bound attribute

<img {{bind-attr src=imageSource}}>

Which will generate the correct HTML:

Inserting inline attribute using bound attribute

<img src="image.jpg" data-bindattr-1="1">

In this case Ember will use the data-bindattr-1="1" attribute to track down the property andupdate it if needed.

Page 29: emberjs_applications.pdf

Controllers 21

3.2 Properties as Functions

But what if we want to compute a property?⁴ Let’s say we want to display the current time in ourtemplate. We can do this using a function. You’d probably guess something like this will work.

Code sample⁵:

js/app.js

13 App.IndexController = Ember.Controller.extend({

14 siteTitle: 'Welcome to Emberoverflow',

15

16 currentTime: function() {

17 return(new Date);

18 }

19 });

And don’t forget to update our index.html.

Code sample⁶:

index.html

31 <script type="text/x-handlebars" id="index">

32 <div class="row">

33 <div class="col-md-8">

34 <h2>{{siteTitle}}</h2>

35 <p>It is {{currentTime}}</p>

36

37 <ul>

38 {{#each item in model}}

39 <li>{{item}}</li>

40 {{/each}}

41 </ul>

42 </div>

43 </div>

44 </script>

⁴In Ember computed properties are properties defined as a function which Ember will call when we ask for the property⁵https://github.com/ryakh/emberoverflow/commit/b501531e80b307c9c4ff06f0331a7a71abadd51e⁶https://github.com/ryakh/emberoverflow/commit/9881f8d289170416f0e07d4b2e230576bc39788d

Page 30: emberjs_applications.pdf

Controllers 22

If you reload the page youwill see It is function () { return(new Date); }which is probably notwhat we wanted. Thats because we need to explicitly tell our controller that a property is a indeeda property and can be called inside our template. We can do so by adding .property() method atthe end of our function.

Code sample⁷:

js/app.js

13 App.IndexController = Ember.Controller.extend({

14 siteTitle: 'Welcome to Emberoverflow',

15

16 currentTime: function() {

17 return(new Date);

18 }.property()

19 });

If you reload our application, you should see the correct date and time.

3.3 Ember Controller Types

There are two controller types. Both of them inherit from Controller object. They are ArrayCon-troller and ObjectController.

Controller should be used when your controller is not a proxy (i.e. it is not an object or array ofobjects; for example an application controller) or it should be used when you are building your owncontroller type.

ObjectController is used to represent a single model.

ArrayController is used to represent an array of models. ArrayController will consist of manyObectController’s for each model in array.

⁷https://github.com/ryakh/emberoverflow/commit/d1c89c707ab3d418bbcc5db0a7951bd7f76c2e55

Page 31: emberjs_applications.pdf

4 Routing, View Tree and NamingConventions

Topics covered in this chapter:

1. introduction to route objects,2. brief explanation of the request cycle,3. setting model for the template,4. inspecting View Tree with Ember Inspector,5. naming conventions.

Snapshot of the application¹.

Back in first chapter we left a piece of codewhichwas provided inside Ember Starter Kit unexplained.I did that on purpose because at that point we lacked knowledge and it could be a little bit confusingtrying to explain the route object. Now that you know about templates and controllers lets dig intothe routes. Open our app.js and search for IndexRoute:

js/app.js

7 App.IndexRoute = Ember.Route.extend({

8 model: function() {

9 return ['red', 'yellow', 'blue'];

10 }

11 });

Now this may seem a little bit confusing at first glance. We have a router and then we are definingseparate routes using route objects. And the model? Isn’t it controller’s responsibility to provide amodel to the template? Well, no. Remember how I told you to forget everything you know aboutMVC frameworks in the previous chapter? If you didn’t do it back then now it’s a good time to freeyour mind. Don’t try to understand Ember with the knowledge you have from other frameworks —it will not get you anywhere and will hurt your brain.

Lets recapitulate everything we know so far. We have an application. Application has a router whichreceives requests made by user (which are represented by URLs and are typed into the browseraddress bar) and then router sends user requests to the controller. Controller decorates our data

¹https://github.com/ryakh/emberoverflow/tree/784253175719387bc99278d3f942002f358f00e4

Page 32: emberjs_applications.pdf

Routing, View Tree and Naming Conventions 24

and sends the data to the template. Template displays data to the user. Wait, but if controller onlydecorates data, where does the data come from? This is where the route comes in.

Router defines routes for our application and tells our application which routes to acceptfrom the user. Request made to router are further handled by routes objects. Route objectsare responsible for loading data from defined data storage and providing the data to thecontroller. Controller decorates the data received from route and sends it to the templateso it can be displayed to our user.

Lets get back to our code. We have model property defined in our IndexRoute. It returns an array ofhard coded values.

Ember expects model property of a route object to return either an object or an array.

Now lets take a look at the index template:

index.html

31 <script type="text/x-handlebars" id="index">

32 <div class="row">

33 <div class="col-md-8">

34 <h2>{{siteTitle}}</h2>

35 <p>It is {{currentTime}}</p>

36

37 <ul>

38 {{#each item in model}}

39 <li>{{item}}</li>

40 {{/each}}

41 </ul>

42 </div>

43 </div>

44 </script>

Here is what happens inside our application — it receives a request to show the index route. UsingEmber conventions it dispatches the request to IndexRoute, there we set themodel which is then sentinto the IndexController, IndexController decorates our data, provides additional properties andsends the data to the template which renders the data with the help of {{each}} block expression.

We are building Q&A site, so the red, yellow and blue are probably not kind of values we want ourusers to see. Lets make our IndexRoute display list of latest questions. Open our IndexRoute andchange it to look like following.

Page 33: emberjs_applications.pdf

Routing, View Tree and Naming Conventions 25

Code sample²:

js/app.js

7 App.IndexRoute = Ember.Route.extend({

8 model: function() {

9 var questions = [

10 {

11 title: 'How do I feed hamsters?',

12 author: 'Tom Dale'

13 },

14

15 {

16 title: 'Are humans insane?',

17 author: 'Tomster the Hamster'

18 }

19 ]

20

21 return questions

22 }

23 });

If you reload our application, you will see a list of two items each having a value of [objectObject].That happens because within the {{each}} block expression we are printing out an item fromwithinthe model (which is an object). What we need to do is to provide a values from object instead.

Code sample³:

index.html

31 <script type="text/x-handlebars" id="index">

32 <div class="row">

33 <div class="col-md-8">

34 <h2>{{siteTitle}}</h2>

35 <p>It is {{currentTime}}</p>

36

37 <ul>

38 {{#each item in model}}

39 <li>

40 {{item.title}} — asked by {{item.author}}

41 </li>

²https://github.com/ryakh/emberoverflow/commit/1ce63fed627c04e90c6e76da272ff98db5b559b1³https://github.com/ryakh/emberoverflow/commit/e0d693fe3364494ded4be7d58a2398342afc6bdd

Page 34: emberjs_applications.pdf

Routing, View Tree and Naming Conventions 26

42 {{/each}}

43 </ul>

44 </div>

45 </div>

46 </script>

Now if you remember controller types described in the previous chapter you probably have guessedthat we need to use ArrayController in this case (although the solution presented above will work).So lets change our controller from Controller to ArrayController.

Code sample⁴:

js/app.js

25 App.IndexController = Ember.ArrayController.extend({

26 siteTitle: 'Welcome to Emberoverflow',

27

28 currentTime: function() {

29 return(new Date);

30 }.property()

31 });

⁴https://github.com/ryakh/emberoverflow/commit/0df321f857521c5cf90dc2babe24a093453c5f75

Page 35: emberjs_applications.pdf

Routing, View Tree and Naming Conventions 27

Find {{#each item in model}} block expression in our template. If we do not provide itwith any further arguments Ember will look for a model that we passed from IndexRoute

to the IndexController. This way we can simplify our template to the following syntax.

Code sample⁵:

index.html

31 <script type="text/x-handlebars" id="index">

32 <div class="row">

33 <div class="col-md-8">

34 <h2>{{siteTitle}}</h2>

35 <p>It is {{currentTime}}</p>

36

37 <ul>

38 {{#each}}

39 <li>

40 {{title}} — asked by {{author}}

41 </li>

42 {{/each}}

43 </ul>

44 </div>

45 </div>

46 </script>

⁵https://github.com/ryakh/emberoverflow/commit/784253175719387bc99278d3f942002f358f00e4

Page 36: emberjs_applications.pdf

Routing, View Tree and Naming Conventions 28

4.1 Inspecting the View Tree

Time for some theory about Ember Inspector. Open up the View Tree. There you will see table withtwo entries: application and index. View Tree represents the layout of the currently active route.If you hover over the single entries, Ember Inspector will highlight routes with some additionalinformation:

Ember Inspector — View Tree

There are two entries in the View Tree at this moment — application and index. Ember will renderapplication route and then nest all other currently active routes underneath it. In our case indexis rendered “inside” application.

The View Tree table has five different columns — name, template, model, controller and view. Eachone represents the object of Ember framework used to render the route.

You can click on any entry in the table to show more information about it. For example ifyou’d like to see more information about IndexController, simply click on it in the ViewTree.

Using the View Tree you can always quickly find out which parts of our application were used torender the route we are currently seeing.

Page 37: emberjs_applications.pdf

Routing, View Tree and Naming Conventions 29

4.2 Naming Conventions

Ember is very opinionated framework. It will identify which objects to stitch together based only ontheir names. It surely is possible to override the names with your own, although it will be better tostick to Ember defaults. So far we’ve covered templates, controllers, router and routes. These Emberobjects can be linked to one another only by setting the correct names (there are also views that canbe linked with other objects; we will cover them in depth later).

This table illustrates naming conventions used in Ember:

Route Name Route Controller Template View

index IndexRoute IndexController index IndexView

about AboutRoute AboutController about AboutView

So if we navigate to index route, Ember will search for route, controller, template and view accordingto it’s naming conventions and if none are found it will generate one for us and keep it in memory(if you look back into the View Tree inside Ember Inspector and hove over the index route, you willsee that the value of the View is set to virtual — that means that the View is generated for us).

Page 38: emberjs_applications.pdf

5 First TestsTopics covered in this chapter:

1. anatomy of Ember tests,2. Ember Test Helpers,3. writing first tests.

Snapshot of the application¹.

So we are en route creating our first application which one day will hopefully become as popularas Stackoverflow. At this point we already have some code which potentially could be broken. Toinsure that as we start introducing changes we do not brake anything we are going to grow a testsuite. In this chapter we are going to build our first tests.

Ember Starter Kit came with a tests directory. It contains the basic scaffold that you will needto start testing your application and a qUnit testing framework². To launch tests simply visitindex.html?test in your browser.

You may have noticed the runner.js file. It intercept the index.html?test request and setseverything up for testing our application and then it loads test.js which contains theactual tests.

¹https://github.com/ryakh/emberoverflow/tree/cd62e33554d89f2b9af13494c4a14f0094ac6939²http://qunitjs.com/

Page 39: emberjs_applications.pdf

First Tests 31

Launching tests for the first time

If you launch tests right away you will see something similar to the screenshot — 1 assertions of2 passed, 1 failed. The error we got says “Application header is rendered test failed”. It also showsus the expected result, the result we got and the difference between two. Lets dig dipper into ourtest.js file and try to figure out what’s going on there.

In order to test our application we need to run the app within our testing framework. We do so bysetting the rootElement (the #ember-testing element is created within runner.js file):

tests/tests.js

2 App.rootElement = '#ember-testing';

Page 40: emberjs_applications.pdf

First Tests 32

Next we need to defer the readiness of our application (so it would be run once the tests are running),set the router location to none (so that the window’s location will not be modified which will preventleaking of state between tests) and finally inject the test helpers into the window’s scope. All of thisis done with these two simple lines of code:

tests/tests.js

5 App.setupForTesting();

6 App.injectTestHelpers();

Here is a list of available test helpers:

1. visit(url) — visits the route and returns a promise.2. find(selector, context) — finds an element within application’s root element

and within the context (which is optional).3. fillIn(input_selector, text) — fills in the selected input with the given text.4. click(selector) — clicks an element returned by provided selector.5. keyEvent(selector, type, keyCode) — simulates, on an element returned by

provided selector, a key event specified by type (keeypress, keydow, keyup) withthe desired keyCode.

And finally we define setup() and teardown() methods. These methods will be executed beforeand after each single test:

tests/tests.js

9 module("Integration tests", {

10 setup: function() {

11 Ember.run(App, App.advanceReadiness);

12 },

13

14 teardown: function() {

15 App.reset();

16 }

17 });

Now we are ready to define our first test. Ember provides base for integration testing and gives usone pre-defined test with two assertions.

There are many other frameworks to test your Ember client side applications, but we will not usethem in this book. We will rely on the default stack provided by Ember.

Page 41: emberjs_applications.pdf

First Tests 33

5.1 Testing all the JavaScripts

Lets take a look at the test that Ember Starter Kit provided us with (I’ve formatted it a little bit so itwill fit into the format of the book better).

Code sample³:

tests/tests.js

21 test("/", function() {

22 visit("/");

23

24 andThen(function() {

25 equal(

26 find("h2").text(),

27 "Welcome to Ember.js",

28 "Application header is rendered"

29 );

30

31 equal(

32 find("li").length,

33 3,

34 "There are three items in the list"

35 );

36 });

37 });

First line is the name of the test (let’s change it to something more meaningful, like “index pagehas a title and a list of questions” — so if the test fails you can track the failing test down easily).Then we are calling for a test helper visit() with a route we expect our test to visit. andThen()helper ensures that our test will wait until our application is idle (until all the promises and otherasync behaviour resolves), and only then it will execute the code inside anonymous function passedas an argument to it (this way we can be sure that our test assertions won’t be executed before theapplication is fully loaded).

Now lets take a look at the assertions. First one is failing because we’ve changed the title of the page.So lets alter the first assertion to make it pass.

³https://github.com/ryakh/emberoverflow/commit/57ac97092c5a5e8821e9413a438d7c081ec689f6

Page 42: emberjs_applications.pdf

First Tests 34

Code sample⁴:

tests/tests.js

25 equal(

26 find("h2").text(),

27 "Welcome to Emberoverflow",

28 "Application header is rendered"

29 );

equal() method takes in an expression, a value into which an expression must evaluate and finallythe name of assertion. The last one is up to you — but be sure to provide something meaningful. Nowif you reload our tests everything should pass. But wait, something is wrong. As you’ve probablyguessed, the second assertion expects our application to contain three li items. But we only havetwo questions. Here is what assertion looks like:

tests/tests.js

31 equal(

32 find("li").length,

33 3,

34 "There are three items in the list"

35 );

We are looking for any li item. And since we have a global navigation which is defined using a list,it will fetch the one item from the navigation and two items from the list of questions which willresult into three li items total. So lets alter out assertion.

Code sample⁵:

tests/tests.js

31 equal(

32 find("ul:not(.nav) > li").length,

33 2,

34 "There are two questions in the list"

35 );

⁴https://github.com/ryakh/emberoverflow/commit/61f7696b8e635de7588e45b166c0a17ffbd736b3⁵https://github.com/ryakh/emberoverflow/commit/cd62e33554d89f2b9af13494c4a14f0094ac6939

Page 43: emberjs_applications.pdf

First Tests 35

Re-run our tests — they should pass.

All tests are green

Probably in the feature if we decide to add more lists and list items to our route, we will need adifferent selector. But for now this one will do.

Before you go berserk writing tests you should know that writing tests costs money. Andbefore knowing what to test you should know what not to test. In real life application itprobably won’t be wise to waste time on testing static parts like About section since thereis no functionality that can be broken. So before you start writing a test, ask yourself if itwill be worth it.

Page 44: emberjs_applications.pdf

6 Working with Real DataTopics covered in this chapter:

1. defining the store,2. creating fixtures,3. returning records from store,4. dynamic segments in routes,5. difference between route and resource.

Snapshot of the application¹.

In 5th chapter we provided our controller with a model which was created from a hard codedJavaScript object. While creating a real world application we probably would want to work withdata that is not hard coded into route object. Ember gives us an ability to define our applicationstore; store will contain all of the data for records loaded from the external source. Inside the storewe need to provide an adapterwhich will tell Ember where from the data should be loaded. We aregoing to define our store using the Fixture adapter.

Fixtures represent data hardcoded into separate file which by it’s structure is very similarto the JSON response that our application will receive from the server. FixtureAdapter willallow us to develop our application, and then switch to another adapter once the API isready without making any changes to your application code.

6.1 Defining Fixtures

So lets define our adapter (add these lines right after the first line in our app.js file).

¹https://github.com/ryakh/emberoverflow/tree/8a9b08970dd72a14f52ab90eb8aabc4caa73fce4

Page 45: emberjs_applications.pdf

Working with Real Data 37

Code sample²:

js/app.js

3 App.Store = DS.Store.extend({

4 adapter: DS.FixtureAdapter

5 });

Before we define our fixtures lets reorganise our code a little bit since our application is growingand soon we will have so much code in a single file that it will be unmaintainable. Lets createthe following directories inside our js/ directory to hold different parts of our application:controllers/, routes/ and fixtures/. Now lets move all the controller code from our app.js

into controllers/application_controllers.js file and the route we have defined so far intoroutes/apllication_routes.js. Do not forget to include our newly created files in the index.htmlright after the inclusion of our app.js script.

Code sample³:

index.html

68 <script src="js/app.js"></script>

69

70 <script src="js/routes/application_routes.js"></script>

71

72 <script src="js/controllers/application_controllers.js"></script>

Now lets create js/fixtures/question_fixtures.js file and give it following content (don’t forgetto include question_fixtures.js file in app.js).

Code sample⁴:

js/fixtures/question_fixtures.js

1 App.Question.FIXTURES = [

2 {

3 id: 101,

4 title: 'How do I feed hamsters?',

5 author: 'Tom Dale',

6 date: '2013-01-01T12:00:00',

7 question: 'Tomster cant eat using knife and a fork because his hands are \

8 too small. We are looking for a way to feed him. Any ideas?'

²https://github.com/ryakh/emberoverflow/commit/ef17243713a8453f2e9609d68d2cc62e317a9733³https://github.com/ryakh/emberoverflow/commit/3c24ff86616234a57aad5b747fc45e12a8dc2be7⁴https://github.com/ryakh/emberoverflow/commit/3134f0674d5b081daab241b4456f4cfc0d7c7710

Page 46: emberjs_applications.pdf

Working with Real Data 38

9 },

10 {

11 id: 102,

12 title: 'Are humans insane?',

13 author: 'Tomster the Hamster',

14 date: '2013-02-02T12:00:00',

15 question: 'I mean are totaly nuts? Is there any hope left for them? Should \

16 we hamsters try to save them?'

17 }

18 ];

Each fixture should have a unique ID. It doesn’t have to be in a sequence of any kind; wecan use different ranges for different kind of fixtures. Here we will use range of 100–199for question fixtures.

But how do we access our data and work with it? Once we tell Ember where to fetch data from itwill do so and store the data in the store. Later we can fetch the data from the store inside our routes.But we need to define properties and behaviour of the data before we can present it to the user. Wedo that by creating a model object. So create a folder called models/ in our js/ folder and createquestion_model.js with the following content.

Code sample⁵:

js/models/question_model.js

1 App.Question = DS.Model.extend({

2 title: DS.attr('string'),

3 question: DS.attr('string'),

4 date: DS.attr('date'),

5 author: DS.attr('string')

6 });

There are four different type of attributes you may use inside your model: string, date,boolean and number. If you don’t explicitly provide an attribute type, Ember will try toguess and set it automatically.

⁵https://github.com/ryakh/emberoverflow/commit/5c9508cb16d5b1aadc61b50d25e56891d29db35f

Page 47: emberjs_applications.pdf

Working with Real Data 39

Last thing left for us to do is to define a model inside our IndexRoute. Change the model propertyto look like this.

Code sample⁶:

js/routes/application_routes.js

1 App.IndexRoute = Ember.Route.extend({

2 model: function() {

3 return this.store.findAll('question');

4 }

5 });

Don’t forget to update our index.html.

Code sample⁷:

index.html

68 <script src="js/app.js"></script>

69

70 <script src="js/models/question_model.js"></script>

71

72 <script src="js/fixtures/question_fixtures.js"></script>

73

74 <script src="js/routes/application_routes.js"></script>

Reload our application and at this point you shouldn’t be able to see any visual differences. OpenEmber Inspector and go into the Data tab. There you will see list of all available Model types, onceclicked on the model type, Ember inspector will display all records of that type and once you clickon the record itself, ember will provide you with information about the record.

⁶https://github.com/ryakh/emberoverflow/commit/a0126b68654f823488d32f779ae9006a5d94ea0f⁷https://github.com/ryakh/emberoverflow/commit/95a0bd252acac0db2687627be5400891b8041c2d

Page 48: emberjs_applications.pdf

Working with Real Data 40

Inspecting record

6.2 Navigating Through the Application

So we have a list of questions, each question has an author, title and a body. But we are displayingonly the first two properties on our index route. Lets give our user an ability to click on the questiontitle to display the question itself. To do this we will need a route in a router, a route, a controllerand a template.

Lets begin by adding a route to the router. Open app.js file and add the following line to our router.

Page 49: emberjs_applications.pdf

Working with Real Data 41

Code sample⁸:

js/app.js

7 App.Router.map(function() {

8 this.route('about');

9 this.resource('question', { path: '/:question_id' });

10 });

With this single line we introduced two new concepts here. Lets start with dynamic segments. Adynamic segment is a portion of path that starts with colon — : and is followed by an identifier.Remember the convention over configuration thing? Here it is again; here we used :question_id

as our dynamic segment. So unless we override the model in our route, Ember will pick the modelname from the path; in our case it will be question.

This will work with the names that mimic the name of the model and have _id suffix. Youcan use anything as a dynamic segment but you will have to define a serialize method ona route if you do.

The second thingwe introduced in our routes is a route vs resource concept. The difference betweenthem is very subtle; probably this is the best way to sum it up: resource is a thing (a model) and aroute is something that you do with the model.

Remember, you can always check you routes using Ember Inspector! You can use it to findour expected templates, routes and controller names.

Now add a template into our index.html.

Code sample⁹:

index.html

48 <script type="text/x-handlebars" id="question">

49 <div class="row">

50 <div class="col-md-8">

51 <h2>{{title}}</h2>

52

53 <p>

54 {{question}}

55 </p>

⁸https://github.com/ryakh/emberoverflow/commit/875f457816219796dce2c5ce03cc6a98db6e9d50⁹https://github.com/ryakh/emberoverflow/commit/8aba4409c98742dea4093d9ed6193b95c1dad081

Page 50: emberjs_applications.pdf

Working with Real Data 42

56

57 <p>

58 Asked by {{author}}

59 </p>

60 </div>

61 </div>

62 </script>

And finally alter an existing index template so it will contain links to questions instead of staticmarkup.

Code sample¹⁰:

index.html

31 <script type="text/x-handlebars" id="index">

32 <div class="row">

33 <div class="col-md-8">

34 <h2>{{siteTitle}}</h2>

35 <p>It is {{currentTime}}</p>

36

37 <ul>

38 {{#each}}

39 <li>

40 {{#link-to 'question' this}}

41 {{title}}

42 {{/link-to}}

43

44 (asked by {{author}})

45 </li>

46 {{/each}}

47 </ul>

48 </div>

49 </div>

50 </script>

Now before proceeding any further, go ahead and reload our application. You will see that itemsin our list of questions turned into hyperlinks (thanks to {{link-to}} block expression into whichwe passed two arguments — a name of the route and an instance of question object). Go ahead andclick on the link. It works! But how is that possible? We still didn’t define neither a controller or aroute. Surprise, convention over configuration! Ember generated the missing objects for us once itfound that we didn’t provide any.

¹⁰https://github.com/ryakh/emberoverflow/commit/ae1c463f8d05638ebfdba06c26f592ec6cb93a18

Page 51: emberjs_applications.pdf

Working with Real Data 43

6.3 Inspecting Promises

Ember’s router makes heavy use of the concept of promises. If you are not familiar with the promisesI encourage you to spend some time learning more about them.

Promises are objects that can potentially be resolved into any value. A promise can eitherbe fulfilled or rejected. Here is a good starting point¹¹ to learn more about promises inJavaScript.

Ember Inspector provides us with ability to inspect promises. Considering the fact that promises canget pretty tricky sometimes this is a pretty helpful feature.

Open Promises tab in Ember Inspector and you should see the list of all promises. Each promisedisplayed has four values:

1. label,2. state,3. fulfilment/rejection value,4. time to settle.

Label represents the name of the promise. State shows in which the promise is (can be Fulfilled,Rejected or Pending). The value provides us with value that promise returned. And finally time tosettle tells us how long it took promise to resolve.

¹¹http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt1-theory-and-semantics

Page 52: emberjs_applications.pdf

Working with Real Data 44

Inspecting promises

Inside Ember Inspector you will see a pretty long list of promises. Most of them are initiated byEmber. Look for a promise called DS: FixtureAdapter#simulateRemoteCall. This promise wascalled inside our index route and it returned a list of all questions we defined in our fixtures. If youclick on the text in the Fulfilment/Rejection value column you can inspect the value returned bythe promise.

Page 53: emberjs_applications.pdf

Working with Real Data 45

6.4 Updating Our Tests

We introduced changes to our code, lets write a simple test to cover them. Open up our tests.jsfile and add the test.

Code sample¹²:

tests/tests.js

39 test("quesion links on index page lead to questions", function() {

40 visit("/");

41 click("ul:not(.nav) > li > a:first");

42

43 andThen(function() {

44 equal(

45 find("h2").length,

46 1,

47 "Question header is rendered"

48 );

49

50 equal(

51 find("p").length,

52 2,

53 "Question and author are rendered"

54 );

55 });

56 });

We are not testing the content that header or paragraphs have in them for a reason. We are clickingon the first link in the list and that can lead to any question. Instead we are expecting our page tohave a h2 header and two paragraphs containing the attributes of our question. Once the applicationgrows, or while working on a bigger application, we would to want to define our selector a littlebit better since our page could have multiple elements that will meet the selector requirement butwon’t meet the logic of our test.

¹²https://github.com/ryakh/emberoverflow/commit/8a9b08970dd72a14f52ab90eb8aabc4caa73fce4

Page 54: emberjs_applications.pdf

7 Relating ModelsTopics covered in this chapter:

1. relating data in your application,2. relation types in Ember,3. accessing related objects in templates.

Snapshot of the application¹.

It’s a common case that inside your application you would like to relate models together (to saywhich one belongs to another and in what kind of relation). Ember Data includes several built-inrelationship types to help you define how your models relate to each other.

7.1 Preparing Fixtures and Models

Knowledge we already have so far will be enough to prepare the basic scaffold. At this point weneed to create user fixtures and user model.

Here are the user fixtures.

Code sample²:

js/fixtures/user_fixtures.js

1 App.User.FIXTURES = [

2 {

3 id: 201,

4 fullname: 'Tom Dale',

5 email: '[email protected]'

6 },

7 {

8 id: 202,

9 fullname: 'Tomster the Hamster',

10 email: '[email protected]'

11 }

12 ];

¹https://github.com/ryakh/emberoverflow/tree/43a5c1b9d1d8116f4c00fcdf454981a688fc10de²https://github.com/ryakh/emberoverflow/commit/fc8b17c52a62718e6f527cc3c34889c309fbb0af

Page 55: emberjs_applications.pdf

Relating Models 47

And model with two attributes.

Code sample³:

js/models/user_model.js

1 App.User = DS.Model.extend({

2 fullname: DS.attr('string'),

3 email: DS.attr('string')

4 });

Don’t forget to include newly created files in our index.html!

7.2 Creating Relations

So we have a question model, each question is asked by a user. You have probably guessed thatthese two need to be related. We can do that by altering our code. Bear with me, there will be a lotof changes and after we do them I will explain them in more detail.

Code sample⁴:

js/models/user_model.js

1 App.User = DS.Model.extend({

2 fullname: DS.attr('string'),

3 email: DS.attr('string'),

4 questions: DS.hasMany('question', { async: true })

5 });

³https://github.com/ryakh/emberoverflow/commit/9c76c07a9acb4c0feb61a14621caba650be3d28b⁴https://github.com/ryakh/emberoverflow/commit/cdda80ac2eef88cae496f32df7a61d1f0561899f

Page 56: emberjs_applications.pdf

Relating Models 48

Code sample⁵:

js/models/question_model.js

1 App.Question = DS.Model.extend({

2 title: DS.attr('string'),

3 question: DS.attr('string'),

4 date: DS.attr('date'),

5 author: DS.belongsTo('user')

6 });

Code sample⁶:

js/fixtures/user_fixtures.js

1 App.User.FIXTURES = [

2 {

3 id: 201,

4 fullname: 'Tom Dale',

5 email: '[email protected]',

6 questions: [101]

7 },

8 {

9 id: 202,

10 fullname: 'Tomster the Hamster',

11 email: '[email protected]',

12 questions: [102]

13 }

14 ];

⁵https://github.com/ryakh/emberoverflow/commit/3cfb4e9a468e398057a4c26b0a267740c614a4df⁶https://github.com/ryakh/emberoverflow/commit/cdd5bdfbc1ad77ee8c6bd3cc58bc64a5c3a482ce

Page 57: emberjs_applications.pdf

Relating Models 49

Code sample⁷:

js/fixtures/question_fixtures.js1 App.Question.FIXTURES = [

2 {

3 id: 101,

4 title: 'How do I feed hamsters?',

5 author: 201,

6 date: '2013-01-01T12:00:00',

7 question: 'Tomster cant eat using knife and a fork because his hands are \

8 too small. We are looking for a way to feed him. Any ideas?'

9 },

10 {

11 id: 102,

12 title: 'Are humans insane?',

13 author: 202,

14 date: '2013-02-02T12:00:00',

15 question: 'I mean are totaly nuts? Is there any hope left for them? Should \

16 we hamsters try to save them?'

17 }

18 ];

Lets start withmodels. Inside usermodel we’ve added a single line: questions: DS.hasMany('question',

{ async: true }). This line tells Ember that a single user can have many questions (a simple oneto many relation). Note the { async: true } option — it tells Ember to load data asynchronouslyfrom the server (i.e. it will make request only when the data is needed; in our case application willfirst make request for list of questions and then another request to get list of users for the questions).

You may chose not to use { async: true } option but then you have to make sure thatyour server returns not only the requested record but also all other related records in asingle response.

In our question model we’ve changed author: from author: DS.attr('string') to author:

DS.belongsTo('user'). Probably you would expect the relation to look something like this: user:DS.belongsTo('user'), which is also ok. In Ember you can name your relation whatever you like,but then you will have to address your relation by it’s name.

Ember offers you three types of relations out of the box: one-to-one relation which iscreated by defining a belongsTo on both models. one-to-many relation is the relation wedefined in our application in the previous step (it is defined by hasMany on one side andbelongsTo on another). many-to-many relation is defined by hasMany relations on bothsides.

⁷https://github.com/ryakh/emberoverflow/commit/4ea5333b6f4f2887fe5a1d88baba9694ca1548d6

Page 58: emberjs_applications.pdf

Relating Models 50

And finally we’ve changed our fixtures. Lets start with the user fixture. Here we added an array ofquestion ID’s to each user — questions: [101]. This way we are telling Ember which questionsuser asked. On the question side we’ve changed the value of author from a string to ID of the author— author: 201. This way we are telling Ember who asked the question.

Ember will try to cache the data on the client side, so next time you will ask our applicationto do something with the data already available (like change sorting or display data in adifferent way), our application will use cached data instead of doing full trip to the serverand back.

Now before we proceed any further go ahead and reload our application:

Our application is using relations

The <App.User> is displayed here because we called for author in out template and in case ofquestion author is another object related to our question. We need to change our template to callfor properties of author.

Page 59: emberjs_applications.pdf

Relating Models 51

Code sample⁸:

index.html

31 <script type="text/x-handlebars" id="index">

32 <div class="row">

33 <div class="col-md-8">

34 <h2>{{siteTitle}}</h2>

35 <p>It is {{currentTime}}</p>

36

37 <ul>

38 {{#each}}

39 <li>

40 {{#link-to 'question' this}}

41 {{title}}

42 {{/link-to}}

43

44 (asked by {{author.fullname}})

45 </li>

46 {{/each}}

47 </ul>

48 </div>

49 </div>

50 </script>

Time has come to run the tests. They all should pass and since we didn’t introduce any changes thatare worth testing, we will not introduce new tests in this chapter.

⁸https://github.com/ryakh/emberoverflow/commit/43a5c1b9d1d8116f4c00fcdf454981a688fc10de

Page 60: emberjs_applications.pdf

8 Managing the AuthenticationTopics covered in this chapter:

1. basic theory of managing user authentication in Ember,2. providing properties from one controller to another,3. controller actions,4. binding properties inside our application to dynamically update them.

Snapshot of the application¹.

We won’t allow just anybody to ask questions on Emberoverflow. We need a way to let our userslog into our application. But how are we going to do that? This part of book will be more theoretical;I will show you the way how to do it on the client side and will leave the backend implementationto you.

8.1 Big Authentication Theory

First you make a login request to the server. Server will tell you what kind of information it needsto authenticate you and requests you to provide the information. You send information back to theserver and in case information provided is correct (which means you can be authenticated) serverwill provide you with a unique bit of information (for example a token which you can store ina cookie) which you can use later with each request to tell the server that you’ve already beenauthenticated.

Lets apply this to our application. We need a place to store the unique string, then inside certainparts of our application we need to check if user is logged in and finally on every request made tothe server we need to send that unique string. Here we are going to use localStorage to store all thedata related to user authentication.

localStorage is not a regular JavaScript object so it can’t be directly observed by Ember.

¹https://github.com/ryakh/emberoverflow/tree/b28a7b81ad0724179286f8e6e002418a7706a0a7

Page 61: emberjs_applications.pdf

Managing the Authentication 53

8.2 Let the Authentication Begin

This is how things will work in theory: we will keep a value stored in localStorage to determinewether a user is logged in or not. Next we are going to have a SignInController which willencapsulate all the sign in logic for our application.

Lets begin by creating the controller, routes and templates.

First we are going to create a route to access the sign-in form.

Code sample²:

js/app.js

7 App.Router.map(function() {

8 this.route('about');

9 this.resource('question', { path: '/:question_id' });

10 this.route('sign-in');

11 });

Now we need a controller. I am going to call the file sing_in_controller.js.

Code sample³:

js/controllers/sign_in_controller.js

1 App.SignInController = Ember.Controller.extend({

2 });

Next thing we want to have is a template to hold our login form, lets add it to index.html.

²https://github.com/ryakh/emberoverflow/commit/90f3b175df6312aed943d876e140633f649aa352³https://github.com/ryakh/emberoverflow/commit/e761118a41c7f25062d3cfeee9318e602c0626f7

Page 62: emberjs_applications.pdf

Managing the Authentication 54

Code sample⁴:

index.html

82 <script type="text/x-handlebars" id="sign-in">

83 <div class="row">

84 <div class="col-md-8">

85 <form {{action signIn on="submit"}}>

86 <div class="form-group">

87 <label for="email">Email address</label>

88

89 {{input id="email"

90 value=email

91 classNames="form-control"

92 placeholder="Enter your email address"}}

93 </div>

94

95 <button class="btn btn-primary" type="submit">

96 Sign in

97 </button>

98 </form>

99 </div>

100 </div>

101 </script>

Note the {{action}} expression; we can define action on any element we want — even on a buttonitself, but the way we did it action will be fired when form is submitted — either by clicking a submitbutton or pressing enter with focus inside input. If we place action on button, submitting the formfrom keyboard won’t work.

Also lets update our application template to display the sign-in link.

⁴https://github.com/ryakh/emberoverflow/commit/718200b4b5227a15743da18b340f5cfb66294c47

Page 63: emberjs_applications.pdf

Managing the Authentication 55

Code sample⁵:

index.html

11 <script type="text/x-handlebars">

12 <nav class="navbar navbar-default" role="navigation">

13 <div class="navbar-header">

14 {{#link-to 'index' classNames='navbar-brand'}}

15 Emberoverflow

16 {{/link-to}}

17 </div>

18

19 <ul class="nav navbar-nav">

20 <li>

21 {{#link-to 'about'}}

22 About site

23 {{/link-to}}

24 </li>

25 </ul>

26

27 <ul class="nav navbar-nav">

28 <li>

29 {{#link-to 'sign-in'}}Sign in{{/link-to}}

30 </li>

31 </ul>

32 </nav>

33

34 {{outlet}}

35 </script>

If you reload our application and try to sign-in using any e-mail, you will see an error displayedin browser’s console once you submit the form: Uncaught Error: Nothing handled the action

'signIn'. That is pretty verbose. So we need to implement an action inside our SignInController.

⁵https://github.com/ryakh/emberoverflow/commit/586cd1dbe739435b7f3415a1db34636792b62830

Page 64: emberjs_applications.pdf

Managing the Authentication 56

Code sample⁶:

js/controllers/sign_in_controller.js

1 App.SignInController = Ember.Controller.extend({

2 actions: {

3 signIn: function() {

4 var email = this.get('email');

5 userToLogin = App.User.FIXTURES.findBy('email', email);

6 localStorage['currentUser'] = userToLogin.id;

7 }

8 }

9 });

If we were using any other adapter other than fixutres we would have usedthis.store.find('user', { email: email }) to query our storage instead ofApp.User.FIXTURES.findBy('email', email). Unfortunately fixture adapter does notsupport the syntax, so if you want to use it you will have to implement it yourself. Alsoin case of authentication you probably don’t want to query your store for user record butinstead send authentication request to the server to do all the heavy lifting there.

But now, even if you sign-in using an email defined in our fixtures file, you still won’t be able totell if you are signed-in or not. You can try and print out the contents of the localStorage by typinglocalStorage in the console of your browser:

1 > localStorage

2 > Storage {currentUser: "202", length: 1}

We need to define a property on our application which will allow us to track wether user is loggedin or not. I’m going to create that property inside ApplicationController.

⁶https://github.com/ryakh/emberoverflow/commit/b171f0c0cae758d5b91f0ba709f84d47a17248f1

Page 65: emberjs_applications.pdf

Managing the Authentication 57

Code sample⁷:

js/controllers/application_controllers.js

9 App.ApplicationController = Ember.Controller.extend({

10 signedInUser: function() {

11 return this.store.find('user', localStorage['currentUser']);

12 }.property(),

13

14 userSignedIn: function() {

15 return localStorage['currentUser'] != null;

16 }.property()

17 });

We defined two properties here. signedInUser will return an instance of the user currently signed-in. The userSignedIn property will return true if user is signed-in otherwise it will return false.

Note the find syntax we used here, as opposed to one we used in our SignInController.Unless we are searching for a record by any other property than ID, we can query thefixture storage using the standard syntax.

Lets update our application template with sing-in/sing-out links.

⁷https://github.com/ryakh/emberoverflow/commit/e56bb77c058edcbd85511a7215c0bab3125b069e

Page 66: emberjs_applications.pdf

Managing the Authentication 58

Code sample⁸:

index.html

11 <script type="text/x-handlebars">

12 <nav class="navbar navbar-default" role="navigation">

13 <div class="navbar-header">

14 {{#link-to 'index' classNames='navbar-brand'}}

15 Emberoverflow

16 {{/link-to}}

17 </div>

18

19 <ul class="nav navbar-nav">

20 <li>

21 {{#link-to 'about'}}

22 About site

23 {{/link-to}}

24 </li>

25 </ul>

26

27 <ul class="nav navbar-nav">

28 {{#if userSignedIn}}

29 <li>

30 <a href="#" {{action signOut}}>

31 Welcome, {{signedInUser.fullname}} — Sign out

32 </a>

33 </li>

34 {{else}}

35 <li>

36 {{#link-to 'sign-in'}}Sign in{{/link-to}}

37 </li>

38 {{/if}}

39 </ul>

40 </nav>

41

42 {{outlet}}

43 </script>

Now we need to update our sign-in template so it won’t show a form once user is already singned-in. Probably, with the knowledge you have so far, you’d guess something link this will work.

⁸https://github.com/ryakh/emberoverflow/commit/aded2b0493fb6a031f2e7d23449f6415bf672a08

Page 67: emberjs_applications.pdf

Managing the Authentication 59

Code sample⁹:

index.html

96 <script type="text/x-handlebars" id="sign-in">

97 <div class="row">

98 <div class="col-md-8">

99 {{#if userSignedIn}}

100 <p>You are already signed-in!</p>

101 {{else}}

102 <form {{action signIn on="submit"}}>

103 <div class="form-group">

104 <label for="email">Email address</label>

105

106 {{input id="email"

107 value=email

108 classNames="form-control"

109 placeholder="Enter your email address"}}

110 </div>

111

112 <button class="btn btn-primary" type="submit">

113 Sign in

114 </button>

115 </form>

116 {{/if}}

117 </div>

118 </div>

119 </script>

If you reload our application you’ll find out that it is not working. Thats because we are trying toaccess a property from ApplicationControllerwithin SignInController. To make properties fromother controllers available in the current controller we need to provide a need property inside thecontroller.

⁹https://github.com/ryakh/emberoverflow/commit/b142b3292148a9c3695cde53c61ffee3e8a722f9

Page 68: emberjs_applications.pdf

Managing the Authentication 60

Code sample¹⁰:

js/controllers/sign_in_controller

1 App.SignInController = Ember.Controller.extend({

2 needs: ['application'],

3

4 // Additional lines truncated

5 });

When you are telling a controller it needs another controller, you need to pro-vide the other controller’s name downcased with the “Controller” part omitted; soApplicationController will become application, SignInController will becomesignIn.

And finally our template needs to be changed as well.

Code sample¹¹:

index.html

96 <script type="text/x-handlebars" id="sign-in">

97 <div class="row">

98 <div class="col-md-8">

99 {{#if controllers.application.userSignedIn}}

100 <p>You are already signed-in!</p>

101 {{else}}

102 <form {{action signIn on="submit"}}>

103 <div class="form-group">

104 <label for="email">Email address</label>

105

106 {{input id="email"

107 value=email

108 classNames="form-control"

109 placeholder="Enter your email address"}}

110 </div>

111

112 <button class="btn btn-primary" type="submit">

113 Sign in

114 </button>

115 </form>

¹⁰https://github.com/ryakh/emberoverflow/commit/016b4c83894d5d43ada58f4c567c9135e15eb1e4¹¹https://github.com/ryakh/emberoverflow/commit/8f0dde4ca5feca2d1e0b2193de084825a5e6e794

Page 69: emberjs_applications.pdf

Managing the Authentication 61

116 {{/if}}

117 </div>

118 </div>

119 </script>

8.3 Signing the User Out

As you’ve may noticed, we already added a signOut action, but nothing is handling the action. Letsadd the action to our ApplicationController.

Code sample¹²:

js/controllers/application_controllers.js

9 App.ApplicationController = Ember.Controller.extend({

10 signedInUser: function() {

11 return this.store.find('user', localStorage['currentUser']);

12 }.property(),

13

14 userSignedIn: function() {

15 return localStorage['currentUser'] != null;

16 }.property(),

17

18 actions: {

19 signOut: function() {

20 delete localStorage['currentUser'];

21 }

22 }

23 });

You may wonder why we decided to define action on ApplicationController. Thatssimple — our application navigation is rendered inside the application template,which is decorated by ApplicationController. But you can also define the action onSignInController and then call for it in ApplicationController using needs property.

8.4 Binding Properties

So we have a working sign in and sign out functionality. But our user has to reload our app eachtime she signs in or out to see the changes. Lets make this dynamic.

¹²https://github.com/ryakh/emberoverflow/commit/468d251635c75915dbaf4881791a63ad9a57f38c

Page 70: emberjs_applications.pdf

Managing the Authentication 62

Ember allows us to create properties that are bound to other values inside our application. Once thebound value changes, Emberwill automatically update all properties that depend on it. In our caseweneed to update two properties on our ApplicationController — signedInUser and userSignedIn.

Unfortunately Ember can’t track external objects and other JavaScript variables set outside of Ember.So we will use localStorage only to read values from once our application is started and to storevalues in once they are changed —wewill keep the currentUser variable both inside our applicationand localStorage.

So lets change our application initializer to hold the currentUser value (this will read the value fromlocalStorage once the application is created).

Code sample¹³:

js/app.js

1 App = Ember.Application.create({

2 currentUser: localStorage['currentUser']

3 });

Now, we need to alter our signIn and signOut actions not only to write to localStorage, but alsoto change a currentUser property in the scope of our application (so Ember will catch the fact thatwe’ve changed the value of currentUser).

Code sample¹⁴:

js/controllers/sign_in_controller.js

1 App.SignInController = Ember.Controller.extend({

2 needs: ['application'],

3

4 actions: {

5 signIn: function() {

6 var email = this.get('email');

7 userToLogin = App.User.FIXTURES.findBy('email', email);

8 localStorage['currentUser'] = userToLogin.id;

9 App.set('currentUser', userToLogin.id);

10 }

11 }

12 });

¹³https://github.com/ryakh/emberoverflow/commit/95cb124a00c5022c017a5f9a7539582a3317ed49¹⁴https://github.com/ryakh/emberoverflow/commit/72f21a054eb5b1e90ffbff838607689c35b757ac

Page 71: emberjs_applications.pdf

Managing the Authentication 63

Code sample¹⁵:

js/controllers/application_controllers.js

9 App.ApplicationController = Ember.Controller.extend({

10 signedInUser: function() {

11 return this.store.find('user', localStorage['currentUser']);

12 }.property(),

13

14 userSignedIn: function() {

15 return localStorage['currentUser'] != null;

16 }.property(),

17

18 actions: {

19 signOut: function() {

20 delete localStorage['currentUser'];

21 App.set('currentUser', undefined);

22 }

23 }

24 });

We also need to tell Ember to watch for App.currentUser changes and update our signedInUserand userSignedIn properties once the change occurs.

Code sample¹⁶:

js/controllers/application_controllers.js

9 App.ApplicationController = Ember.Controller.extend({

10 signedInUser: function() {

11 return this.store.find('user', localStorage['currentUser']);

12 }.property('App.currentUser'),

13

14 userSignedIn: function() {

15 return localStorage['currentUser'] != null;

16 }.property('App.currentUser'),

17

18 actions: {

19 signOut: function() {

20 delete localStorage['currentUser'];

21 App.set('currentUser', undefined);

¹⁵https://github.com/ryakh/emberoverflow/commit/d3a08f9c6129c7d3c3f51875220a95e764e728c4¹⁶https://github.com/ryakh/emberoverflow/commit/0c9aa2494fb80a20b4cda034944e2c6f4f996bbf

Page 72: emberjs_applications.pdf

Managing the Authentication 64

22 }

23 }

24 });

Now if you reload our application and sign in or sign out, you will see the content of the screenchange dynamically according to the state of our application.

8.5 Testig Signing-in

In our test we will visit the sign-in route, fill in the displayed form with pre-defined data, submitit and will try to find the “already logged in” message.

Code sample¹⁷:

tests/tests.js

58 test("user will be able to log in", function() {

59 delete localStorage['currentUser'];

60 App.set('currentUser', undefined);

61

62 visit("/sign-in");

63

64 fillIn(".form-control", "[email protected]");

65 click("button");

66

67 andThen(function() {

68 equal(

69 find("p").text(),

70 "You are already signed-in!",

71 "Signed-in message rendered"

72 );

73 });

74 });

We’ve added the delete localStorage['currentUser'] and App.set('currentUser', undefined)

lines into our test for a reason — qUnit is not creating a separate environment for our tests and willshare the localStorage with our application. This way we need to nullify the value of currentUserbefore we try to sign the user in inside our test.

¹⁷https://github.com/ryakh/emberoverflow/commit/b28a7b81ad0724179286f8e6e002418a7706a0a7

Page 73: emberjs_applications.pdf

9 Asking Our First QuestionTopics covered in this chapter:

1. creating a new record,2. binding properties to dynamically update template,3. creating actions in controllers,4. waiting for promises to resolve inside your actions,5. ordering records in template.

Snapshot of the application¹.

Lets start by creating a route. Remember what we told in chapter 6 when we were talking aboutdifference between a route and a path? So you’ve probably guessed that we will create a new routeusing route and not a resource.

Code sample²:

js/app.js

9 App.Router.map(function() {

10 this.route('about');

11 this.resource('question', { path: '/:question_id' });

12 this.route('ask-question');

13 this.route('sign-in');

14 });

Remember that Ember is generating all the needed parts for us and will provide them untilwe manually override them.

Now we need to provide a link to the ask-question route. Here we will also hide the link for userswho are not signed in.

¹https://github.com/ryakh/emberoverflow/tree/1480837dd477ae63e9cdb237a17512dea8033d2e²https://github.com/ryakh/emberoverflow/commit/1b4cde1034a69df8c01799741139be35dac7d14c

Page 74: emberjs_applications.pdf

Asking Our First Question 66

Code sample³:

index.html

11 <script type="text/x-handlebars">

12 <nav class="navbar navbar-default" role="navigation">

13 <div class="navbar-header">

14 {{#link-to 'index' classNames='navbar-brand'}}

15 Emberoverflow

16 {{/link-to}}

17 </div>

18

19 <ul class="nav navbar-nav">

20 {{#if userSignedIn}}

21 <li>

22 {{#link-to 'ask-question'}}

23 Ask Question

24 {{/link-to}}

25 </li>

26 {{/if}}

27

28 <li>

29 {{#link-to 'about'}}

30 About site

31 {{/link-to}}

32 </li>

33 </ul>

34

35 <ul class="nav navbar-nav">

36 {{#if userSignedIn}}

37 <li>

38 <a href="#" {{action signOut}}>

39 Welcome, {{signedInUser.fullname}} — Sign out

40 </a>

41 </li>

42 {{else}}

43 <li>

44 {{#link-to 'sign-in'}}Sign in{{/link-to}}

45 </li>

46 {{/if}}

47 </ul>

48 </nav>

³https://github.com/ryakh/emberoverflow/commit/f47341c9ed380a7038d23752446400ffa57c10ba

Page 75: emberjs_applications.pdf

Asking Our First Question 67

49

50 {{outlet}}

51 </script>

Don’t rely only on Ember to manage you application permissions. Ember is client sideframeworkwhichmeans that all of your code will be downloaded to user’s browser makingit all available to her; which means she can make changes into the code. When developingan application that uses a backend be sure to check for user permission on your server aswell.

We need a form so our user can type in the question and send it off to our application making itavailable for other users to answer.

Code sample⁴:

index.html

129 <script type="text/x-handlebars" id="ask-question">

130 <div class="row">

131 <div class="col-md-8">

132 <h2>

133 Ask New Question

134 </h2>

135

136 <form {{action askQuestion on="submit"}}>

137 <div class="form-group">

138 <label for="title">Question title</label>

139

140 {{input id="title"

141 value=title

142 classNames="form-control"

143 placeholder="Give your question an accurate name"}}

144 </div>

145

146 <div class="form-group">

147 <label for="question">Question</label>

148

149 {{textarea id="question"

150 value=question

151 classNames="form-control"

152 placeholder="Describe your problem in as much detail as possible"}}

⁴https://github.com/ryakh/emberoverflow/commit/1e3c9ba29cf320cbdb59506088cf97479951b21d

Page 76: emberjs_applications.pdf

Asking Our First Question 68

153 </div>

154

155 <button class="btn btn-primary" type="submit">

156 Ask Question!

157 </button>

158 </form>

159 </div>

160 </div>

161 </script>

Reload our application and you should see the form we created:

New question form

Lets make things a little more complicated — we want to show the text of the question and update itin real time as our user types it in. It would probably be a pain in the ass to do this using jQuery orvanilla JavaScript. With Ember it all boils down to a single Handlebars expression. Note the valueof property question defined inside {{textarea}} expression. It tells Ember to create a propertynamed question on the controller and to keep it in sync with the value entered in textarea. So allwe need to do is to update our ask-question template with three lines.

Page 77: emberjs_applications.pdf

Asking Our First Question 69

Code sample⁵:

index.html

129 <script type="text/x-handlebars" id="ask-question">

130 <div class="row">

131 <div class="col-md-8">

132 <h2>

133 Ask New Question

134 </h2>

135

136 <form {{action askQuestion on="submit"}}>

137 <div class="form-group">

138 <label for="title">Question title</label>

139

140 {{input id="title"

141 value=title

142 classNames="form-control"

143 placeholder="Give your question an accurate name"}}

144 </div>

145

146 <div class="form-group">

147 <label for="question">Question</label>

148

149 {{textarea id="question"

150 value=question

151 classNames="form-control"

152 placeholder="Describe your problem in as much detail as possible"}}

153 </div>

154

155 {{#if question}}

156 <div class="form-group">

157 <p>Question preview:</p>

158 <p>{{question}}</p>

159 </div>

160 {{/if}}

161

162 <button class="btn btn-primary" type="submit">

163 Ask Question!

164 </button>

165 </form>

166 </div>

⁵https://github.com/ryakh/emberoverflow/commit/ff882a753022417e0ff127a9dc35892e5bf17614

Page 78: emberjs_applications.pdf

Asking Our First Question 70

167 </div>

168 </script>

So we have a form, but it needs a controller so a new question can be asked. Here is what we aregoing to do next:

1. create a new question locally,2. save our question into the storage,3. clean the question form.

So lets create controller; for this purpose I am going to create a new file called question_-

controllers.js that will hold all the question related controllers. Inside the controller we are goingto implement an action to create a new question locally.

Code sample⁶:

js/controllers/question_controllers.js

1 App.AskQuestionController = Ember.ArrayController.extend({

2 needs: ['application'],

3

4 actions: {

5 askQuestion: function() {

6 var question = this.store.createRecord('question', {

7 title: this.get('title'),

8 question: this.get('question'),

9 author: this.get('controllers.application.signedInUser'),

10 date: new Date()

11 });

12 }

13 }

14 });

Note the needs property defined in our controller. We could also use thethis.store.find('user', App.currentUser) syntax, but I’ve chosen to use theneeds property and get the current user from the ApplicationController.

Now if you will try to create a new question, you will get an error: Assertion failed: You can

only add a 'user' record to this relationship. Thats happens because our signedInUsermethod returns a promise and not an actual User object. We need to alter our action to wait for thepromise to resolve and only then to set the author.

⁶https://github.com/ryakh/emberoverflow/commit/d635745c8a5096451fc6603474902d90bfc065db

Page 79: emberjs_applications.pdf

Asking Our First Question 71

Code sample⁷:

js/controllers/question_controllers.js

1 App.AskQuestionController = Ember.ArrayController.extend({

2 needs: ['application'],

3

4 actions: {

5 askQuestion: function() {

6 var question = this.store.createRecord('question', {

7 title: this.get('title'),

8 question: this.get('question'),

9 date: new Date()

10 });

11

12 this.get('controllers.application.signedInUser').then(function(user) {

13 question.set('author', user);

14 });

15 }

16 }

17 });

Saving the record is pretty straight forward — we just need to call question.save() in our action.But there is catch. You probably guessed it so far: we are working with promises here and probablywe don’t want to do anything else until our question is saved. We need to update our action so itwill wait until the record is saved.

Code sample⁸:

js/controllers/question_controllers.js

1 App.AskQuestionController = Ember.ArrayController.extend({

2 needs: ['application'],

3

4 actions: {

5 askQuestion: function() {

6 var question = this.store.createRecord('question', {

7 title: this.get('title'),

8 question: this.get('question'),

9 date: new Date()

10 });

⁷https://github.com/ryakh/emberoverflow/commit/55215b21a197e95d4e83403c45606470bf404a8e⁸https://github.com/ryakh/emberoverflow/commit/f9e5cbda86ef64ffe7c0adfcf42261cd8be42bc6

Page 80: emberjs_applications.pdf

Asking Our First Question 72

11

12 this.get('controllers.application.signedInUser').then(function(user) {

13 question.set('author', user);

14 });

15

16 var controller = this;

17

18 question.save().then(function(question) {

19 controller.setProperties({

20 title: '',

21 question: ''

22 });

23 });

24 }

25 }

26 });

Before we actually save the record we are setting the variable controller which holds the currentcontroller (so we can call it later) thenwe call a save()method andwait until the promise is resolved.Only then we clean the form using the setProperties method. Also here you may do more stufflike disable the form until record is saved or show progress bar or loading spinner.

Now, to make things a little bit more interesting, lets add a sidebar where we will display three latestquestions asked (sorted from newest to oldest). To feed our controller with data we need to create aroute.

Code sample⁹:

js/routes/question_routes.js

1 App.AskQuestionRoute = Ember.Route.extend({

2 model: function() {

3 return this.store.findAll('question');

4 }

5 });

We’ve provided our controller with the data, but how do we sort the data inside our controller?First lets update our AskQuestionController and add a new property called latestQuestions. Thisproperty will be responsible for two things. It will take only first three records from the model givento controller and then it will watch for new records in our store and if we add a new record to thestore it will update the list of latest questions.

⁹https://github.com/ryakh/emberoverflow/commit/b8fa9f04cef0a0522399c5c1aaf878903481b176

Page 81: emberjs_applications.pdf

Asking Our First Question 73

To sort the data we need to add sortProperties property to our controller and provide it with validargument. We are also going to add a sortAscending property which tells our controller how to sortthe model provided (ascending or descending).

Code sample¹⁰:

js/controllers/question_controllers.js

1 App.AskQuestionController = Ember.ArrayController.extend({

2 needs: ['application'],

3 sortProperties: ['date'],

4 sortAscending: true,

5

6 latestQuestions: function() {

7 return this.slice(0,3);

8 }.property('@each'),

9

10 // Additional lines truncated

11 });

property('@each') in this context refers to the list of all questions — Ember will watch them forchange and if one occurs it will update the lastQuestions property right away.

There are two ways to sort your records. Here we sorted our data on the client side (in thebrowser). The second approach is to sort data on the server, telling the server how we wantour data to be sorted when we set a model inside our Route:

1 App.AskQuestionRoute = Ember.Route.extend({

2 model: function() {

3 return this.store.findAll('question', { order: 'date' });

4 }

5 });

This will send a request to the backend to order the record by date property.

And finally we need to update our template to list three latest questions in the sidebar.

¹⁰https://github.com/ryakh/emberoverflow/commit/434c4ffbf6591bb8f60bbe3731a636873f24b445

Page 82: emberjs_applications.pdf

Asking Our First Question 74

Code sample¹¹:

index.html

129 <script type="text/x-handlebars" id="ask-question">

130 <div class="row">

131 <div class="col-md-8">

132 <!-- Additional lines truncated -->

133 </div>

134

135 <div class="col-md-4">

136 <h2>

137 Maybe this will help

138 </h2>

139

140 {{#each latestQuestions}}

141 <div class="panel panel-default">

142 <div class="panel-body">

143 {{#link-to 'question' this}}

144 {{title}}

145 {{/link-to}}

146

147 {{question}}

148 <br>

149 — {{author.fullname}}

150 </div>

151 </div>

152 {{/each}}

153 </div>

154 </div>

155 </script>

Now reload our application and try to add new questions; you will see them appearing in the sidebaras you are adding them (also note how the old questions are getting removed so the total number ofquestions will always be three).

Lets make the final improvement before we move on to the next chapter: after our user asks a newquestion, lets redirect her to the newly created question. To do this we need to add single line toAskQuestionController.

¹¹https://github.com/ryakh/emberoverflow/commit/daaf20a1ec4213ab7e7b21f883d1aba71c45b4a7

Page 83: emberjs_applications.pdf

Asking Our First Question 75

Code sample¹²:

js/controllers/question_controllers.js

1 App.AskQuestionController = Ember.ArrayController.extend({

2 // Additional lines truncated

3

4 actions: {

5 askQuestion: function() {

6 // Additional lines truncated

7

8 question.save().then(function(question) {

9 controller.setProperties({

10 title: '',

11 question: ''

12 });

13

14 controller.transitionToRoute('question', question);

15 });

16 }

17 }

18 });

9.1 Testing What We Did

Here I am going to cheat a little bit. Since I know the ID of user I am going to set it in our testdirectly.

Code sample¹³:

tests/tests.js

76 test("signed-in user can ask new question", function() {

77 localStorage['currentUser'] = 201;

78 App.set('currentUser', 201);

79

80 visit("/ask-question");

81 fillIn("#title", "Question title");

82 fillIn("#question", "Question");

83 click("button");

84

¹²https://github.com/ryakh/emberoverflow/commit/d291b92e91017ac942715908729d8ddb34d474d3¹³https://github.com/ryakh/emberoverflow/commit/24039d0029cb32554d75377c8fef4b219581223c

Page 84: emberjs_applications.pdf

Asking Our First Question 76

85 andThen(function(){

86 equal(

87 find("h2").text(),

88 "Question title",

89 "Question title is rendered"

90 );

91

92 equal(

93 find("p:first").text().replace(/\s+/g, ''),

94 "Question",

95 "Question is rendered"

96 );

97 });

98 });

The only part here that is tricky the line where we are asserting the content of our paragraph. Weneed to delete all spaces in it since the text() method called directly on paragraph will return textsurrounded by a bunch of empty spaces.

Now if you run our tests you should see that the very first test we wrote fails (it won’t necessarilyfail on the first run since qUnit is running tests in random order and it takes to create a new questionbefore the test to make it fail). So we need to update our test. In fact we don’t care about number ofasked questions — since it can vary. We are going to test the list of questions not to be empty.

Code sample¹⁴:

tests/tests.js

21 test("index page has a title and a list of questions", function() {

22 visit("/");

23

24 andThen(function() {

25 equal(

26 find("h2").text(),

27 "Welcome to Emberoverflow",

28 "Application header is rendered"

29 );

30

31 notEqual(

32 find("ul:not(.nav) > li").length,

33 0,

34 "There are questions in the list"

¹⁴https://github.com/ryakh/emberoverflow/commit/1480837dd477ae63e9cdb237a17512dea8033d2e

Page 85: emberjs_applications.pdf

Asking Our First Question 77

35 );

36 });

37 });

Re-run our tests and every single one should pass.

Page 86: emberjs_applications.pdf

10 Answering QuestionsTopics covered in this chapter:

1. creating related records.2. managing blank states,3. creating mixins.

Snapshot of the application¹.

So our users can ask questions, but there is still no way to answer them. Lets give our users abilityto answer questions. As you’ve probably guessed we will need few things to make that possible: atemplate, a controller and a model. I will also go ahead and create a set of fixtures which will addan answer and a new user.

10.1 Modelling Our Data

Lets start with adding a new fixtures and altering existing ones.

Code sample²:

js/fixtures/user_fixtures.js

1 App.User.FIXTURES = [

2 {

3 id: 201,

4 fullname: 'Tom Dale',

5 email: '[email protected]',

6 questions: [101]

7 },

8 {

9 id: 202,

10 fullname: 'Tomster the Hamster',

11 email: '[email protected]',

12 questions: [102]

13 },

14 {

¹https://github.com/ryakh/emberoverflow/tree/04837b38dc5a503caee74ca4f55960e9e0348cb2²https://github.com/ryakh/emberoverflow/commit/e7caca4a8cc2653f45306f8a82c0e09b8e38007c

Page 87: emberjs_applications.pdf

Answering Questions 79

15 id: 203,

16 fullname: 'Random Guy',

17 email: '[email protected]'

18 }

19 ];

A new set of fixtures containing pre-defined answer.

Code sample³:

js/fixtures/answer_fixtures.js

1 App.Answer.FIXTURES = [

2 {

3 id: 301,

4 answer: 'You can feed them from a bowl!',

5 date: '2013-01-01T12:00:00',

6 author: 203

7 }

8 ];

And finally lets update our question fixtures with the relation to the answer we created in theprevious step.

Code sample⁴:

js/fixtures/question_fixtures.js

1 App.Question.FIXTURES = [

2 {

3 id: 101,

4 title: 'How do I feed hamsters?',

5 author: 201,

6 date: '2013-01-01T12:00:00',

7 question: 'Tomster cant eat using knife and a fork because his hands are \

8 too small. We are looking for a way to feed him. Any ideas?',

9 answers: [301]

10 },

11 {

12 id: 102,

13 title: 'Are humans insane?',

³https://github.com/ryakh/emberoverflow/commit/4cb36ae18d5085e7f8b152888cc6e0c11504407b⁴https://github.com/ryakh/emberoverflow/commit/8dbbbc6cf091b7e9b2cb7e58a3137dfb3cbf494e

Page 88: emberjs_applications.pdf

Answering Questions 80

14 author: 202,

15 date: '2013-02-02T12:00:00',

16 question: 'I mean are totaly nuts? Is there any hope left for them? Should \

17 we hamsters try to save them?'

18 }

19 ];

Nowwe need to create an answermodel and update existing questionmodel according to the fixtureswe’ve created.

Code sample⁵:

js/models/answer_model.js

1 App.Answer = DS.Model.extend({

2 answer: DS.attr('string'),

3 date: DS.attr('date'),

4 author: DS.belongsTo('user')

5 });

Code sample⁶:

js/models/question_model.js

1 App.Question = DS.Model.extend({

2 title: DS.attr('string'),

3 question: DS.attr('string'),

4 date: DS.attr('date'),

5 author: DS.belongsTo('user'),

6 answers: DS.hasMany('answer', { async: true })

7 });

Youmay have noticed that we didn’t update some of the fixtures andmodels. Thats becausewe are going to access only one side of relation (we are going to access user from answers).If we were accessing answers from user then Ember would expect us to define relations onthe user side as well.

Don’t forget to add newly created files to our index.html. Now lets go ahead and create a templateto hold our answer form. We will create our answer form inside existing question template.

⁵https://github.com/ryakh/emberoverflow/commit/8747073fd135f87840f9374509197aff693484da⁶https://github.com/ryakh/emberoverflow/commit/2b1af79b26b2743796587957b676c4e22ec7c5e0

Page 89: emberjs_applications.pdf

Answering Questions 81

Code sample⁷:

index.html

74 <script type="text/x-handlebars" id="question">

75 <div class="row">

76 <div class="col-md-8">

77 <h2>{{title}}</h2>

78

79 <p>

80 {{question}}

81 </p>

82

83 <p>

84 Asked by {{author.fullname}}

85 </p>

86 </div>

87 </div>

88

89 <hr>

90

91 <div class="row">

92 <div class="col-md-8">

93 {{#each answers}}

94 <div class="panel panel-default">

95 <div class="panel-heading">

96 {{author.fullname}} on {{date}}

97 </div>

98

99 <div class="panel-body">

100 {{answer}}

101 </div>

102 </div>

103 {{/each}}

104 </div>

105 </div>

106

107 <hr>

108

109 <div class="row">

110 <div class="col-md-8">

111 <h4>Submit your answer:</h4>

⁷https://github.com/ryakh/emberoverflow/commit/ef1826ac5f42a627b7c34e72a6e9d29df2c31bef

Page 90: emberjs_applications.pdf

Answering Questions 82

112

113 <form {{action answerQuestion on="submit"}}>

114 <div class="form-group">

115 {{textarea id="answer" value=answer classNames="form-control"}}

116 </div>

117

118 <button class="btn btn-primary" type="submit">

119 Submit

120 </button>

121 </form>

122 </div>

123 </div>

124 </script>

Nowwe need an action to create a new answer. But which controller shouldwe use? Ember Inspectorto the rescue. Open it up and lets take a look at the View Tree — we are inside QuestionController,which is generated by Ember for us since we didn’t provide any.

View Tree of Question Route

So lets go ahead and create QuestionControllerwhich will contain answerQuestion action. You’ve

Page 91: emberjs_applications.pdf

Answering Questions 83

probably guessed what we will do in the action:

1. create a new answer for the question locally,2. store the answer in the store (which will force the synchronisation of the store with the server),3. clean the answer form.

Creating a controller is pretty straightforward as it is similar to the AskQuestionController.

Code sample⁸:

js/controllers/question_controllers.js

35 App.QuestionController = Ember.ObjectController.extend({

36 needs: ['application'],

37

38 actions: {

39 answerQuestion: function() {

40 var answer = this.store.createRecord('answer', {

41 answer: this.get('answer'),

42 question: this.get('model'),

43 date: new Date()

44 });

45

46 this.get('controllers.application.signedInUser').then(function(user) {

47 answer.set('user', user);

48 });

49

50 var controller = this;

51

52 answer.save().then(function(answer) {

53 controller.get('model.answers').addObject(answer);

54

55 controller.setProperties({

56 answer: ''

57 });

58 });

59 }

60 }

61 });

You are familiar with some lines of code since we already used them in AskQuestionController.But there are some differences in here. So lets talk about them. First, when we are setting a question

⁸https://github.com/ryakh/emberoverflow/commit/5ff499e5974ad2ae8914cb7cfc01f312945158a4

Page 92: emberjs_applications.pdf

Answering Questions 84

we are calling for this.get('model') instead of looking up the store for the model, as we did withthe user relation. Thats because the model is already loaded in our controller and all we have to dois ask for it.

Second we are calling for controller.get('model.answers').addObject(answer). Thats becauseEmber is not watching the related models in the context of the current model and because of thatwe need to add a new answer object manually. You can try and delete this line and you will see thata new answer will not be added into the template of QuestionView controller (but it will be addedto our store — you can check that from Ember Inspector).

10.2 Introducing Mixins

You’ve probably noticed that things are getting a bit repetitive in our code. Namely our question_-controller.js file has few identical lines of code that are responsible for setting an author ofquestion or answer. We can extract that functionality into the Mixin. Mixin is an Ember Class whichallows us to add it’s properties into other Classes.

Note that Mixins are created with Ember.Mixin.create, not Ember.Mixin.extend.

So lets go ahead and create a Mixin (append it to the beginning of the question_controllers.jsfile, otherwise you will get an error).

Code sample⁹:

js/controllers/question_controllers.js

1 App.SetAuthorMixin = Ember.Mixin.create({

2 needs: ['application'],

3

4 setAuthorFor: function(object) {

5 this.get('controllers.application.signedInUser').then(function(user) {

6 object.set('author', user);

7 });

8 }

9 });

Now we can extend our controllers using the newly created Mixin.

⁹https://github.com/ryakh/emberoverflow/commit/eb83adfbb67a7b6011c433b7b2b6d95c888855b6

Page 93: emberjs_applications.pdf

Answering Questions 85

Code sample¹⁰:

js/controllers/question_controllers.js

11 App.AskQuestionController = Ember.ArrayController.extend(

12 App.SetAuthorMixin, {

13

14 sortProperties: ['date'],

15 sortAscending: false,

16

17 latestQuestions: function() {

18 return this.slice(0,3);

19 }.property('@each'),

20

21 actions: {

22 askQuestion: function() {

23 var question = this.store.createRecord('question', {

24 title: this.get('title'),

25 question: this.get('question'),

26 date: new Date()

27 });

28

29 this.setAuthorFor(question);

30

31 var controller = this;

32

33 question.save().then(function(question) {

34 controller.setProperties({

35 title: '',

36 question: ''

37 });

38

39 controller.transitionToRoute('question', question);

40 });

41 }

42 }

43 });

44

45 App.QuestionController = Ember.ObjectController.extend(

46 App.SetAuthorMixin, {

47

48 actions: {

¹⁰https://github.com/ryakh/emberoverflow/commit/855726748e0032a5b315585b5f76b8b4041ed7bd

Page 94: emberjs_applications.pdf

Answering Questions 86

49 answerQuestion: function() {

50 var answer = this.store.createRecord('answer', {

51 answer: this.get('answer'),

52 question: this.get('model'),

53 date: new Date()

54 });

55

56 this.setAuthorFor(answer);

57

58 var controller = this;

59

60 answer.save().then(function(answer) {

61 controller.get('model.answers').addObject(answer);

62

63 controller.setProperties({

64 answer: ''

65 });

66 });

67 }

68 }

69 });

10.3 Empty states

If we navigate to the question that has no answers youwill see that the space reserved for the answersis empty and it probably would be better if we let user know that the question has no answers:

Page 95: emberjs_applications.pdf

Answering Questions 87

Question without answers

Ember has easy solution for this case as well. All we need to do is to update the question templateand extend the {{each}} block expression the following way.

Code sample¹¹:

index.html

74 <script type="text/x-handlebars" id="question">

75 <div class="row">

76 <div class="col-md-8">

77 <h2>{{title}}</h2>

78

79 <p>

80 {{question}}

81 </p>

82

83 <p>

84 Asked by {{author.fullname}}

85 </p>

¹¹https://github.com/ryakh/emberoverflow/commit/ad75f193e8261a0cd903f729a9cd45fd60e1c315

Page 96: emberjs_applications.pdf

Answering Questions 88

86 </div>

87 </div>

88

89 <hr>

90

91 <div class="row">

92 <div class="col-md-8">

93 {{#each answers}}

94 <div class="panel panel-default">

95 <div class="panel-heading">

96 {{author.fullname}} on {{date}}

97 </div>

98

99 <div class="panel-body">

100 {{answer}}

101 </div>

102 </div>

103 {{else}}

104 <h4>Sorry, there are no answer to this question</h4>

105 {{/each}}

106 </div>

107 </div>

108

109 <hr>

110

111 <div class="row">

112 <div class="col-md-8">

113 <h4>Submit your answer:</h4>

114

115 <form {{action answerQuestion on="submit"}}>

116 <div class="form-group">

117 {{textarea id="answer" value=answer classNames="form-control"}}

118 </div>

119

120 <button class="btn btn-primary" type="submit">

121 Submit

122 </button>

123 </form>

124 </div>

125 </div>

126 </script>

Page 97: emberjs_applications.pdf

Answering Questions 89

The {{each}} block expression will try to cycle through the provided array. If array has no items tocycle through, the {{else}} part of the block expression will be rendered. And if you will submit anew answer, Ember will hide the empty state and show the answers for the question.

10.4 Testing our Changes

This time we will not create a new test but extend an existing one. The reason is simple — to write atest that tests user ability to answers questions we will have to copy the existing test (since we needto create question we can answer first) and this will lead to unnecessary duplicity.

Code sample¹²:

tests/tests.js

76 test("signed-in user can ask new question", function() {

77 localStorage['currentUser'] = 201;

78 App.set('currentUser', 201);

79

80 visit("/ask-question");

81 fillIn("#title", "Question title");

82 fillIn("#question", "Question");

83 click("button");

84

85 fillIn("#answer", "Answer");

86 click("button");

87

88 andThen(function(){

89 equal(

90 find("h2").text(),

91 "Question title",

92 "Question title is rendered"

93 );

94

95 equal(

96 find("p:first").text().replace(/\s+/g, ''),

97 "Question",

98 "Question is rendered"

99 );

100

101 notEqual(

102 find(".panel").length,

¹²https://github.com/ryakh/emberoverflow/commit/04837b38dc5a503caee74ca4f55960e9e0348cb2

Page 98: emberjs_applications.pdf

Answering Questions 90

103 0,

104 "New answer was added"

105 );

106

107 equal(

108 find(".panel-body").text().replace(/\s+/g, ''),

109 "Answer",

110 "Question was answered"

111 );

112 });

113 });

Re-run the tests to see them all pass.

Page 99: emberjs_applications.pdf

11 Cleaning Templates UpTopics covered in this chapter:

1. components,2. views,3. partials,4. render.

Snapshot of the application¹.

Our application is growing rapidly. Templates are getting bigger and soon it will be hard to maintainthem. In this chapter we will go though different methods Ember gives us to organise our templates.

11.1 Components

On our index route we have a list of questions rendered. Lets clean the template by creating acomponent which will hold a question. We will also include a number of answers for each questionasked.

To define a component in Ember we need to do two things:

1. define the template of the component,2. define the code for the component (Component Object).

Lets begin with creating a template. Here is simple rule to remember while dealing with components:component template should include a hyphen in its name; i.e. question-preview is a validcomponent template namewether question or question_preview is not. So let’s create a componenttemplate by copying the part from index template.

¹https://github.com/ryakh/emberoverflow/tree/2a21fcb770380c9152719fb0012ff232e8248bcf

Page 100: emberjs_applications.pdf

Cleaning Templates Up 92

Code sample²:

index.html

228 <script type="text/x-handlebars" id="components/question-preview">

229 {{#link-to 'question' this}}

230 {{title}}

231 {{/link-to}}

232

233 (asked by {{author.fullname}})

234 </script>

Also note the components/ prefix in the template name. Without it we will create a regular template.Now lets also update our index template to use the component.

Code sample³:

index.html

53 <script type="text/x-handlebars" id="index">

54 <div class="row">

55 <div class="col-md-8">

56 <h2>{{siteTitle}}</h2>

57

58 <p>It is {{currentTime}}</p>

59

60 <h3> Latest questions</h3>

61

62 <ul>

63 {{#each}}

64 {{question-preview}}

65 {{/each}}

66 </ul>

67 </div>

68 </div>

69 </script>

If you reload our application, you will see that component was rendered two times, although it hasno content. Thats because component is unaware of it’s surroundings and if you want to have anyof the properties available in your template to be available in your component you have to explicitlypass them in (plus we will need to update our component template).

²https://github.com/ryakh/emberoverflow/commit/357c94069c711c4debb68bd77d6787c4dc3538f7³https://github.com/ryakh/emberoverflow/commit/b9f2706b42853798de39e7e4d2b4006518f1796c

Page 101: emberjs_applications.pdf

Cleaning Templates Up 93

Code sample⁴:

index.html

53 <script type="text/x-handlebars" id="index">

54 <div class="row">

55 <div class="col-md-8">

56 <h2>{{siteTitle}}</h2>

57

58 <p>It is {{currentTime}}</p>

59

60 <h3> Latest questions</h3>

61

62 <ul>

63 {{#each}}

64 {{question-preview question=this tagName="li"}}

65 {{/each}}

66 </ul>

67 </div>

68 </div>

69 </script>

Code sample⁵:

index.html

225 <script type="text/x-handlebars" id="components/question-preview">

226 {{#link-to 'question' question}}

227 {{question.title}}

228 {{/link-to}}

229

230 (asked by {{question.author.fullname}})

231 </script>

Remember the tagName property we mentioned before? Here we used it to define HTML elementinside which the component will be rendered.

⁴https://github.com/ryakh/emberoverflow/commit/ff63f8dbf7a37e5e5c2aab6a87e02389b831516c⁵https://github.com/ryakh/emberoverflow/commit/6b575d9aaa8ce1232b1e14c9d0b4e3acfefe42bf

Page 102: emberjs_applications.pdf

Cleaning Templates Up 94

If you still don’t understand how tagName works, open up code inspector and findany of the rendered components. You will see something like this: <li id="ember394"

class="ember-view">. Now try to remove the tagName property from index template,reload the page and look for the component again. This time you will see a div insteadof li: <div id="ember394" class="ember-view"> with same attributes as our li elementhad. Whenever we call for Ember to render any element it will wrap it into div and setit’s attributes so it can be identified once needed. We can take that to our advantage andoverride the wrapping tag from div to anything we like.

Ok but what about number of answers on each question? First we need to create a question_-

components.js file which will hold our Component Object on which we are going to create aproperty that will hold the number of answers.

Code sample⁶:

js/components/question_components.js

1 App.QuestionPreviewComponent = Ember.Component.extend({

2 answersCount: function() {

3 return this.get('question.answers.length');

4 }.property('question.answers.length')

5 });

Now lets update our component template to display number of answers.

Code sample⁷:

index.html

225 <script type="text/x-handlebars" id="components/question-preview">

226 {{#link-to 'question' question}}

227 {{question.title}}

228 {{/link-to}}

229

230 <span class="label label-default">{{answersCount}} answers</span>

231

232 (asked by {{question.author.fullname}})

233 </script>

Ok what we created stinks. First, code in question_components.js is ugly. Second, “1 answers”,really? And finally — lets hide the label if question has no answer.

Luckily for us Ember has solution for all of our problems out of the box. First lets change the codein our question_component.js.

⁶https://github.com/ryakh/emberoverflow/commit/9e74d4940f4bf801241b181dc15ebeb8b874c67f⁷https://github.com/ryakh/emberoverflow/commit/8bd4452c2c5cfaccb9403989ce844a6a3c117efc

Page 103: emberjs_applications.pdf

Cleaning Templates Up 95

Code sample⁸:

js/components/question_components.js

1 App.QuestionPreviewComponent = Ember.Component.extend({

2 answersCount: Ember.computed.alias('question.answers.length')

3 });

What you see above is a shorthand syntax to define a bound property. It equals to the code we’vewritten before. Now lets take care of the correct inflection.

Code sample⁹:

js/components/question_components.js

1 App.QuestionPreviewComponent = Ember.Component.extend({

2 answersCount: Ember.computed.alias('question.answers.length'),

3

4 pluralForm: function() {

5 var answers = this.get('answersCount');

6 return answers === 1 ? 'answer' : 'answers'

7 }.property('answersCount')

8 });

We just query our question for number of answers and if the number of answers equals one wereturn ‘answer’, otherwise we return ‘answers’. Note that we are binding our property to the onewe’ve created manually before. Don’t forget to update the template.

Code sample¹⁰:

index.html

225 <script type="text/x-handlebars" id="components/question-preview">

226 {{#link-to 'question' question}}

227 {{question.title}}

228 {{/link-to}}

229

230 <span class="label label-default">{{answersCount}} {{pluralForm}}</span>

231

232 (asked by {{question.author.fullname}})

233 </script>

And finally lets hide the label if we have no answers. We are going to define a property on ourcomponent which will return true if question has answers otherwise it will return false.

⁸https://github.com/ryakh/emberoverflow/commit/3dfdf13ba02e1a7852ca0dba5efe41dfb36c9e49⁹https://github.com/ryakh/emberoverflow/commit/18813569a010469a57514601a92e6c75cf6e6a66¹⁰https://github.com/ryakh/emberoverflow/commit/8af3f00b48aefde1ae82d3d125f90e5e32d0510e

Page 104: emberjs_applications.pdf

Cleaning Templates Up 96

Code sample¹¹:

js/components/question_components.js

1 App.QuestionPreviewComponent = Ember.Component.extend({

2 answersCount: Ember.computed.alias('question.answers.length'),

3

4 pluralForm: function() {

5 var answers = this.get('answersCount');

6 return answers === 1 ? 'answer' : 'answers'

7 }.property('answersCount'),

8

9 hasAnswers: function() {

10 return this.get('answersCount') > 0;

11 }.property('answersCount')

12 });

Now we need to update our template with {{if}} block expression.

Code sample¹²:

index.html

225 <script type="text/x-handlebars" id="components/question-preview">

226 {{#link-to 'question' question}}

227 {{question.title}}

228 {{/link-to}}

229

230 {{#if hasAnswers}}

231 <span class="label label-default">{{answersCount}} {{pluralForm}}</span>

232 {{/if}}

233

234 (asked by {{question.author.fullname}})

235 </script>

Finally we can move properties such as tagName and classNames directly into our component object(this will clean our template even more).

¹¹https://github.com/ryakh/emberoverflow/commit/a67b54f27aeb8da114df460962badf1d5e79d369¹²https://github.com/ryakh/emberoverflow/commit/33ffb0c0a937cfffebb84a552052c8a8ff953fec

Page 105: emberjs_applications.pdf

Cleaning Templates Up 97

Code sample¹³:

js/components/question_components.js

1 App.QuestionPreviewComponent = Ember.Component.extend({

2 tagName: 'li',

3

4 answersCount: Ember.computed.alias('question.answers.length'),

5

6 pluralForm: function() {

7 var answers = this.get('answersCount');

8 return answers === 1 ? 'answer' : 'answers'

9 }.property('answersCount'),

10

11 hasAnswers: function() {

12 return this.get('answersCount') > 0;

13 }.property('answersCount')

14 });

Code sample¹⁴:

index.html

1 <script type="text/x-handlebars" id="index">

2 <div class="row">

3 <div class="col-md-8">

4 <h2>{{siteTitle}}</h2>

5

6 <p>It is {{currentTime}}</p>

7

8 <h3> Latest questions</h3>

9

10 <ul>

11 {{#each}}

12 {{question-preview question=this}}

13 {{/each}}

14 </ul>

15 </div>

16 </div>

17 </script>

¹³https://github.com/ryakh/emberoverflow/commit/59816410a2a8556aff3e650faab79a0bd56519cf¹⁴https://github.com/ryakh/emberoverflow/commit/7f207c17367d94554bde959e8e739169716ebd20

Page 106: emberjs_applications.pdf

Cleaning Templates Up 98

11.2 Partials

Our question has only list of answers (it can also have a list of related questions, related answersetc.). But there are already more than 50 lines of code in the question template. Here is another,rather simple technique that will help us clean our templates up. It is called partials. If you haveRails background, you are familiar with it. To render a partial you only need a template. Partialtemplate names in Ember should start with underscore.

Code sample¹⁵:

index.html

237 <script type="text/x-handlebars" id="_answers">

238 <div class="row">

239 <div class="col-md-8">

240 {{#each answers}}

241 <div class="panel panel-default">

242 <div class="panel-heading">

243 {{author.fullname}} on {{date}}

244 </div>

245

246 <div class="panel-body">

247 {{answer}}

248 </div>

249 </div>

250 {{else}}

251 <h4>Sorry, there are no answer to this question</h4>

252 {{/each}}

253 </div>

254 </div>

255 </script>

We have to update our question template to render the partial.

¹⁵https://github.com/ryakh/emberoverflow/commit/be6226c8870ee4604060a10c27f6774136c1b6ca

Page 107: emberjs_applications.pdf

Cleaning Templates Up 99

Code sample¹⁶:

index.html

71 <script type="text/x-handlebars" id="question">

72 <div class="row">

73 <div class="col-md-8">

74 <h2>{{title}}</h2>

75

76 <p>

77 {{question}}

78 </p>

79

80 <p>

81 Asked by {{author.fullname}}

82 </p>

83 </div>

84 </div>

85

86 <hr>

87

88 {{partial 'answers'}}

89

90 <hr>

91

92 <div class="row">

93 <div class="col-md-8">

94 <h4>Submit your answer:</h4>

95

96 <form {{action answerQuestion on="submit"}}>

97 <div class="form-group">

98 {{textarea id="answer" value=answer classNames="form-control"}}

99 </div>

100

101 <button class="btn btn-primary" type="submit">

102 Submit

103 </button>

104 </form>

105 </div>

106 </div>

107 </script>

¹⁶https://github.com/ryakh/emberoverflow/commit/eb29063eb586e23834853c39672ae154b0927829

Page 108: emberjs_applications.pdf

Cleaning Templates Up 100

Partial will just insert one template into another. Inside partial you have full access to everythingyou have access to in the parent template. But partials are pretty dumb — they will only mimic thefunctionality of the controller of template they are inserted to.

11.3 Views to the Rescue

Basically views share very similar philosophy as components do. Also remember that Ember.Componentis actually a subclass of Ember.View. Unlike view, component is completely isolated. Also, unlikecomponent, actions triggered in view are handled by the controller of the template the view wasinserted to (components handle actions triggered inside them in component object).

So lets move our answer form into the view. We need a {{view}} block expression to define a regionfor our view.

Code sample¹⁷:

index.html

71 <script type="text/x-handlebars" id="question">

72 <div class="row">

73 <div class="col-md-8">

74 <h2>{{title}}</h2>

75

76 <p>

77 {{question}}

78 </p>

79

80 <p>

81 Asked by {{author.fullname}}

82 </p>

83 </div>

84 </div>

85

86 <hr>

87

88 {{partial 'answers'}}

89

90 <hr>

91

92 {{#view App.AnswerFormView}}

93 <div class="row">

94 <div class="col-md-8">

¹⁷https://github.com/ryakh/emberoverflow/commit/4867fe55f5f69e1fe90feead5d61b848b5738c6a

Page 109: emberjs_applications.pdf

Cleaning Templates Up 101

95 <h4>Submit your answer:</h4>

96

97 <form {{action answerQuestion on="submit"}}>

98 <div class="form-group">

99 {{textarea id="answer" value=answer classNames="form-control"}}

100 </div>

101

102 <button class="btn btn-primary" type="submit">

103 Submit

104 </button>

105 </form>

106 </div>

107 </div>

108 {{/view}}

109 </script>

Now thats gross and makes our template even messier. Lets pull out our view into a separatetemplate, shall we?

Code sample¹⁸:

index.html

243 <script type="text/x-handlebars" id="answer_form">

244 <div class="row">

245 <div class="col-md-8">

246 <h4>Submit your answer:</h4>

247

248 <form {{action answerQuestion on="submit"}}>

249 <div class="form-group">

250 {{textarea id="answer" value=answer classNames="form-control"}}

251 </div>

252

253 <button class="btn btn-primary" type="submit">

254 Submit

255 </button>

256 </form>

257 </div>

258 </div>

259 </script>

Don’t forget to change question template to call for View object.

¹⁸https://github.com/ryakh/emberoverflow/commit/81390ee214705ad0f3e7d8be280e40da1e5b898d

Page 110: emberjs_applications.pdf

Cleaning Templates Up 102

Code sample¹⁹:

index.html

71 <script type="text/x-handlebars" id="question">

72 <div class="row">

73 <div class="col-md-8">

74 <h2>{{title}}</h2>

75

76 <p>

77 {{question}}

78 </p>

79

80 <p>

81 Asked by {{author.fullname}}

82 </p>

83 </div>

84 </div>

85

86 <hr>

87

88 {{partial 'answers'}}

89

90 <hr>

91

92 {{view App.AnswerFormView}}

93 </script>

I broke the previous code into multiple steps on purpose. Here I wanted to demonstrate thatyour view doesn’t have to have a template defined — you can define your template inlineusing the {{view}} block expression. If you define your view with a block expression thenyou can define reusable behaviour (such as hiding or showing parts of your application)for various HTML markups.

Now all we need to do is to bind the view template to our view. We can do that in the View object.

¹⁹https://github.com/ryakh/emberoverflow/commit/67dd0e15e6dc8ac7a6ae6dddd9c92ce6dda10f51

Page 111: emberjs_applications.pdf

Cleaning Templates Up 103

Code sample²⁰:

js/views/answer_views.js

1 App.AnswerFormView = Ember.View.extend({

2 templateName: 'answer_form'

3 });

Our view object can handle different type of events such as click, doubleClick, submit, etc.(list of all events²¹). If you call an action inside your view template it will be propagatedto the controller inside which the view is nested. To direct action into the view you canuse {{action target="view”}}. If you do that don’t forget to create an action inside yourview.

11.4 Mighty Render

Lets complicate things a little bit. Start by updating our fixtures (we will add one more answer).

Code sample²²:

js/fixtures/answer_fixtures.js

1 App.Answer.FIXTURES = [

2 {

3 id: 301,

4 answer: 'You can feed them from a bowl!',

5 date: '2013-01-01T12:00:00',

6 author: 203

7 },

8 {

9 id: 302,

10 answer: 'What about a steak in a bowl?',

11 date: '2013-02-02T12:00:00',

12 author: 202

13 }

14 ];

²⁰https://github.com/ryakh/emberoverflow/commit/c21853457549413fa4e987fd20d55c32bbfd80dc²¹http://emberjs.com/api/classes/Ember.View.html#toc_event-names²²https://github.com/ryakh/emberoverflow/commit/005b11f9591d48f1dca87577dbf8366f15303e4b

Page 112: emberjs_applications.pdf

Cleaning Templates Up 104

Code sample²³:

js/fixtures/question_fixtures.js

1 App.Question.FIXTURES = [

2 {

3 id: 101,

4 title: 'How do I feed hamsters?',

5 author: 201,

6 date: '2013-01-01T12:00:00',

7 question: 'Tomster cant eat using knife and a fork because his hands are \

8 too small. We are looking for a way to feed him. Any ideas?',

9 answers: [301,302]

10 },

11 {

12 id: 102,

13 title: 'Are humans insane?',

14 author: 202,

15 date: '2013-02-02T12:00:00',

16 question: 'I mean are totaly nuts? Is there any hope left for them? Should \

17 we hamsters try to save them?'

18 }

19 ];

Again, we are not defining the user-answer relation in our fixtures because we are notaccessing it.

Now if you reload our application and open the question with answers you will notice that theanswers are ordered in a way they are defined inside fixtures. But how can we change that? Herewhere {{render}} expression comes in. Here is a brief introduction to render: it will call for anycontroller and actually render the controller right inside the controller it was called in.

First thing we need is a template to render. Luckily for us we already have _answers partial whichwe can rename into answers. Also remember that we will have a separate controller. Lets begin withaltering the template.

²³https://github.com/ryakh/emberoverflow/commit/ccf9cd0823ed6c56646b257888caf9bb0f0466db

Page 113: emberjs_applications.pdf

Cleaning Templates Up 105

Code sample²⁴:

index.html

207 <script type="text/x-handlebars" id="answers">

208 <div class="row">

209 <div class="col-md-8">

210 {{#each}}

211 <div class="panel panel-default">

212 <div class="panel-heading">

213 {{author.fullname}} on {{date}}

214 </div>

215

216 <div class="panel-body">

217 {{answer}}

218 </div>

219 </div>

220 {{else}}

221 <h4>Sorry, there are no answer to this question</h4>

222 {{/each}}

223 </div>

224 </div>

225 </script>

Now we also need to alter our question template. Note that {{render}} expression takes infollowing arguments: the name of the template/controller and the model.

Code sample²⁵:

index.html

71 <script type="text/x-handlebars" id="question">

72 <div class="row">

73 <div class="col-md-8">

74 <h2>{{title}}</h2>

75

76 <p>

77 {{question}}

78 </p>

79

80 <p>

²⁴https://github.com/ryakh/emberoverflow/commit/a88e8bdc406d60fa2a341d889bcef0d629328b98²⁵https://github.com/ryakh/emberoverflow/commit/409caf97290e397a4121452c58fa58115a7b8bc8

Page 114: emberjs_applications.pdf

Cleaning Templates Up 106

81 Asked by {{author.fullname}}

82 </p>

83 </div>

84 </div>

85

86 <hr>

87

88 {{render 'answers' answers}}

89

90 <hr>

91

92 {{view App.AnswerFormView}}

93 </script>

Here we told Ember to render answers template and stitch it together with answers controller. Themodel we passed consists of answers which are part of question-answers relation.

Last thing left for us to do is to create a controller. So if we wanted to sort our answers from newestto oldest, our controller will look similar to the next code sample.

Code sample²⁶:

js/controllers/answer_controllers.js

1 App.AnswersController = Ember.ArrayController.extend({

2 sortProperties: ['date'],

3 sortAscending: false

4 });

Now if you reload our application, open Ember Inspector and examine the View Tree, you will seethat AnswersController is rendered nested under the QuestionController:

²⁶https://github.com/ryakh/emberoverflow/commit/2a21fcb770380c9152719fb0012ff232e8248bcf

Page 115: emberjs_applications.pdf

Cleaning Templates Up 107

Controllers nested thanks to Render

In this chapter we won’t be testing anything since we didn’t introduce anything worth testing wedidn’t test so far. Instead re-run our test suite just to make sure we didn’t brake anything.

Page 116: emberjs_applications.pdf

12 Nesting Views, Editing Records,Helpers

Topics covered in this chapter:

1. nesting views and routes,2. redirecting user,3. defining your own helpers,4. editing existing records.

Snapshot of the application¹.

There is one last major concept left for us to cover in this book — nesting our views. Before we diveinto code let me explain what we will do. On our index route we have a list of all questions. Whenuser clicks on the question, our application renders a new template containing only the selectedquestion. What we want to do instead is to split our page in half and display the list of questionson the left and once the question is selected, we will display it on the right keeping the list of allquestions still visible.

“If your user interface is nested, your routes should be nested” — Yehuda Katz², memberof the Ember core team

This is a great hint to begin with. So lets go ahead and try to nest our question route under theindex route. But wait, how do we nest a route under the index route? Unfortunately there is no wayto nest routes under the index route in Ember. We will do the following: right after our applicationis loaded, we will redirect user to the questions route and under the route we will nest other routes.

So lets open our app.js file and change the code of the router.

¹https://github.com/ryakh/emberoverflow/tree/38a3b083c18fddfc38ff916768ad4b659dffde58²http://yehudakatz.com/

Page 117: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 109

Code sample³:

js/app.js

9 App.Router.map(function() {

10 this.route('about');

11 this.route('sign-in');

12

13 this.route('ask-question');

14

15 this.resource('questions', function() {

16 this.resource('question', { path: '/:question_id' });

17 });

18 });

Now lets open our application_routes.js and alter the existing index route so it will redirect theuser to the questions route (instead of providing model).

Code sample⁴:

js/routes/application_routes.js

1 App.IndexRoute = Ember.Route.extend({

2 redirect: function() {

3 this.transitionTo('questions');

4 }

5 });

Next we are going to move code from application into question objects of our application. Lets startwith controllers. Move the IndexController from application_controllers.js into question_-

controllers.js (and don’t forget to change the name of the controller).

³https://github.com/ryakh/emberoverflow/commit/a53c949545368b656806cce251fc729edd30474c⁴https://github.com/ryakh/emberoverflow/commit/6408c1981467db74de42c76508c71cc5d77c890b

Page 118: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 110

Code sample⁵:

js/controllers/question_controllers.js

71 App.QuestionsController = Ember.ArrayController.extend({

72 siteTitle: 'Welcome to Emberoverflow',

73

74 currentTime: function() {

75 return(new Date);

76 }.property()

77 });

We also need to create a QuestionsRoute in our question_routes.js file.

Code sample⁶:

js/routes/question_routes.js

7 App.QuestionsRoute = Ember.Route.extend({

8 model: function() {

9 return this.store.find('question');

10 }

11 });

And finally rename our index template to questions.

Code sample⁷:

index.html

53 <script type="text/x-handlebars" id="questions">

54 <div class="row">

55 <div class="col-md-8">

56 <h2>{{siteTitle}}</h2>

57

58 <p>It is {{currentTime}}</p>

59

60 <h3> Latest questions</h3>

61

62 <ul>

63 {{#each}}

⁵https://github.com/ryakh/emberoverflow/commit/b567fb4d3e0b5789e6306ffe5d18bc35b9ed1c54⁶https://github.com/ryakh/emberoverflow/commit/fc4cbb00e2ea7ca4c8033638401d734f8b103454⁷https://github.com/ryakh/emberoverflow/commit/c21ede13a5cbb4c40aa393ba9c1df5ebbef97acd

Page 119: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 111

64 {{question-preview question=this}}

65 {{/each}}

66 </ul>

67 </div>

68 </div>

69 </script>

If you reload our application, you will see that you will be redirected to the questions route rightaway. If you click on any question you will see that it will become active, the link will change yetnothing will be displayed. To fix that we need to alter our questions template a little bit. Namelywe need to provide it with {{outlet}} expression which will hold all the nested content.

Code sample⁸:

index.html

53 <script type="text/x-handlebars" id="questions">

54 <div class="row">

55 <div class="col-md-6">

56 <h2>{{siteTitle}}</h2>

57

58 <p>It is {{currentTime}}</p>

59

60 <h3> Latest questions</h3>

61

62 <ul>

63 {{#each}}

64 {{question-preview question=this}}

65 {{/each}}

66 </ul>

67 </div>

68

69 <div class="col-md-6">

70 {{outlet}}

71 </div>

72 </div>

73 </script>

⁸https://github.com/ryakh/emberoverflow/commit/c7755422775fcd61735c5c971212762bb0590a39

Page 120: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 112

Nested Views in action

Now if you reload our application and try to click on the question, you will see it rendered on theright side of the page.

If you answer any question you will see the answer’s count update in real time. It’s allpossible thanks to the properties we created earlier.

12.1 Making Things Neat With Helpers

The current date and time we are displaying on the front page is in human unreadable format. Wecan easily fix that by creating a custom helper. First lets create a file to hold our helpers, it will becalled helpers.js (don’t forget to add it into our index.html). Here is howwe define custom helper.

Page 121: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 113

Code sample⁹:

js/helpers.js

1 Ember.Handlebars.registerBoundHelper('currentDate', function() {

2 });

Our helper will take in an argument which is a format of the date. We will use moment.js to formatour dates. So add the following script into our index.html.

Code sample¹⁰:

index.html

254 <script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"\

255 ></script>

Now we need to define the helper. Our helper will receive a single parameter called format and willuse it to format the date.

Code sample¹¹:

js/helpers.js

1 Ember.Handlebars.registerBoundHelper('currentDate', function(format) {

2 return moment().format(format);

3 });

And finally we need to update the template to use our helper.

⁹https://github.com/ryakh/emberoverflow/commit/de14ae8689d20454bd401ec8343da11f6fad9b33¹⁰https://github.com/ryakh/emberoverflow/commit/a9ed376ac04ace41c28e62c7c5e62d2b6c3393ef¹¹https://github.com/ryakh/emberoverflow/commit/b75d85bfe4f372849709defb3a07b295f3fa55a2

Page 122: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 114

Code sample¹²:

index.html

53 <script type="text/x-handlebars" id="questions">

54 <div class="row">

55 <div class="col-md-6">

56 <h2>{{siteTitle}}</h2>

57

58 <p>It is {{currentDate "LL"}}</p>

59

60 <h3> Latest questions</h3>

61

62 <ul>

63 {{#each}}

64 {{question-preview question=this}}

65 {{/each}}

66 </ul>

67 </div>

68

69 <div class="col-md-6">

70 {{outlet}}

71 </div>

72 </div>

73 </script>

It is also worth mentioning that some of Handlebars expressions you’ve seen before are helpers thatEmber provided us with. You have seen them in previous chapter. Namely they are: {{partial}},{{component}}, {{view}} and {{render}}.

12.2 Editing Questions

Here we are going to complicate things a little bit — only author of the question can edit it. To makethe editing of the question possible we are going to expand QuestionController and question

template.

First we are going to add isEditing property to the QuestionController using which we willdetermine wether to show or hide form for editing question. Next we are going to add thecanEditQuestion property which will determine wether a user can or can not edit the question.And finally we are going to add two actions — one to show the form and second to save the editsuser made.

¹²https://github.com/ryakh/emberoverflow/commit/b9c2e425ddf1042fc9b01e6375f553b40eb4fcc0

Page 123: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 115

Code sample¹³:

js/controllers/question_controllers.js

45 App.QuestionController = Ember.ObjectController.extend(

46 App.SetAuthorMixin, {

47 isEditing: false,

48

49 canEditQuestion: function() {

50 return this.get('author.id') == App.currentUser

51 }.property(),

52

53 actions: {

54 toggleEditQuestion: function() {

55 this.toggleProperty('isEditing');

56 },

57

58 submitEdits: function() {

59 this.toggleProperty('isEditing');

60 this.get('model').save();

61 },

62

63 // Additional lines truncated

64 }

65

66 // Additional lines truncated

67 }

68 });

Here is the template; here we are not using anything that was not covered in this book so far.

¹³https://github.com/ryakh/emberoverflow/commit/aa1bfef2dda2740ab2866f3156484c61eaebd7c1

Page 124: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 116

Code sample¹⁴:

index.html

75 <script type="text/x-handlebars" id="question">

76 <div class="row">

77 <div class="col-md-8">

78 <h2>{{title}}</h2>

79

80 {{#if isEditing}}

81 <form {{action submitEdits on="submit"}}>

82 <div class="form-group">

83 <label for="title">Question title</label>

84

85 {{textarea

86 id="question"

87 value=question

88 classNames="form-control"

89 placeholder="Describe your problem in as much detail as possible"}}

90 </div>

91

92 <button class="btn btn-primary" type="submit">

93 Update question

94 </button>

95 </form>

96 {{else}}

97 <p>

98 {{question}}

99 </p>

100

101 <p>

102 Asked by {{author.fullname}}

103 </p>

104

105 {{#if canEditQuestion}}

106 <p>

107 <a href="#" {{action toggleEditQuestion}}>Edit question</a>

108 </p>

109 {{/if}}

110 {{/if}}

111 </div>

112 </div>

¹⁴https://github.com/ryakh/emberoverflow/commit/8c8cc8ea7c219720eb884e94b11744128d448996

Page 125: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 117

113

114 <hr>

115

116 {{render 'answers' answers}}

117

118 <hr>

119

120 {{view App.AnswerFormView}}

121 </script>

Now if you reload our application and navigate between questions you will see that link to editthe question is not changing after the transition. You already know the way how to fix this —by binding our canEditQuestion property to another property. But which one should we use? Inthis case we need to use model property. This will tell our QuestionController to reevaluate thecanEditQuestion property every time the underlaying model changes.

Code sample¹⁵:

js/controllers/question_controllers.js

45 App.QuestionController = Ember.ObjectController.extend(

46 App.SetAuthorMixin, {

47 isEditing: false,

48

49 canEditQuestion: function() {

50 return this.get('author.id') == App.currentUser

51 }.property('model'),

52

53 // Additional lines truncated

54 }

55 });

Now if you change the question and submit changes they will be saved right away; all possiblethanks to a single line: this.get('model').save(). But how is that possible? Here is what’s goingon under the hood: our {{textarea}} helper has a value=question attribute defined. With thissingle line it is bound to the question model’s property question and when we are changing thecontent inside textarea Ember is updating the property on the model. And finally when we callthis.get('model').save() Ember will just take the current state of the model and save it.

¹⁵https://github.com/ryakh/emberoverflow/commit/71f20823124a513b533fd309886976f50191e6b8

Page 126: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 118

12.3 Final Tests

If you run our test suite two tests will fail. Lets start with the quesion links on index page lead

to questions test. It fails for two reasons. First we’ve nested our view and now there are two headerelements instead of one. Second we’ve added “Edit question” link wrapped in the paragraph. We caneasily fix that by changing values in our assertions.

Code sample¹⁶:

tests/tests.js

39 test("quesion links on index page lead to questions", function() {

40 visit("/");

41 click("ul:not(.nav) > li > a:first");

42

43 andThen(function() {

44 equal(

45 find("h2").length,

46 2,

47 "Question header and application headers are rendered"

48 );

49

50 equal(

51 find("p").length,

52 3,

53 "Question, author and edit link are rendered"

54 );

55 });

56 });

The second failing test is a little tricky. There are two failures. Both are happening due to the selectorselecting the wrong element. We can hack around the first one with some CSS magic. For the secondone we are going to upgrade our question template and give the paragraph of question a uniqueID. After you do that this test should pass.

¹⁶https://github.com/ryakh/emberoverflow/commit/03ff62460d812ef80e735fadefad06518f666ef3

Page 127: emberjs_applications.pdf

Nesting Views, Editing Records, Helpers 119

Code sample¹⁷:

tests/tests.js

76 test("signed-in user can ask new question", function() {

77 localStorage['currentUser'] = 201;

78 App.set('currentUser', 201);

79

80 visit("/ask-question");

81 fillIn("#title", "Question title");

82 fillIn("#question", "Question");

83 click("button");

84

85 fillIn("#answer", "Answer");

86 click("button");

87

88 andThen(function(){

89 equal(

90 find("h2:last").text(),

91 "Question title",

92 "Question title is rendered"

93 );

94

95 equal(

96 find("p#question").text().replace(/\s+/g, ''),

97 "Question",

98 "Question is rendered"

99 );

100

101 notEqual(

102 find(".panel").length,

103 0,

104 "New answer was added"

105 );

106

107 equal(

108 find(".panel-body").text().replace(/\s+/g, ''),

109 "Answer",

110 "Question was answered"

111 );

112 });

113 });

¹⁷https://github.com/ryakh/emberoverflow/commit/38a3b083c18fddfc38ff916768ad4b659dffde58

Page 128: emberjs_applications.pdf

13 Summing Things UpIn last chapter we’ve finished working on our application. The knowledge you’ve gained during thecourse of this book should be sufficient for you to start creating awesome Ember applications.

Unlike other frameworks, learning Ember is really hard. It introduces many new concepts that manyof us are unfamiliar with. On the other side, Ember has many different aspects and the currentdocumentation does not describe them.When I’ve started learning Ember I had a feeling that nothingrelates to anything — just bits of information randomly scattered around. All that contributes to thefact that the learning curve of Ember is rather steep.

At this moment you should already know all key Ember conventions and concepts. At this point youshould understand all the magic behind the Ember and things should be clicking in. In this chapterI am just going to summarise things you’ve learned from the book.

13.1 Naming Conventions

Ember uses naming conventions to stitch together different objects in your application withoutwriting too much repetitive code. The conventions are set in a way so you can always guess theexpected name. In case you do not provide the expected object, Ember will generate one for you andkeep it in memory.

When your application starts, Ember will look for objects with Application prefix, load them andin case there is nothing to load, Ember will generate them for you. Whenever you request a route,Ember will look for objects starting with the name of route requested.

Here is the table you’ve seen in chapter four. I’ll repeat it here again, because it does good jobillustrating how Ember naming conventions work:

Route Name Route Controller Template View

index IndexRoute IndexController index IndexView

about AboutRoute AboutController about AboutView

13.2 Ember Under Microscope

Remember when in first chapter I’ve listed all of the available objects with the brief description? Inthis part I will do so again yet in different way also describing each object in more details. Also Iwill try to tell how different objects relate to each other.

Page 129: emberjs_applications.pdf

Summing Things Up 121

Non-Scientific explanation of Ember’s internals

So lets go through this scheme step by step. When request (represented by URL typed into theaddress bar of the browser) comes into your application it is handled by the router. Router willidentify request and Ember will call for all other needed objects based on the request made. Lets say

Page 130: emberjs_applications.pdf

Summing Things Up 122

our user requested /posts.

After the request was identified, Ember will relay it to the route object. So in our case ember willsearch for the PostsRoute object. PostsRoute object is the one responsible for getting the data fromexternal source into our application by querying the store (think of the store as of a layer that isbeneath all of your models). To query the store we need to have a model defined. So in our case wewill need to define a PostModel.

After route fetches the data from the store, Ember will give the data to the PostsController so itcan decorate the data and send it further in a user presentable form. Controller and model are notrelated in a direct way. Controller knows nothing about the model and vice versa. Controller willwork with any data provided by the route. So you can fetch anything, lets say list of users in yourPostsRoute and send it into PostsController.

There is one layer between controller and template: a view. At this point all you need to know isthat view object sets the template and you also can define actions on your view. If you don’t providea templateName property, Ember will use it’s convention and will look for posts template.

And finally we have a template. Template is responsible for presenting data to the user. Fairly simpleuntil you complicate it by inserting other objects into your template.

There are four basic type of objects you can insert into any template. Partial is the “dumbest” one.It will just insert the defined template into the current one keeping the context (model, view andcontroller) of the latter.

Next we have a component and a view. They are very similar; in fact component is a subclass ofview. They will render their own templates. The difference between two is the context. Componentknows nothing about model. You can just pass any object into the component and work with it.View is a little bit tricky; component will use the current view wether the view has it’s own viewdefined as an object (remember if you don’t provide any, Ember will generate one for you). Andfinally the controller used will be the current one for both of them.

And finally we have render; the most complicated of them all. Render will insert defined templateinto the current one and will decorate the inserted template with it’s own controller. Render willnot use the context of the current template; instead it will use it’s own model, view and controller.

Page 131: emberjs_applications.pdf

Final WordsSo where to now? There is only one last piece of advice left for me to give you: get out into the wildand start developing your first Ember application.

Here is the list of resources that you may find useful when developing your own Ember applications:

1. An official Ember API¹.2. Ember Weekly — Ember.js news, tips & code delivered directly to your inbox².3. Ember Watch — Collection of Ember resources³.4. Also don’t forget to check the Appendix of the book for some useful recipes.

I hope you’ve enjoyed reading this book as much as I enjoyed writing it. And I hope that workingwith Ember will be a pleasant experience to you.

Acknowledgement

First I would like to give shout-out to the entire Ember.js team for developing a great product. Youguys made the world a better place.

Thanks to my fiancée for supporting me on this book.

Thanks to Sharunas Jurevic⁴ for the photo on the cover of the book.

And finally thank you for reading this book :-)

¹http://emberjs.com/api/²http://emberweekly.com/³http://emberwatch.com/⁴http://www.flickr.com/photos/stuffedpeppers/5571468411/in/set-72157626256391191/

Page 132: emberjs_applications.pdf

AppendixThis part of book contains small code snippets — recipes that can be useful to you once you startdeveloping your own Ember application.

Stubbing Out Fixture Querying

If you want to query your fixtures you have to implement your own queryFixtures method inFixtureAdapter. The best way to do so is to return a static value since fixtures are there to jump-start you application development and my advice is to start using backend as soon as possible.

This example shows you how to implement queryFixturesmethod that will search within fixtures:

1 App.FixtureAdapter = DS.FixtureAdapter.extend({

2 queryFixtures: function(records, query, type) {

3 return records.filter(function(record) {

4 for(var key in query) {

5 if (!query.hasOwnProperty(key)) { continue; }

6 var value = query[key];

7 if (record[key] !== value) { return false; }

8 }

9 return true;

10 });

11 }

12 });

However it is better to stub the method and make it return static value.

Page 133: emberjs_applications.pdf

Appendix 125

Sending Authentication Token with Every Request

At it’s core, Ember’s REST adapter uses jQuery for Ajax requests. Knowing that fact, addingauthentication token to every request is pretty straight forward:

1 Ember.$.ajaxSetup({

2 beforeSend: function(xhr) {

3 xhr.setRequestHeader('Token name', 'Token value');

4 }

5 });

Setting Model Out of the Context

This technique is useful when you are using {{render}} helper and can’t take the model from thecontext of the current controller. For example you can use this technique if you are trying to rendera list of users on every route.

1 App.ApplicationRoute = Ember.Route.extend({

2 setupController: function() {

3 this.controllerFor('users').set('model', this.store.find('user'));

4 },

5 })

Now you can call in your template for {{render "users"}} expression without setting the model;it will be set in the ApplicationRoute thanks to setupController method.

Page 134: emberjs_applications.pdf

Appendix 126

Make localStorage Observable by Ember

All we need to do is to create a wrapper around the localStorage and use the wrapper to observe forchanges:

1 var LocalStorage = Ember.Object.extend({

2 unknownProperty: function(key) {

3 return localStorage[key];

4 },

5

6 setUnknownProperty: function(key, value) {

7 localStorage[key] = value;

8 this.notifyPropertyChange(key);

9 return value;

10 }

11 });

12

13 var storage = new LocalStorage();

And now you can use localStorage as any other Ember object:

1 Ember.set(storage, 'key', 'value');

Kudos to Yehuda Katz for this one.