workshop 17: emberjs parte ii

54
Front End Workshops EmberJS - In Depth Marc Torrent & Mario García [email protected] [email protected]

Upload: visual-engineering

Post on 16-Feb-2017

418 views

Category:

Software


1 download

TRANSCRIPT

Page 1: Workshop 17: EmberJS parte II

Front End WorkshopsEmberJS - In Depth

Marc Torrent & Mario García

[email protected]@visual-engin.com

Page 2: Workshop 17: EmberJS parte II

In previous workshops of EmberJS...

Page 3: Workshop 17: EmberJS parte II

Remember theBIG PICTURE

ember-cli Router

Routesapplication.js

Models

Templates

Components

Ember-DataControllers

SERVICES

Page 4: Workshop 17: EmberJS parte II

Ember Data - Adapters & Serializers

Page 5: Workshop 17: EmberJS parte II

Ember Data

ApplicationRoute/Controller/Component

Can be thought as a read-through cache

Single store - Central repository of models in your application

Beware of cache-backend desynchronization!

Ember Data

Page 6: Workshop 17: EmberJS parte II

Communication with backend

How we access dataDS.Adapter

DS.Serializer

As of Ember Data 2.0, the default Adapter and Serializer follow JSON API format.

REST API

Ember

Ember DataFormat of the data

Page 7: Workshop 17: EmberJS parte II

JSON API Specification

Content-Type: application/vnd.api+json

Top level attribute “data”

Attribute names are dasherized

Attribute “type” should be pluralized

It’s about the data...

Page 8: Workshop 17: EmberJS parte II

JSON API Specification

..but also about the end points!

Action HTTP Verb URL Response status*

Find GET /articles/123200 - Successful404 - Not found

Find All GET /articles200 - Successful404 - Not found

Update PATCH /articles/123200 - Successful204 - No Content

Create POST /articles201 - Created204 - No Content

Delete DELETE /articles/123200 - Successful204 - No Content

*More response statuses are possible. See the complete specification at http://jsonapi.org/format/

Page 9: Workshop 17: EmberJS parte II

Adapters

Extend/build your own adapters to fit your needs!

Priority rules apply between adapters!

DS.Adapter

DS.JSONAPIAdapter

DS.RESTAdapter

How to communicate with backend

Page 10: Workshop 17: EmberJS parte II

Customize your adapter (1 of 3)

// app/adapters/application.js

export default DS.JSONAPIAdapter.extend({

host: 'https://api.example.com',

namespace: 'api/v1'

});

store.findRecord('post', 1) GET request to http://api.example.com/api/v1/posts/1

Host customization - Endpoint path customization

store.findAll('person') GET request to http://api.example.com/api/v1/people

store.findAll('user-profile') GET request to http://api.example.com/api/v1/user-profiles

Page 11: Workshop 17: EmberJS parte II

Customize your adapter (2 of 3)

Type path customization

// app/adapters/application.js

export default DS.JSONAPIAdapter.extend({

pathForType(type) {

return Ember.String.underscore(type);

}

});

store.findRecord('post', 1) GET request to /post/1

store.findAll('person') GET request to /person

store.findAll('user-profile') GET request to /user_profile

Headers customization

// app/adapters/application.jsexport default DS.JSONAPIAdapter.extend({ headers: { 'API_KEY': 'secret key', 'ANOTHER_HEADER': 'Some header value' }});

Page 12: Workshop 17: EmberJS parte II

Customize your adapter (3 of 3)

Pluralization customization

Ember Inflector at your rescue!

Allows definition of irregular and uncountable pluralizations

Houston...

Our API REST serves at the following endpoints:➔ /formulae (instead of the regular form /formulas)➔ /advice (instead of the pluralized form /advices)

...do we have a PROBLEM?

Page 13: Workshop 17: EmberJS parte II

Serializers

DS.Serializer

DS.JSONAPISerializer

DS.JSONSerializer

DS.RESTSerializer

