Introducing Ember.js & ember-cli
David Thompson
Today
- Learn what Ember.js and ember-cli are
- Get an overview of the key concepts
- See an example project, using the Marvel API
- Get some pointers to next steps
Ember.js
Web application framework
Opinionated
Designed to communicate with an API
Ember-Data is an optional add-on to work with API data
ember-cli
Broccoli: asset compilation
QUnit: write tests
Testem: run tests
Babel: ES 2015 support
Express: local server
ember s
ember t --server
Marvel API
https://developer.marvel.com
Complex JSON objects in a non-standard format
{
"posts": [
{
"id": 1,
"title": "Ember is great",
"author": "David Thompson"
},
{
"id": 2,
"title": "I am bored",
"author": "Chad"
}
]
}
Standard?
{
"post": {
"id": 1,
"title": "The Name of the Rose",
"author": "Umberto Eco"
}
}
/posts
/posts/1
DS.RESTAdapter
Non-standard
{
"code": 200,
"status": "Ok",
"copyright": "© 2015 MARVEL",
"attributionText": "Data provided by Marvel. © 2015 MARVEL",
"attributionHTML": "<a href=\"http://marvel.com\">Data provided by Marvel. © 2015 MARVEL</a>",
"etag": "dec2c14b0d86d0abb13a402343744796cefb4b84",
"data": {
"offset": 0,
"limit": 20,
"total": 1485,
"count": 20,
"results": [
{
"id": 1011334,
"name": "3-D Man",
"description": "",
"modified": "2014-04-29T14:18:17-0400",
"thumbnail": {
"path": "http://i.annihil.us/u/prod/marvel/i/mg/c/e0/535fecbbb9784",
"extension": "jpg"
},
"resourceURI": "http://gateway.marvel.com/v1/public/characters/1011334",
"comics": {
"available": 11,
"collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/comics",
"items": [
{
"resourceURI": "http://gateway.marvel.com/v1/public/comics/21366",
"name": "Avengers: The Initiative (2007) #14"
},
{
"resourceURI": "http://gateway.marvel.com/v1/public/comics/24571",
"name": "Avengers: The Initiative (2007) #14 (SPOTLIGHT VARIANT)"
},
{
Lucky Pierre
Example Application
Base Template
Where the HTML ends up
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Superhero</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{content-for 'head'}}
<link rel="stylesheet" href="assets/vendor.css">
<link rel="stylesheet" href="assets/superhero.css">
{{content-for 'head-footer'}}
</head>
<body>
{{content-for 'body'}}
<script src="assets/vendor.js"></script>
<script src="assets/superhero.js"></script>
{{content-for 'body-footer'}}
</body>
</html>
/app/index.html
Router
Application State
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
export default Router.map(function() {
this.route('about');
this.resource('characters', function() {
this.route('show', {
path: ':character_id'
});
});
});
/app/router.js
/
/loading
/about
/characters
/characters/loading/
/characters/:character_id
Defining a path in the router means Ember looks for a matching Route, Controller, View, and Template
Route
Load the model
import Ember from 'ember';
export default Ember.Route.extend({
queryParams: {
query: {
refreshModel: true
},
offset: {
refreshModel: true
}
},
model: function(params) {
if (!params.query) {
return this.store.find('character', { offset: params.offset });
}
return this.store.find('character', { nameStartsWith: params.query, offset: params.offset });
}
});
/app/routes/characters/index.js
Load JSON from API into model
Handle query parameters
Model
Represent the data
import Ember from 'ember';
export default Ember.Route.extend({
queryParams: {
query: {
refreshModel: true
},
offset: {
refreshModel: true
}
},
model: function(params) {
if (!params.query) {
return this.store.find('character', { offset: params.offset });
}
return this.store.find('character', { nameStartsWith: params.query, offset: params.offset });
}
});
/app/models/character.js
Load JSON from API into model
Handle query parameters
Controller
Decorates the model
import Ember from 'ember';
export default Ember.ArrayController.extend({
queryParams: ['query', 'offset'],
query: null,
offset: 0,
increment: 20,
queryField: Ember.computed.oneWay('query'),
meta: function() {
return this.get("content.meta");
}.property('content.[]'),
actions: {
search: function() {
this.set('query', this.get('queryField'));
this.set('offset', 0);
},
next: function() {
// TODO: check if there are any more results - compare offset to total
var next = this.get('increment') + this.get('meta').offset;
this.set('offset', next);
}
}
});
/app/controllers/characters/index.js
Templates get properties from controller
Controllers handle events
View
"The role of the view in an Ember.js application is to translate primitive browser events into events that have meaning to your application."
Template
Presents the UI
<div class="clearfix mxn2">
<div class="col col-12 p2 mx-auto">
<div class="flex flex-wrap flex-stretch p1">
{{#each model as |character|}}
{{characters/character-card character=character}}
{{/each}}
</div>
</div>
</div>
/app/templates/characters/index.js
Use Handlebars to present data
Ember helpers for forms and UI elements
Wait! What about that API?!
Serializer
Format API data
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
extract: function(store, type, payload, id, requestType) {
var results = {};
results['characters'] = payload.data.results;
delete payload.data.results;
results['meta'] = payload.data;
return this._super(store, type, results, id, requestType);
}
});
/app/seriaizers/character.js
Transform
Custom attribute
import DS from 'ember-data';
export default DS.Transform.extend({
deserialize: function(serialized) {
if (serialized) {
serialized = serialized.path + "." + serialized.extension;
}
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
/app/transforms/thumbnail.js
Custom attribute available to models
Next Steps
- ember-cli 101
- Ember.js guides
- ember-cli guides
- https://github.com/foraker/ember-reading-group
- make something
Introducing Ember.js & ember-cli
By David Thompson
Introducing Ember.js & ember-cli
- 810