Escolar Documentos
Profissional Documentos
Cultura Documentos
Getting Started
What Is Ember?
Not as Important
Ruby, Python, Java, PostgreSQL, MySQL, etc.
Should Have
Others Using
Others Using
Others Using
Others Using
Others Using
Others Using
Separating Responsibilities
Model-view-controller (MVC) helps to create reusable and extendable code by separating and
isolating responsibilities.
model
Manipulates
Updates
view
controller
Displays
Uses
user
Current Responsibilities
Today, most web applications send data to the servers to validate, process, and render HTML.
Web Server
Web Browser
Request URL
validation
processing
rendering
Shifting Responsibilities
Frameworks like Ember are shifting responsibilities from the server to the web browser.
Web Server
Web Browser
Request URL
Receive HTML & Assets
validation
processing
rendering
All Encompassing
Ember provides the client-side framework plus development tooling, conventions, and standards.
framework
tooling
conventions
standards
Best Practices
Embers holistic approach gives you benets from the entire JavaScript community.
framework
tooling
convention
standards
Installing
Steps for installing Ember CLI and creating our Ember application:
1. Ensure Node.js is installed and npm is available.
https://nodejs.org
Installing
Steps for installing Ember CLI and creating our Ember application:
1. Ensure Node.js is installed and npm is available.
2. Install Ember CLI using npm.
Console
$ npm install -g ember-cli
Checking for
Once Ember CLI is installed, youll have an ember executable available.
Console
$ npm install -g ember-cli
ember-cli@2.x.x /path/to/node/lib/node_modules/ember-cli
$ ember version
version: 2.x.x
Getting Help in
Console
$ ember help
Usage: ember <command (Default: help)>
Available commands in ember-cli:
ember
ember
ember
ember
ember
ember
ember
ember
ember
ember
ember
Console
$ ember new woodland
installing app
create app/app.js
create app/index.html
create app/router.js
$ cd woodland
$ ember serve
Livereload server on http://localhost:49154
Serving on http://localhost:4200/
Level 2.1
Getting Started
Working in App
Most of your work will be done within the ember new-generated app directory.
app
Console
app.js
$ ember new
installing app
create app/app.js
create app/index.html
create app/router.js
components
index.html
models
router.js
routes
styles
app.css
templates
application.hbs
Introducing Templates
Templates tell Ember what HTML to generate and display in the web browser.
app/templates/application.hbs
<h2 id="title">Welcome to Ember</h2>
{{outlet}}
router.js
routes
templates
application.hbs
app/templates/application.hbs
<h2 id="title"></h2>
{{outlet}}
app/templates/application.hbs
Rendered HTML
<h2 id="title"></h2>
<h2 id="title"></h2>
{{outlet}}
app/templates/user.hbs
<h3>I am from user.hbs</h3>
app/templates/application.hbs
Rendered HTML
<h2 id="title"></h2>
<h2 id="title"></h2>
{{outlet}}
app/templates/post.hbs
<h3>I am from post.hbs</h3>
<p>I have more content</p>
Forgetting an {{outlet}}
Without an outlet in the application template, there is nowhere for other templates to go.
app/templates/application.hbs
<h2 id="title"></h2>
</h2>
app/templates/post.hbs
<h3>I am from post.hbs</h3>
<p>I have more content</p>
Bad
Rendered HTML
<h2 id="title"></h2>
Auto-generating Templates
Ember creates templates in memory automatically whenever you do not provide one.
app/templates/application.hbs
<h2 id="title">Woodland Wanderer
Whatchamacallits</h2>
{{outlet}}
Rendered HTML
<h2 id="title">Woodland Wanderer
Whatchamacallits</h2>
app/templates/index.hbs
Hello from Index
app/templates/orders.hbs
Hello from Orders
URL
/orders
View a receipt
for Order #123
/orders/123
app/router.js
import Ember from 'ember';
import config from './config/environment';
const Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
});
export default Router;
Router.map(function() {
this.route('orders', { path: '/orders' });
});
Router.map(function() {
this.route('orders', { path: '/orders' });
this.route('index', { path: '/' });
});
Router.map(function() {
this.route('orders', { path: '/orders' });
});
Router.map(function() {
this.route('orders');
});
app/templates/index.hbs
Hello from Index
<a href="/orders">Orders</a>
Bad
app/templates/index.hbs
Good
Router.map(function() {
this.route('orders');
});
app/templates/index.hbs
Hello from Index
{{#link-to "orders"}}Orders{{/link-to}}
Router.map(function() {
this.route('orders');
});
Customizing {{link-to}}
The generated anchor tag may be customized by passing additional attributes to {{link-to}}.
app/templates/index.hbs
Hello from Index
{{#link-to "orders" class="orders-link" }}Orders{{/link-to}}
app/templates/index.hbs
Hello from Index
{{#link-to "orders" tagName="div" }}Orders{{/link-to}}
Additional Helpers
Ember ships with many other Handlebars helpers for tag generation and logic.
debugger
each
if
input
link-to
log
textarea
unless
URL path
index
orders
/orders
url
User activity within the
app changes the state
and updates the URL.
Level 2.2
app/templates/index.hbs
Manages state
Denes HTML
???
Manages state
Denes HTML
Introducing Routes
Routes are responsible for collecting data and rendering the proper templates.
Router
Routes
Templates
Manages state
Denes HTML
Auto-generated Routes
Auto-generated routes render the template of the same name.
app/router.js
app/templates/orders.hbs
Router.map(function() {
this.route('orders');
});
Generating a Route
Ember CLI provides a generator for creating a route and updating the router.
ember generate route <route-name>
Console
app/routes/orders.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return 'Nate';
}
});
app/templates/orders.hbs
Order for {{model}}
app/routes/orders.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return { id: '1', name: 'Nate' };
}
});
app/templates/orders.hbs
Order {{model.id}} for {{model.name}}
Customizing a Route
Routes have several hooks for customizing their behavior.
Usage
activate
deactivate
model
redirect
app/templates/orders.hbs
{{#each model as |order|}}
{{#link-to ???}}
Order {{order.id}} for {{order.name}}<br>
{{/link-to}}
{{/each}}
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
State
URL
/orders
View a receipt
for Order #
/orders/###
order route
order template
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
order route
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
order template
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
/orders/1
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
/orders/1
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
/orders/1
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
/orders/1
/orders/
1
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
/orders/ :order_id
/orders/1
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
:order_id
1
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
{.:order_id 1}
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
?
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
{order_id:
1}}
{.order_id: 1
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
{order_id: 1}
router
order route
order template
app/router.js
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
{.order_id: 1}
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
{order_id: 1}
{id: '1', }
router
order route
order template
app/routes/order.js
export default Ember.Route.extend({
model(params) {
return [
{ id: '1', name: 'Nate' },
{ id: '2', name: 'Gregg' }
].findBy('id', params.order_id);
}
});
{order_id: 1}
findBy(property, value) is
provided by Ember and returns
the first matching item.
show me order 1
order route,
find order 1
order template,
render order 1
/orders/1
{order_id: 1}
{id: '1', }
router
order route
app/routes/order.hbs
<p>Order {{model.id}} for {{model.name}}</p>
<p>The order is ready!</p>
order template
app/templates/orders.hbs
{{#each model as |order|}}
{{#link-to ???}}
Order {{order.id}} for {{order.name}}<br>
{{/link-to}}
{{/each}}
order object
app/templates/orders.hbs
{{#each model as |order|}}
{{#link-to "order" order}}
Order {{order.id}} for {{order.name}}<br>
{{/link-to}}
{{/each}}
app/templates/orders.hbs
{{#each model as |order|}}
{{#link-to "order" order}}
Order {{order.id}} for {{order.name}}<br>
{{/link-to}}
{{/each}}
Rendered HTML
<a href="/orders/1">Order 1 for Nate<br></a>
<a href="/orders/2">Order 2 for Gregg<br></a>
Navigating to an Order
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
application.hbs
page header
index.hbs
page content
{{outlet}}
orders.hbs
page footer
order.hbs
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
/
URL path
application.hbs
page header
index.hbs
page content
{{outlet}}
orders.hbs
page footer
order.hbs
Route
index.js
Template
index.hbs
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
/orders
URL path
/orders
application.hbs
page header
index.hbs
page content
{{outlet}}
orders.hbs
page footer
order.hbs
Route
orders.js
Template
orders.hbs
Router.map(function() {
this.route('orders');
this.route('order', { path: '/orders/:order_id' });
});
/orders/1
URL path
/orders/1
application.hbs
page header
index.hbs
page content
{{outlet}}
orders.hbs
page footer
order.hbs
Route
order.js
Template
order.hbs
Router.map(function() {
this.route('orders', function() {
this.route('order', { path: '/:order_id' });
});
});
Adding an anonymous
function defines a parent route.
application.hbs
page header
index.hbs
page content
{{outlet}}
orders.hbs
page footer
list
order detail
{{outlet}}
orders/index.hbs
orders/order.hbs
Router.map(function() {
this.route('orders', function() {
this.route('order', { path: '/:order_id' });
});
});
application.hbs
page header
index.hbs
page content
{{outlet}}
orders.hbs
page footer
list
order detail
{{outlet}}
/
URL path
index.js
Template
index.hbs
Router.map(function() {
this.route('orders', function() {
this.route('order', { path: '/:order_id' });
});
});
application.hbs
page header
index.hbs
page content
{{outlet}}
orders.hbs
page footer
list
order detail
{{outlet}}
/orders
URL path
/orders
orders/index.js
Template
orders/index.hbs
orders/index.hbs
orders/order.hbs
A new orders.index
route is automatically
created.
Router.map(function() {
this.route('orders', function() {
this.route('order', { path: '/:order_id' });
});
});
application.hbs
page header
index.hbs
page content
{{outlet}}
orders.hbs
page footer
list
order detail
{{outlet}}
/orders/1
URL path
/orders/1
orders/order.js
Template
orders/order.hbs
orders/index.hbs
orders/order.hbs
An orders
namespace is added
to the order route.
app/templates/orders.hbs
{{#each model as |order|}}
{{#link-to "orders.order" order}}
Order {{order.id}} for {{order.name}}<br>
{{/link-to}}
{{/each}}
{{outlet}}
Level 3
app/routes/orders.js
app/routes/order.js
Generating a Service
Services are long-living objects (aka, singletons) that are available throughout your app.
Console
$ ember generate service store
installing service
create app/services/store.js
Dening a Service
Services are dened in app/services and extend Ember.Service.
app/services/store.js
app/routes/orders.js
app/services/store.js
app/routes/orders.js
}
});
app/services/store.js
app/routes/orders.js
store: Ember.inject.service('store')
});
After injection, the store service becomes available as the store property.
app/services/store.js
app/routes/orders.js
store: Ember.inject.service()
});
app/services/store.js
app/routes/orders/order.js
app/services/store.js
app/routes/orders/order.js
app/services/store.js
app/routes/orders/order.js
store: Ember.inject.service()
});
2
3
4
Product
1. title
2. price
3. description
4. imageUrl
1
2
Product
1. title
2. price
3. description
4. imageUrl
Order
1. name
2. line items
1
Product
1. title
2. price
3. description
4. imageUrl
Order
1. name
2. line items
Line Item
1. product
2. quantity
Product
1. title
2. price
3. description
4. imageUrl
Order
1. name
2. line items
Line Item
1. product
2. quantity
Introducing Models
Models represent the underlying (and sometimes persisted) data of the application.
Product
1. title
2. price
3. description
4. imageUrl
app/models/product.js
import Ember from 'ember';
Product
1. title
2. price
3. description
4. imageUrl
app/models/product.js
import Ember from 'ember';
export default Ember.Object.extend({
});
Ember.Object provides
create(). Record properties
may optionally be passed
in at creation.
app/models/line-item.js
app/models/order.js
import Ember from 'ember';
export default Ember.Object.extend({
});
app/services/store.js
import Ember from 'ember';
export default Ember.Service.extend({
getOrderById(id) { /* */ },
getOrders() { /* */ }
});
app/services/store.js
import
import
import
import
app/
!"" models/
# !"" line-item.js
# !"" order.js
# $"" product.js
$"" services/
$"" store.js
app/services/store.js
import
import
import
import
app/
!"" models/
# !"" line-item.js
# !"" order.js
# $"" product.js
$"" services/
$"" store.js
app/services/store.js
Product
1. title
2. price
3. description
4. imageUrl
import
import
import
import
app/services/store.js
import Product from 'woodland/models/product';
Product
1. title
2. price
3. description
4. imageUrl
getProducts() returns the
product record array to the
caller.
const products = [
Product.create({title:
Product.create({title:
Product.create({title:
Product.create({title:
];
app/services/store.js
Order
1. name
2. line items
Line Item
1. product
2. quantity
const orders = [
Order.create({ id: '1234', name: 'Blaise
items: [
LineItem.create({product: products[0],
LineItem.create({product: products[1],
LineItem.create({product: products[2],
LineItem.create({product: products[3],
]
}),
];
Blobfish',
quantity:
quantity:
quantity:
quantity:
1}),
1}),
0}),
0})
app/services/store.js
Order
Line Item
1. name
1. product
2. line items 2. quantity
app/templates/index.hbs
<div class="card">
<h1>Order Today!</h1>
<p class="card-content">Our online store helps</p>
{{#link-to "orders"}}Order Today!{{/link-to}}
</div>
<div class="grid group">
{{#each model as |product|}}
<div class="product-media">
<img src="{{product.imageUrl}}" />
</div>
<div class="product-content">
<h2>{{product.title}}: <b>${{product.price}}</b>
app/styles/app.css
html {
background-color: #ebe9df;
color: #726157;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
line-height: 1.5;
}
body {
min-height: 100%;
}
/* */
Level 4
Actions
We need
something from
which to populate
the order form.
Well start by
initializing a
new, empty
order record.
app/routes/orders/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
const store = this.get('store');
return store.newOrder();
},
store: Ember.inject.service()
});
app/templates/orders/index.hbs
<form>
<label for="name">Name</label>
<input type="text" id="name" >
{{#each model.items as |item|}}
<label>
{{item.product.title}} @
<input type="number" min="0">
</label>
{{/each}}
<input type="submit" type="Order">
</form>
app/templates/orders/index.hbs
<form>
<label for="name">Name</label>
{{input type="text" id="name" value=model.name}}
{{#each model.items as |item|}}
<label>
{{item.product.title}} @
{{input type="number" min="0" value=item.quantity}}
</label>
{{/each}}
<input type="submit" type="Order">
</form>
How do we
intercept the
submission?
Introducing Actions
Actions map generic DOM events to specic application activities and functions.
DOM events
application domain
map to
event
actionName
click
expandArticle
keyup
submit
autoCompleteSearch
createOrder
app/templates/orders/index.hbs
<form>
<!-- -->
<button type="submit">Order</button>
</form>
Mapping an Action
Actions are mapped in templates using the {{action}} helper, dened on the element to watch.
app/templates/orders/index.hbs
<form>
<!-- -->
<button type="submit">Order</button>
</form>
Mapping an Action
Actions are mapped in templates using the {{action}} helper, dened on the element to watch.
app/templates/orders/index.hbs
<form {{action "createOrder" model on="submit"}}>
<!-- -->
<button type="submit">Order</button>
</form>
Handling Actions
Action handlers are functions dened in an actions block on the route or its parents.
app/routes/orders/index.js
export default Ember.Route.extend({
actions: {
createOrder(order) {
const name = order.get('name');
alert(name + ' order saved!');
}
},
model() { return this.get('store').newOrder(); }
});
Setting an ID to pretend
that the order was saved.
Ordering in Bulk
To nish the order form, the design calls for a button to increment item quantities by 10.
app/templates/orders/index.hbs
<!-- -->
{{#each model.items as |item|}}
<label>
{{item.product.title}} @
${{item.product.price}}/ea
{{input type="number" min="0" value=item.quantity}}
<button {{action "addToItemQuantity" item 10}}>+10</button>
</label>
{{/each}}
<!-- -->
app/routes/orders/index.js
export default Ember.Route.extend({
actions: {
addToItemQuantity(lineItem, amount) {
lineItem.incrementProperty('quantity', amount);
},
createOrder(order) { /* */ }
},
model() { /* */ }
});
Ordering Complete
All of the functionality for adding a new order is now in the system.
Level 5.1
Calculate
LineItem costs
Calculate and
style the cost
percentages
Missing
total price
What we want
app/templates/orders/order.hbs
app/templates/orders/order.hbs
app/models/line-item.js
import Ember from 'ember';
export default Ember.Object.extend({
title: Ember.computed('product.title', function() {
return this.get('product.title');
})
});
alias
collect
empty
equal
lterBy
mapBy
sort
sum
uniq
app/models/line-item.js
import Ember from 'ember';
export default Ember.Object.extend({
title: Ember.computed.alias('product.title' ),
unitPrice: Ember.computed.alias('product.price')
});
app/templates/orders/order.hbs
{{#each model.items as |lineItem|}}
<tr>
<td>{{lineItem.title}}</td>
<td>{{lineItem.quantity}}</td>
<td>{{lineItem.unitPrice}}</td>
<td>$XX</td>
<td>XX%</td>
</tr>
{{/each}}
$10
$5
$0
$0
app/models/line-item.js
import Ember from 'ember';
export default Ember.Object.extend({
price: Ember.computed('quantity', 'unitPrice', function() {
return parseInt(this.get('quantity'), 10) * this.get('unitPrice');
}),
title: Ember.computed.alias('product.title'),
unitPrice: Ember.computed.alias('product.price')
});
parseInt() is used because quantity is getting set from a form input. Form inputs return
string values. Adding the base 10 indicator is just a good practice.
app/templates/orders/order.hbs
{{#each model.items as |lineItem|}}
<tr>
<td>{{lineItem.title}}</td>
<td>{{lineItem.quantity}}</td>
<td>{{lineItem.unitPrice}}</td>
<td>${{lineItem.price}}</td>
<td>XX%</td>
</tr>
{{/each}}
Calculate and
style the cost
percentages
Missing
total price
$10
+
=$15
=$15
=$15
$15
app/models/order.js
collect
empty
Name of the
collection to use
The element
property to map
equal
lterBy
mapBy
sort
sum
uniq
The mapped
array will
automatically
update if the
items array
changes or its
price values
change.
app/models/order.js
collect
empty
equal
lterBy
mapBy
sort
sum
uniq
this.get('price'); //=> 15
Level 5.2
/$10
=0%
/$10
=50%
/$10
=20%
/$10
=30%
Introducing Components
Components are a reusable way to combine a template with action handling and behavior.
Component
Template
Actions
Used for:
Charts
Tabs
Tree widgets
Buttons with conrmations
Date range selectors
Wrap libraries and services
http://w3c.github.io/webcomponents/spec/custom/
{{link-to}} and
{{input}} are
components!
Generating a Component
Ember CLI provides a generator for creating a component and its template.
Console
$ ember generate component item-percentage
installing component
create app/components/item-percentage.js
create app/templates/components/item-percentage.hbs
app/components/item-percentage.js
import Ember from 'ember';
export default Ember.Component.extend({
});
app/components/item-percentage.js
import Ember from 'ember';
export default Ember.Component.extend({
percentage: Ember.computed('itemPrice', 'orderPrice', function() {
return this.get('itemPrice') / this.get('orderPrice') * 100;
})
});
app/templates/components/item-percentage.hbs
<span>
{{percentage}}%
</span>
app/templates/orders/order.hbs
{{#each model.items as |lineItem|}}
<tr>
<td>{{lineItem.title}}</td>
<td>{{lineItem.quantity}}</td>
<td>{{lineItem.unitPrice}}</td>
<td>${{lineItem.price}}</td>
<td>
{{item-percentage itemPrice=lineItem.price orderPrice=model.price}}
</td>
</tr>
{{/each}}
But were
missing the
bold styling.
app/components/item-percentage.js
import Ember from 'ember';
export default Ember.Component.extend({
isImportant: Ember.computed.gte('percentage', 50),
percentage: Ember.computed('itemPrice', 'orderPrice', function() {
return this.get('itemPrice') / this.get('orderPrice') * 100;
})
});
app/templates/components/item-percentage.hbs
<span class="{{if isImportant 'important'}}">
{{percentage}}%
</span>
How about
clicking to show
the details?
app/components/item-percentage.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
toggleDetails() {
this.toggleProperty('showDetails');
}
},
toggleProperty() switches a
propertys value between true
and false each time it is called.
app/templates/components/item-percentage.hbs
<span class="{{if isImportant 'important'}}" {{action "toggleDetails"}}>
{{percentage}}%
</span>
app/templates/components/item-percentage.hbs
<span class="{{if isImportant 'important'}}" {{action "toggleDetails"}}>
{{#if showDetails}}
${{itemPrice}} / ${{orderPrice}}
{{else}}
{{percentage}}%
{{/if}}
</span>
Application Complete!