Upload
sandino-nunez
View
160
Download
0
Embed Size (px)
Citation preview
INTRO TO EMBER.JS
Sandino Núñez@sandinosaso
https://github.com/sandinosaso
YES, ANOTHER JAVASCRIPT FRAMEWORK
EMBER.JS
WHAT IS EMBER.JS ➤ A framework for creating ambitious web application➤ Built for productivity (provides architecture)➤ Opinionated (sometimes perceived as difficult to
learn)➤ Convention over Configuration
(besides awesome!)
Simple apps can use simple tools
APPS NEEDS
➤ Keep it organized➤ Modularize your code➤ Separate concerns➤ Establish conventions for smooth
teamwork➤ Keep it testable
ARCHITECTUREBIG
EMBER CORE CONCEPTS➤ Classes and Instances➤ Computed Properties➤ Bindings➤ Templates➤ Route➤ Models➤ Components➤ Data Down - Actions Up➤ Lots of other features - we’ll just barely scratch the
surface in today’s session
EMBER OBJECT➤ Base “class” that almost every object should inherit
from➤ Contains …
EMBER OBJECT 1 import Ember from 'ember';
2 3 let Person = Ember.Object.extend({ 4 say(message) { 5 alert(message); 6 } 7 }); 8 9 let Bob = Person.create(); 10 Bob.say('Hello World'); 11 // alerts "Hello World" 12 13 let Man = Person.extend({ 14 say(message) { 15 message += ', says the man.'; 16 this._super(message); 17 } 18 }); 19 20 let Tom = Man.create(); 21 Tom.say('Hello World'); 22 // alerts "Hello World, says the man."
EMBER OBJECT➤ Obj.create() instead of new instances➤ Obj.extend() for single inheritance (mixins exists as
well)➤ this._super() calls overridden implementation➤ Obj.reopen() to edit class definition➤ Obj.get(‘key’) and Obj.set(‘key’)➤ Allows dynamically created property values➤ Object can listen for properties changes➤ Use of .setProperties({ key: value }) for multiple at
time.
COMPUTED PROPERTIES➤ Used to build a property that depends on other
properties➤ Provide any property key you access and the property
value will be recomputed if change➤ Should not contain application behavior, and should
generally not cause any side-effects when called. 1 import Ember from 'ember'; 2 3 let Person = Ember.Object.extend({ 4 firstName: '', 5 lastName: '', 6 7 fullName: Ember.computed('firstName', 'lastName', function() { 8 return this.get('firstName') + ' ' + this.get('lastName'); 9 }) 10 }); 11 12 let bob = Person.create({ 13 firstName: 'Bob', 14 lastName: 'Esponja' 15 }); 16 17 bob.get('fullName'); 18 // "Bob Esponja"
COMPUTED PROPERTIES
(advanced usage)
1 export default Ember.Component.extend({ 2 selectedTodo: null, 3 4 todos: [ 5 Ember.Object.create({ isDone: true }), 6 Ember.Object.create({ isDone: false }), 7 Ember.Object.create({ isDone: true }) 8 ], 9 10 // Custom 11 incomplete: Ember.computed('[email protected]', function() { 12 var todos = this.get('todos'); 13 return todos.filterBy('isDone', false); 14 }), 15 16 // Using defined macro 17 incomplete: Ember.computed.filterBy('todos', 'isDone', false), 18 19 indexOfSelectedTodo: Ember.computed('selectedTodo', 'todos.[]', function() { 20 return this.get('todos').indexOf(this.get('selectedTodo')); 21 }) 22 });
BINDINGS➤ Unlike most other frameworks that include some sort of
binding implementation, bindings in Ember can be used with any object.
➤ A one-way binding only propagates changes in one direction, using computed.oneWay()
1 wife = Ember.Object.create({ 2 householdIncome: 80000 3 }); 4 5 Husband = Ember.Object.extend({ 6 householdIncome: Ember.computed.alias('wife.householdIncome') 7 }); 8 9 husband = Husband.create({ 10 wife: wife 11 }); 12 13 husband.get('householdIncome'); // 80000 14 15 // Someone gets raise. 16 wife.set('householdIncome', 90000); 17 husband.get('householdIncome'); // 90000
TEMPLATES➤ Uses Handlebars + Ember helpers➤ Ember adds partial, render, view and control helpers➤ Each template is backed by a model➤ They include helpers such as: other templates, if
else, loops, formatting helpers, expressions like {{model.firstName}}, outlets (which are placeholders), components
1 <div id="main"> 2 <div class="container-fluid"> 3 4 {{outlet}} 5 6 </div> 7 </div>
ROUTES➤ Url representation of your application’s objects, telling the
template ➤ The router.js file defines the mapping between routes/urls
1 import Ember from 'ember'; 2 import config from './config/environment'; 3 4 const Router = Ember.Router.extend({ 5 location: config.locationType 6 }); 7 8 Router.map(function() { 9 this.route('login'); 10 11 this.route('patients', function() { 12 this.route('new'); 13 this.route('edit', { path: 'edit/:id' }); 14 this.route('view', { path: 'view/:id' }); 15 this.route('audit', { path: 'audit/:id' }); 16 17 this.route('activities', { path: '/activities/:patient_id' }, function() { 18 this.route('new', { path: 'new' }); 19 this.route('view', { path: 'view/:id' }); 20 }); 21 22 this.route('events', { path: '/events/:patient_id' }, function() { 23 this.route('new', { path: '/new/:eventType' }); 24 this.route('view', { path: '/view/:id' }); 25 }); 26 27 }); 28 }); 29 30 export default Router;
1 import Ember from 'ember'; 2 3 export default Ember.route.extend({ 4 5 model: function() { 6 return [ 7 { 8 title: “London Palace" 9 }, 10 { 11 title: "Eiffel Tower" 12 } 13 ]; 14 } 15 });
ROUTING(implementation of a base route)
The model usually is a record. The result of a query to ember-data store
1 import Ember from 'ember'; 2 3 export default Ember.route.extend({ 4 5 model(params) { 6 var url = 'https://api.github.com/repos/' + params.repo_id; 7 return Ember.$.getJSON(url); 8 } 9 });
The model can be a POJO
1 import Ember from 'ember'; 2 3 export default Ember.route.extend({ 4 5 model: function(params) { 6 return this.store.find('myModel', params); 7 }, 8 9 setupController: function(controller, model) { 10 controller.set('model', model); 11 } 12 });
The result of an ajax call
ROUTING(returning multiple models)
1 import Ember from 'ember'; 2 3 export default Ember.route.extend({ 4 5 setupController(controller, model) { 6 controller.setProperties({ 7 patient: model.patient, 8 products: model.products 9 }); 10 11 set(controller, 'isLoading', false); 12 }, 13 14 model(params) { 15 return RSVP.hash({ 16 patient: this.store.findRecord('patient', params.id), 17 products: this.store.findAll('product') 18 }); 19 } 20 });
ROUTES➤ The route queries the model and makes it available in the controller
and templates➤ When the template or models being shown to the user changes, Ember
automatically keeps the url in the browser’s address bar up-to-date➤ This means that at any point, users are able to share the URL of your
app➤ Ember Add-on for Chrome and Firefox let you inspect routes / data /
promises➤ error and loading routes automatically created and used on model
loading and error states
MODELS➤ Define a mapping to the application data➤ Has default types: string, number, boolean, date. Supports
customs types 1 import Ember from 'ember'; 2 import Model from 'ember-data/model'; 3 import attr from 'ember-data/attr'; 4 import { belongsTo, hasMany } from 'ember-data/relationships'; 5 6 const { computed } = Ember; 7 8 export default Model.extend({ 9 'name': attr('string'), 10 'surname': attr('number'), 11 'is_admin': attr('boolean'), 12 13 'roles': hasMany('roles', { embedded: 'always'}), 14 'office': belongsTo('office', { async: 'true' }), 15 16 fullName: computed('name', 'surname', () => { 17 return this.get('name') + ' ' + this.get('surname'); 18 }), 19 20 roleList: computed('roles.@each', () => { 21 return this.get('roles').map((role) => { 22 return role.get('name'); 23 }) 24 }) 25 });
MODELS
➤ In the first case the data comes from backend in the model itself (payload)
➤ In the second case the model only contains the ‘ID’ of the related model and is loaded asynchrony with an specific call when required.
13 'roles': hasMany('roles', { embedded: 'always'}), 14 'office': belongsTo('office', { async: 'true' }),
(relationships)
You can also define custom attr options, for example (readOnly):
16 'entityId': attr('string', { readOnly: true }),17 'entityType': attr('string', { readOnly: true }),
MODELS➤ You can define custom types to fit any kind of data➤ Makes it easy to maintain as you only need to change in
one place 1 // app/transforms/utc.js 2 import DS from 'ember-data'; 3 import moment from 'moment'; 4 5 export default DS.Transform.extend({ 6 serialize(value) { 7 let formatedDate = moment.utc(value).format('YYYY-MM-DDTHH:mm:ss', 'en'); 8 return formatedDate; 9 }, 10 11 deserialize(value) { 12 return value; 13 } 14 });
1 export default Model.extend({ 2 'productId': attr('number'), 3 'eventTypeID': attr('number'), 4 'reasonSVID': attr('string'), 5 'notes': attr('string'), 6 7 'eventDate': attr('utc', { defaultValue: () => new Date() }) 8 }
ADAPTER / SERIALIZER➤ By default ember-data uses JSON-API format
➤ You can override default behavior either application level or for particular models
(fit any backend data even legacy one !!)
➤ In Ember Data, the Adapter determines how data is persisted to a backend data store, such as the URL format and headers for a REST API.
➤ You can change default functionality (if your backend conventions differ from ember-data assumptions) by extending it
ADAPTER(customization example)
1 import Ember from 'ember'; 2 import DS from 'ember-data'; 3 4 export default DS.JSONAPIAdapter.extend({ 5 host: 'https://api.example.com', 6 namespace: 'api', 7 8 session: Ember.inject.service('session'), 9 10 headers: Ember.computed('session.authToken', function() { 11 return { 12 'API_KEY': this.get('session.authToken'), 13 'ANOTHER_HEADER': 'Some header value' 14 }; 15 }) 16 });
➤ In Ember Data, serializers format the data sent to and received from the backend store.
SERIALIZER(customization example)
ADAPTER / SERIALIZER
(customization/ methods)
COMPONENTS➤ A completed isolated view that has no access to the
surrounding context ➤ A great way to build reusable components for your application➤ routable-components will be the future
DATA DOWN! ACTIONS UP!➤ DDAU - single data flow (hence unidirectional)➤ Apps easier to reason about➤ Improves performance
(GoodBye MVC)
DEMO - SHOWCASE - TIME