Extend/build your own serializers to fit your needs!

Priority rules apply between serializers!

Define the format of the data

Page 14: Workshop 17: EmberJS parte II

Serializing data sent to backend

Customize your serializer (1 of 4)

// JSON generated by Ember Data{ "data": { "attributes": { "name": "Product", "amount": 100, "currency": "SEK" }, "type": "product" }}

// But server expects...{ "data": { "attributes": { "name": "Product", "cost": { "amount": 100, "currency": "SEK" } }, "type": "product" }}

Change the format of the data sent to backend with the serialize() hook.

Page 15: Workshop 17: EmberJS parte II

Normalizing data received from backend

Customize your serializer (2 of 4)

// Server response{ "id": "1", "body": "A comment!", "date": { "offset": "GMT+0200 (CEST)", "value": "Tue Apr 05 2016" }}

// But Ember Data expects...{ "id": "1", "body": "A comment!", "offset": "GMT+0200 (CEST)", "value": "Tue Apr 05 2016"}

Change the format of the data received from backend with the normalizeResponse() hook.

Page 16: Workshop 17: EmberJS parte II

Identifier

Customize your serializer (3 of 4)

Specify your resource identifier key through primaryKey property

export default DS.JSONSerializer.extend({

primaryKey: '_id'

});

Attribute names

If the JSON payload does not match your Ember model attribute names...

// app/models/person.js

export default DS.Model.extend({

lastName: DS.attr('string')

});

// app/serializers/person.js

export default DS.JSONAPISerializer.extend({

attrs: {

lastName: 'lastNameOfPerson'

}

});

Page 17: Workshop 17: EmberJS parte II

Custom transforms

Customize your serializer (4 of 4)

Use custom transforms if the server sends data that does not fit any Ember Data built-in attributes (string, number, boolean and date)

A custom transform should implement the serialize() and deserialize()

Once implemented, it can be used as a regular type of attribute

Page 18: Workshop 17: EmberJS parte II

Routing and Navigation

Page 19: Workshop 17: EmberJS parte II

Routing - Implicit routes

ember g route users

ember g route users/edit

applicationapplication-loading

application-error

indexindex-loading

index-error

usersusers-error

users/index

users-loading

users/edit

*All routes have its .hbs associated templates

loading

error

users/edit-error

users/edit-loading

users/index-loading

users/index-error

users/loading

users/error

Page 20: Workshop 17: EmberJS parte II

Routing - Serve more than one model

Promises, promises, promises!

Ember.RSVP.hash resolves when all its promises parameters resolve

Be extra careful when using hash in the model() hook in cases where the hook is not executed*

*See http://stackoverflow.com/questions/20521967/emberjs-how-to-load-multiple-models-on-the-same-route

Alternatively, you can use route nesting to load multiple models

More info in http://emberigniter.com/load-multiple-models-single-route/

Page 21: Workshop 17: EmberJS parte II

Routing - Rendering a specific template

// app/routes/posts/new.js

export default Ember.Route.extend({

renderTemplate() {

this.render('posts/form');

}

});

It is possible to render a specific template through the renderTemplate() hook.

DRY*!!!!!

*Don’t Repeat Yourself

// app/routes/posts/edit.js

export default Ember.Route.extend({

renderTemplate() {

this.render('posts/form');

}

});

Page 22: Workshop 17: EmberJS parte II

Routing - Preventing and retrying transitions

Invoke transition.abort() to immediately abort the current transition

Store the transition object and re-attempt invoking transition.retry()

Every transition attempt fires the willTransition action on the current active routes, starting with the leaf-most route

Ember Router passes a transition object to various hooks on the involved routes

export default Ember.Route.extend({

beforeModel(transition) {},

model(params, transition) {},

afterModel(model, transition) {}

});

Page 23: Workshop 17: EmberJS parte II

Routing - Loading routes

When navigating to /users route, Ember will try to find loading templates*:

users-loading loading or application-loading

The router pauses transitions until the promises returned from beforeModel, model and afterModel hooks fulfill

An intermediate transition into a loading substate happens immediately

The URL won’t be updated on the loading transition

*See https://github.com/emberjs/ember.js/issues/12367

A loading event will be fired on that route

Page 24: Workshop 17: EmberJS parte II

Routing - Error routes

When an error occurs navigating to /users, Ember will try to find templates*:

users-error error or application-error

Analogous approach to loading substates in the case of errors

An intermediate transition into an error substate happens in case of thrown error or rejected promise

The URL won’t be updated on the error transition

*See https://github.com/emberjs/ember.js/issues/12367

An error event will be fired on that route

Page 25: Workshop 17: EmberJS parte II

Routing - Query params

{{#link-to "episodes" (query-params filter="simp")}}Episodes{{/link-to}}

transitionTo('episodes', { queryParams: { filter: 'simp' }});

Query params are declared on route-driven controllers

Use controller’s queryParams property to bind query params and URL

Query params to backend

Query params in frontend

transitionTo

link-to helper

store.query('episodes', { filter: 'simp' });

Page 26: Workshop 17: EmberJS parte II

Templates

Page 27: Workshop 17: EmberJS parte II

➢ Data Binding in template attributes

➢ Links

➢ Actions

➢ Input Helpers

➢ Write your own helpers

Page 28: Workshop 17: EmberJS parte II

Data Binding in Templates

Controller’s Properties

<div class=”_tomster”><img src={{tomster.img}}

width=100></div>

DOM Attributes

<div class=”_tomster”><input type=”checkbox”

disabled={{tomster.disabled}}><img src={{tomster.img}}

width=100></div>

Collections

{{#each cartoons as |cartoon|}} <div class=”_cartoon”>

<input type=”checkbox” disabled={{cartoon.disabled}}>

<img src={{cartoon.img}} width=100></div>{{/each}}

Page 29: Workshop 17: EmberJS parte II

Links: {{#link-to }}

A helper for links in our application without the need of using anchors and href.

Router.map( function() {

this.route(‘about’);

this.route(‘cartoons’, function() {

this.route(‘cartoon’, {path: ‘/:cartoon_id’});

});

});

<ul>

{{#each model as |cartoon|}}

<li>{{#link-to "cartoons.cartoon"

cartoon}}{{cartoon.name}}{{/link-to}}

</li>

{{/each}}

</ul>

<ul>

<li><a href="/cartoon/1">Columbus Tomster</a></li>

<li><a href="/cartoon/2">LTS Tomster</a></li>

</ul>

Page 30: Workshop 17: EmberJS parte II

Links: {{#link-to }} - Multiple Segments

We pass the model to the most inner nested route. The model() hook won’t be called on routes where we specify the model through the link-to.

Router.map( function() {

this.route(‘cartoons’, function() {

this.route(cartoon, {path: ‘/:cartoon_id’},

function() {

this.route(‘specs’);

this.route(‘spec’, {path: ‘/specs/:

spec_id’});

});

});

});

<ul>

{{#each cartoons as |cartoon|}}

{{#each cartoon.specs as |spec|}}

<li>{{#link-to "cartoons.cartoon.

spec" spec}}{{spec.name}}{{/link-to}}

</li>

{{/each}}

{{/each}}

</ul>

Page 31: Workshop 17: EmberJS parte II

Links: The Route that went away… (I)

Be careful with the structure of the templates and routes and beware of models that you pass into the routes

Page 32: Workshop 17: EmberJS parte II

Links: The Route that went away… (II)

Be careful with the structure of the templates and routes and beware of models that you pass into the routes

Keep an eye on how you distribute your outlets and the relation between index.hbs and the current template in our nested route.

Let’s see it in action!Now you tell me what should I

place in the router - routes - templates and link-to

Page 33: Workshop 17: EmberJS parte II

Actions

1. Template helper for binding a DOM Event with a Controller/Route/Component.

2. It can accept parameters such as Ember.Objects, Models or basic data structures.

3. You can also specify the type of event by using the “on” option

4. By default, actions prevent the default browser action on a DOM Element. To turn down this behavior, pass the option preventDefault=false

Page 34: Workshop 17: EmberJS parte II

Input Helpers {{input}} {{textarea}}

1. Template helper for <input> and <textarea> DOM Elements.

2. Forget about actions with inputs and use the the input helpers as EmberJS provides out of the box 2-way data-binding

3. Pass HTML and HTML5 attributes

4. Use it also with radio and checkboxes

Page 35: Workshop 17: EmberJS parte II

Write Your Own Helpers

Custom template helpers for your own needs. Keep an eye on addons to see if someone else has already written the helper you need.

ember generate helper i18n

That file should export a function wrapped with Ember.Helper.helper()

The function receives a bundle destination as string as the first parameter (params[0]) and the key to look for as the second parameter (params[1])

To pass multiple arguments to a helper, add them as a space-separated list after the helper name

Use named Parameters to make behavior of helpers configurable

export function i18n([bundle, key], {lang,

toUpperCase}) {

let resource = dictionary[bundle][key],

translation = lang? resource[lang] :

resource[DEFAULT_LANG];

return toUpperCase? translation.

toUpperCase() : translation;

}

export default Ember.Helper.helper(i18n);

<p>{{i18n "catalog" "category"

lang="es"}}</p>

<p>{{i18n "catalog" "category"}}</p>

<p>{{i18n "catalog" "category"}}</p>

<p>{{i18n "catalog" "product"

toUpperCase="true"}}</p>

Page 36: Workshop 17: EmberJS parte II

Services

Page 37: Workshop 17: EmberJS parte II

Routes

Controllers

Components

Helpers

...

Service

Persistent Object that can be accessed for all the application

modules though:

Ember.inject.service()

Page 38: Workshop 17: EmberJS parte II

Services - For What?

Services are useful for features that require shared state or persistent connections.

User/session authenticationGeolocation

Web SocketsServer-sent events or notifications

Server-backed API calls that may not fit Ember Data

Third-party APIs

LoggingInternationalization

Page 39: Workshop 17: EmberJS parte II

Services - and How can I use it?

Create the Service:

ember generate service i18n

Services must extend theEmber.Service base class:

Ember.Service.extend({});

Basic Service Advanced Service

Use the service in an Ember Object or inherited:

i18n: Ember.inject.service()

The Service will be injected by the same name we are assigning to a variable (i18n)

USE YOUR IMAGINATION, EVERYTHING IS POSSIBLE!

Page 40: Workshop 17: EmberJS parte II

Components

Page 41: Workshop 17: EmberJS parte II

LA JOYA DE LA CORONA

AND ROUTABLE COMPONENTS ARE COMING!!

Page 42: Workshop 17: EmberJS parte II

Components: Definition

➢ A reusable partial view (template with Handlebars) with controller that can be combined with other components and contain other components.

❖ Components are used in route templates.❖ They have a specific life cycle and can handle actions of their template and

also pass actions to their containing components, routes and controllers.❖ At this moment components are not routable but soon they will become

routable and then, controllers will disappear.❖ At this moment, EmberJS 2.4.0, All components need to be included inside

a template route that, by definition, has a Shim Controller. ❖ You can think of Components as widgets that are reusable through all our

application and that expose a template helper per usage.

ember generate component my-component→ components/my-component.js→ templates/my-component.hbs

<ul><li>{{my-component}}</li>

</ul>

Page 43: Workshop 17: EmberJS parte II

Components: Passing Properties

➢ Pass Properties TOP - DOWN to components. Always from route templates or other components to the destination component.

export default Ember.Route.extend({

model() {

return this.store.findAll('actor');

}

});

<ul>

{{#each model as |actor|}}

<li>{{actor-component actor=actor}}</li>

{{/each}}

</ul>

<div>{{actor.name}}<span {{action "toggleSection"}} style="margin-left: 5px;">{{more}}

</span></div>

{{#if showImage}}

<div><img src="{{actor.img}}" width="200px"></div>

{{/if}}

Page 44: Workshop 17: EmberJS parte II

Components: Wrapping Content

➢ Let the developer use your component by passing custom content into it.○ The template using your component will pass an html block inside the

component○ The component MUST define the {{yield}} expression in its template○ The component MUST be used in block form: {{#my-component}}...

{{/my-component}}

<div>{{yield}}<span {{action

"toggleSection"}}>{{more}}</span>

</div>

{{#if showImage}}

<div>

<img src="{{actor.img}}" width="200px">

</div>

{{/if}}

<ul>

{{#each model as |actor|}}

<li>{{#actor-component actor=actor}}

<p>{{actor.name}}</p>

{{/actor-component}}

</li>

{{/each}}

</ul>

Page 45: Workshop 17: EmberJS parte II

Components: LifeCycle

➢ Access to hooks when the component is going to be rendered or destroyed.

➢ It enables you to:○ Direct DOM manipulation○ Listening and responding to browser events○ Using 3rd party JavaScript libraries in your Ember app

Initial Rendering:

1 init

2 didReceiveAttrs

3 willRender

4 didInsertElement

5 didRender

Re-Rendering:

1 didUpdateAttrs

2 didReceiveAttrs

3 willUpdate

4 willRender

5 didUpdate

Component Destroy:

1 willDestroyElement

2 willClearRender

3 didDestroyElement

6 didRender

Page 46: Workshop 17: EmberJS parte II

Components: Data Down - Actions Up

➢ Keep the idea that state (properties) always travels in one direction:TOP - DOWN.

➢ Instead, event handling (actions) always travel in the opposite direction:BOTTOM-UP.

➢ Components and Controllers can handle actions from their contained Components. See it as a data synchronization hub.

export default Ember.Component.extend({

actions: {

toggleSection() {

this.toggleProperty('showImage');

this.get('onSelected')(this);

}

}

export default Ember.Controller.extend({

actions: {

sectionShown(selectedActor) {

this.set('selectedActor', selectedActor);

}

}

});

{{actor-component actor=actor onSelected=(action "sectionShown")}}

Page 47: Workshop 17: EmberJS parte II

Components: Transversal Communication

➢ In some situations components can not be on the same parent.➢ We need a data-layer and a BUS using Pub-Sub➢ For this purpose, use a Service extending Ember.Evented

export default Ember.Component.extend({

Appointment: Ember.inject.service(),

actions: {

complete() {

this.get(‘appointment’).trigger(‘complete’);

}

}

});

export default Ember.Component.extend({

appointment: Ember.inject.service(),

willRender() {

this.get(‘appoinment’).on(‘complete’,

this, ‘onComplete’);

},

onComplete() {

// Do Something

}

});

Page 48: Workshop 17: EmberJS parte II

Integration with 3rd party libraries

Page 49: Workshop 17: EmberJS parte II

Integrate into the Ember using the asset manifest file ember-cli-build.js

Integration with 3rd party libraries

If an Ember addon exists... Install via Ember CLI and you are ready to go!

Front-end dependencies can be installed through Bower Package Manager

Other assets can (and should) just be placed in the vendor folder of the project

app.import({

development: 'bower_components/moment.js',

production: 'bower_components/moment.min.js'

});

app.import('vendor/amd.js', {

exports: {

'amd': ['raw', 'request']

}

});

Globals AMD Modules

Page 50: Workshop 17: EmberJS parte II
Page 51: Workshop 17: EmberJS parte II

Remember theBIG PICTURE

ember-cli Router

Routes Models

Templates

Components

Ember-DataControllers

SERVICES

Page 52: Workshop 17: EmberJS parte II
Page 53: Workshop 17: EmberJS parte II

Thanks for your time!

Do you have any questions?

Page 54: Workshop 17: EmberJS parte II