Globals -> Modules
Callbacks -> Promises
Views -> Web Component
- Ember Igniter
- Ember.js - Functional Programming and the Observer Effect
- EmberConf 2015
- An Agile Design Manifesto for Ember.js
Templating goes together with Binding. You typically experience them as a single unit even if they are separate.
The compiler see the template fragment, identifying any binding expressions, event handlers, etc. It parses the binding expressions if any. The result of this compiling is cached so none of the above needs to be repeated.
- The Road to Ember 2.0
- HTMLBars - A feature-by-feature review
- How is HTMLBars better
- ember-cli-esnext
- morph-range
- simple-dom
- morph-attr
- dom-helper
- Long comment to read
- EmberShare using Share.js
- Controller vs Component
Routes drive components instead of controller + view. Think of it as iOS ViewController. So it is a Web Component == Component == ViewController.
In Ember 2.0, there is no more routable controller, only routable component. Controllers aren't really needed. At least not as a standalone entity.
In Ember 2.0, replace the concept of the controller to the concept of top-level component. Pick up from Cocoa. Route go through a component instead of through a controller. Simplify data flow between component and bring in virtual DOM idea.
tE2Hi1keT6kaIdd
// Deprecated in Ember 2.0
{{#each users}}
{{firstName}}
{{lastName}}
{{/each}}
// Use this instead
{{#each user in users}}
{{user.firstName}}
{{user.lastName}}
{{/each}}
// Easy to forget _super() when using didInsertElement
didInsertElement: function() {
this._super();
this.set('inserted', true);
}
// Instead use this, no need to remember to use _super()
markAsInserted: function() {
this.set('inserted', true);
}.on('didInsertElement');
- Inside FastBoot: The road to server-side rendering
- Where is FastBoot?
- Ember 2.0 and FastBoot
- You're missing the point of server-side rendered JavaScript
- Re-render universe
STOP: http://emberjs.com/guides/components/
Use ember-cli instead of starter kit.
Ember is Cocoa and Angular is JSF? Ember in a page has many tiny group of MVC.
Don't operating on wrong assumption. Expose your ignorance.
- Ember Hot Seat
- Twitter search for Ember.js news
- Ember Weekly
- Ghost using Ember.js
- An in-depth introduction to Ember.js (Smashing Magazine)
- Want to learn Ember.js? Start here!
- Robin Ward's AngularJS vs Ember
- Instructure
- Ember vs Angular - Templates
- ember-appkit-rails using ES6 module but testing is QUnit
- A series to compare Angular with Ember
- ic-ajax
- EmberScript
- Ember ListView
- Build your own Ember app kit lite
- ES6 modules, build tools and browser app delivery
- Sketches from EmberConf 2014
- Drag and drop image upload with Ember.js
- Ember and Web Components
- Ember.js with Ruby on Rails
- Shared terminology yet different concepts between Ember.js and Rails
- Ember and Polymer
- Tuts+ Let's Learn Ember
- Some nice things to learn from iOS MVVM
- Ember Best Practices
- Interesting discussion
Ember.run.once();
setProperties({});
Since there is no round-trip, just pass real object in parameters, and no need for String.
ember g route friends/edit
ember g controller friends/edit --type=object
- Building an Ember app with Rails
- Building an Ember.js production application with ember-cli
- Deploying ember-cli and Rails to Heroku
- ember-cli-rails?
- Deploying ember-cli to Amazon S3 with Grunt
- Rails and ember-cli
- Rails + Ember.js + ember-cli
- OAuth2 Resource Owner Password Credentials Grant
There are lots of options here:
- Separate repos
- Single repo (back-end and front-end directory)
- Single repo (client directory inside Rails app)
- Almost any setup you can think of...
For proxying, we can use ember-cli to Rails.
AppAPI/
.git
Gemfile
Rakefile
app/
AppUI/
.git
package.json # Development dependencies
bower.json # Front-end dependencies
gulpfile.js
app/
Broccoli is all about trees. Take the 'lib' tree, concat it into a single app.js.
var concat = require('broccoli-concat');
var compileSass = require('broccoli-sass');
var autoprefixer = require('broccoli-autoprefixer');
var mergeTrees = require('broccoli-merge-trees');
var appJs = concat('lib', {
inputFiles: ['jquery.js', '**/*.js'],
outputFile: '/assets/app.js'
});
var appCss = compileSass(['styles'], 'app.scss', '/assets/app.css');
module.exports = mergeTrees([appJs, appCss, 'public']);
// See what object in memory is being created
App = Ember.Application.create({
LOG_ACTIVE_GENERATION: true,
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true,
LOG_VIEW_LOOKUPS: true,
modulePrefix: 'CP',
ready: function() {
// jQuery ready hook
}
});
{{log model}}
Not a loop like programmers might think. It is an "aggregate changes over time" mechanism. Used to batch, and order (or reorder) work in a way that is most efficient by scheduling work on specific queues. These queues have a priority, and are processed to completion in priority order.
The run loop is not constantly running!
Some things 'cost' more than others:
- DOM manipulation
- Layout calculation
- Your cool, but expensive algorithm
Ember.run.queues return an array of queue buckets like ["sync", "actions", "routerTransitions", "render", "afterRender", "destroy"]. We throw jobs into these buckets and they are prioritise to run.
autoRun not in test environment.
// Asynchronous
Ember.run(function() {
this.set();
this.set();
});
Ember.run.joinEmber.run.later- Use this instead ofsetTimeoutEmber.run.next- Next tick, considerscheduleEmber.run.schedule- Push to queueEmber.run.scheduleOnceEmber.run.debounceEmber.run.throttle
Usually user events that will trigger run loop.
Every ember has a default top-level ApplicationRoute with a first nested IndexRoute. Think of it this way, at your application.hbs template, you will have an {{outlet}} where the index.hbs can reside when the IndexRoute is being visited.
| Name | Object |
|---|---|
| Router | this.resource('tables') and App.TablesRoute |
| Controller | App.TablesController |
| Model | App.Table |
| View | App.TablesView |
| Template | tables |
The shareable part. Ember router presents a DSL that helps bridge user-readable URLs with an application-level state machine. As you enter or exit URLs, the router signals to routes that they should configure or cleanup.
If you have this.resource('tickets'), you will have 2 routes automatically like TicketsRoute and TicketsIndexRoute. The route name will be tickets.index.
Index routes provide the default route where another may be nested.
// Detect the '/' path and render the todos template
this.resource('todos', { path: '/' });
Action routes (I like verb):
- /login/one-time-password/authenticate
- /recover-username
// If your developing and when to change index to other module
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('pipelines');
}
});
State is collection of active objects in memory.
Your route prepare state, fetch model data from API and setup controller state. The controller state is where all state change happen (long-living State). The view presents the state.
App.SearchRoute = Ember.Route.extend({
model: function() { /* models for state */ },
setupController: function(controller, model) {
controller.set('content', model);
// other initial state
},
renderTemplate: function() {
this.render();
}
});
<button {{ action 'search' }}>Search</button>
Only ever one instance.
Router has a map to find our route.
Besides generating route classes, router also generates controllers and templates. The router wires them together when entering a route.
You'll spend most time on the route than any other Ember's parts. In Rails, you use Controller to load data, but in Ember, you do it at Route. Route loads data and assigns it to a controller using setupController(). Controller in Ember rarely has the job to load data!
When /tables is visited, Ember calls the App.TablesRoute object, which finds the list of tables and assigns it to the App.TablesController.
In current versions of Ember (1.10), when a route is entered, it builds a controller, associates a model with it, and hands it off to an (old-style) view for rendering. The view itself is invisible (Ember.View); you just write a template with the correct name.
With Ember 2.0, you still have route, but controller+template+view are unified into 1 component.
Routes manage state, including serialisation and de-serialisation.
// search-results makes a better class name than it does a URL
this.route('search-results', { path: 'search/:term' });
Route has a entrance or exit and the following are the lifecycle hooks (asynchronous or synchronous).
Route hooks:
beforeModel- (Async) intended for rejecting entrance to a route or redirecting away to another route (permission maybe)model- (Async) is expected to return a promise that will be set as the model presented to the templateafterModel- (Async) can reject proceeding into a route based on the content of the model or fetch additional dataactivate(Sync) is called when a route is first enteredsetupController- (Sync) set the model on the controllerrenderTemplate- (Sync)serialize- (Sync) formats an object or collection passed as the model into parameters for building the route's URL segmentdeactivate- (Sync) called when exiting a routewillTransitiondidTransition
Can we have more than one model in a route?
App.IndexRoute = Ember.Route.extend({
actions: {
didTransition: function() {
Ember.run.once(this, function() {
trackAnalytics(this.get('router.url'));
});
}
}
});
Asynchronous promises getJSON
App.IndexRoute = Ember.Route.extend({
model: function() {
return new Ember.RSVP.Promise(function(resolve, reject) {
$.getJSON('/tickets.json').then(function(data) {
Ember.run(null, resolve, data);
}, function(error) {
Ember.run(null, reject, error);
});
});
}
});
When you visit a route, the XXXIndexRoute is called first with the model hook. Based on the model, ember will automatically create an Array/Object controller.
Think of Controller or Route as the Application State.
One Page != One Controller. This is almost always wrong in Ember.js. Don't give a controller too many responsibilities. Break it up into tiny pieces of UI functionalities. Just introduce more controllers. They are free on client-side :)
App.ListsController = Ember.ArrayController.extend({
needs: ['snapshot'], // use more controllers
selected: -> {
this.get('model').findBy('selected')
}.property('[email protected]'),
hovering: -> {
this.get('model').findBy('hovering')
}.property('[email protected]')
});
Controller is not the place to LOAD data as in Rails. Instead make use of Route's hook and promises to load data.
Controller in Ember is just ViewModel. In Cocoa iOS, every View is backed by a Controller, which may or may not have some data.
- Model - All sessions. Represent long-term state that persists from session to session.
- Controller - Just this session
- Template/View - What is currently visible. Are transient, responsible for little or no state.
Ember's controllers are mediating controllers and route objects are coordinating controllers - Tom Dale on services
Controllers have 2 responsibilities in Ember:
- Manage transient state for sections of the running application. This involves setting properties on the controller, and having the controller handle some actions.
- Decorate models for presentation.
Delivers model data to views and templates.
Array and Object controllers manage a model property. They act as proxy to the model's attributes and methods, sending them along if asked.
In a single UI view, you will have many controllers at one go. They are all instantiated by Ember and stay on the screen for as long as the UI need it to. So 1 page does not mean you have only 1 controller.
3 types:
ControllerObjectControllerArrayController
Extend Ember.Evented for your controller if you want event bus.
How controller talk to view? Ember.View.views.XX
// Some validation example for controller. isValid state is available to you all the time
App.SearchController = Ember.Controller.extend({
actions: {
search: function() {
if (this.get('isValid')) {
this.transitionToRoute('search.index', keyword);
}
}
},
isValid: function() {
return !Ember.isEmpty(this.get('keyword'));
}.property('keyword'),
isNotValid: Ember.computed.not('isValid')
});
<button {{ action='search' }} {{ bind-attr disabled=isNotValid }}>Search</button>
Sortable mix-in.
An ArrayController could manage the knowledge about which item in an array is selected.
Controller is also the place where you hold UI temporary state like checkbox status, etc.
{{ view Ember.Checkbox checkedBinding="artistIsChecked" }}
The artistIsChecked resides at the controller and make the UI show or hide with ease.
Controllers can share their state with other controllers, and have parent-child relationships. Actions can bubble up through these relationships, making them a powerful and important part of how complex applications are structured.
To cause an action to keep bubbling even though it was handled, return true from the handler.
The itemController of an {{#each helper can access its parent via parentController to access maybe toggle, filter states, or search query etc.
You can also use needs which is good for placing currentUser information.
Always consider the tradeoffs inherent in coupling controllers to each other. Good alternatives to needs include use of controllerFor on routes to access controller instances and send messages, and the register/inject dependency injection of Ember containers.
See how itemController come about
<script type="text/x-handlebars" data-template-name="_map">
<div id="map"></div>
</script>
{{ partial 'map' }}
Why it took so long? Model (Route) vs Controller are two equally good place for Query Params? But now, it is the controller's job.
/?query=params
App.ArticlesController = Ember.ArrayController.extend({
queryParams: ['page'],
page: 1
});
Model-dependent state! - Alex Matchneer
What's the job of a controller anyway?
- Manage application state
- Wrap the model with additional information to present to the template (also application state)
Query params will be on the controller state.
Flows are just as important to good interfaces as individual screens are.
Directed graph, nodes, edges, etc
replaceWith
transitionTo
See http://www.youtube.com/watch?v=iFBOYMxDl40
Nesting in Ember.js is visual (rendering views inside views). It is not like in Rails, which is nested resources for scoped access.
this.resource('project', { path: '/projects/:projectId' }, function() {
this.route('story', { path: '/stories/:storyId' });
});
If you want to render into other named outlet, you can:
{{outlet toolbar}}
{{outlet inspector}}
export default Ember.Route.extend({
renderTemplate: function() {
this.render({outlet: 'inspector'});
}
});
CP
- Transforms an object property defined as a function into a value
- The function will only be called once and the returned value will be cached, so they are efficient
- Can specify properties that your computed property depends on
- The cached result will be recomputed if the dependencies are modified
Check out the source at computed.js
- Consider data as flowing in 2 different directions from a computed property
- A
.get()flows down, while a.set()flows up
Element attributes in views can be bound to objects' properties.
<button {{ bind-attr disabled=isNotValid }}>Search</button>
.property('names.[]') // bad?
.property('activeSquares.@each')
// Or use Ember.arrayComputed using Ember.computed.map
names: map('[email protected]', function(person) {
return person.get('name').toUpperCase();
})
@this!!!!!
For you Angular peeps, an Ember.js component is roughly equivalent to an E restricted, transcluded, isolate-scoped directive. - [https://twitter.com/tomdale/status/361288660240441344](Tom Dale)
Followed closely to Web Component standard.
If you just want some static HTML and pass in simple properties, you do not need to extend Ember.Component, but if you need to change the wrapped element, integrating with third-party library like D3, or handle actions, then you need to extend Ember.Component class to better manage things.
App.HeatMapComponent = Ember.Component.extend({
width: 900,
height: 280,
margin: { top: 50, right: 0, bottom: 100, left: 30 },
colors: [ '#2F0000', '#661201' ],
draw: function(data) {
// draw the heat map using D3
this.set('data', data);
var svg = d3.select('#' + this.get('elementId'));
},
didInsertElement: function() {
var data = this.get('controller.data.content');
// You can prepare the data here and do anything initial
// data massaging here
this.draw(data);
}
});
One of the major reasons an Ember.View exists is to translate browser events into application events. That's why it captures all browser events in functions like dragStart, click, etc. It's also why Ember.View emits application events to Controllers. Browser events come in, application events go out.
The Ember.View is the Ember object responsible for pushing the HTML into the DOM, updating the DOM as the bound object's values are modified, and then responding to user interaction such as clicks.
Views only manage DOM and rendering concerns and you don't typically need to create them.
Ember.View manages DOM lifecycle and events, but is not responsible for decorating models or managing state. For that you need a controller.
Views are normally quite temporary instances, and ill suited for keeping track of application state. Once a view is no longer displayed its Ember.View instance is destroyed, losing any state. So if you want application state, do it are controller!
App.MyView = Ember.View.extend({
didInsertElement: function() {
this.get('controller').on('startAnimation', this.startAnimation, this);
},
startAnimation: function() {
this.$().css(left: 200);
}
});
// Possible future for Ember.View?
class View extends HTMLElement {
hide() {
this.isVisible = false;
}
}
Separate row, cell into view.
App.RowView = Ember.View.extend({
templateName: 'row',
tagName: 'tr',
classNames: ['board-row']
});
App.CellView = Ember.View.extend({
templateName: 'cell',
tagName: 'td',
classNameBindings: ['color', 'piece'],
attributeBindings: 'draggable',
draggable: 'true',
dragOver: function(e) {
e.preventDefault();
},
dragLeave: function(e) {
e.preventDefault();
},
dragStart: function(e) {
e.dataTransfer.setData('text/text', 'data');
},
drop: function(e) {
this.get('controller').send('gameChanged');
}
});
// Create a new App.RowView for each row...
{{ #each rows }}
{{ view App.RowView }}
{{ /each }}
// And the RowView itself will in turn create a new CellView for each cell...
<tr class="board-row">
{{ #each this }}
{{ view App.CellView }}
{{ /each }}
</tr>
// If we use tagName and classNames, we can simplify it:
{{ #each this }}
{{ view App.CellView colorBinding=color pieceBinding=piece }}
{{ /each }}
- HTML-aware
- Context-specific helpers/bindings
- No more
bind-attr - Validate HTML
- No more metamorphs
- No more DOM pollution
- Use DOM fragment and deep clone
- String manipulations increase the pressure on the GC
- Kris Seldon talking about HTMLBars
- Commit - Add ability for templates to reuse morphs
{{#link-to 'articles' (query-params sort='ASC')}}
// s-expressions
{{capitalize (reverse foo)}}
{{reverse (capitalize foo)}}
// We will be able to write this
<a href="{{url}}">Click here</a>
// Not this
<a {{bind-attr href=url}}>Click here</a>
If you use Handlebars.SafeString, be sure to escape any user input with Handlebars.Utils.escapeExpression
- Liquid Fire: Animations and Transitions for Ember Apps
- ember-animation-demo
- ember-animate
- Liquid Fire
- Liquid Fire library - Using Velocity.js underlying
With Ember.View, we can use the hooks:
- Animate in using
willInsertElement/didInsertElement - Swap? Maybe
willClearRender?? - Animate out using
willDestroyElement parentViewDidChange?
No animation story in Ember, is it because need to wait for HTMLBars?
The places your data will go!
- Migration (From old MySQL CP to new Postgres CP)
- Search (ElasticSearch)
- Caching (Redis)
- Mobile (API endpoints)
- Transition for ember-data?
- wycats's app.js example
- EPF - Ember.js Persistence Foundation by GroupTalent
- Model maker
- ember-cli + ic-ajax
All you need to do is implement the find and findAll methods.
Model can be used for transient data (data we don't plan to store to a database) as well as persistence data.
Models are good for defining structured data.
Pre-render data server-side rather than calling 50+ Ajax on boot-time. Use server-side rendering.
- Torii - Best one to use?
- Ember.SimpleAuth
- ember-devise-simple-auth
- End-to-end security
- How Zendesk use ember-resource for authentication token? At the initializer?
- Ember add-on to track of Rails CSRF
- AI auth using Angular
- OAuth with Ember and Rails
- SOA in Rails
- OAuth for Rails
- OAuth provider
- Minimal API Authentication on Rails
- Authentication options in Rails
App.SecretArticlesRoute = Ember.Route.extend({
beforeModel: function() {
if (!this.controllerFor('auth').get('isLoggedIn')) {
this.transitionTo('login');
}
}
});
Access-Control-Allow-Origin with CORS (Cross Origin Resource Sharing). Use rack-cors gem.
By default, jQuery does not send cookies with Ajax requests, so session-based authentication may not be working! withCredentials to the rescue!
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
options.xhrFields = { withCredentials: true };
});
// OAuth?
App = Ember.Application.create({
ready: function() {
var store = App.__container__.lookup('store:main');
var id = 'me?_=' + (new Date()).getTime();
store.find('user', id).then(function(user) {
App.currentUser = user;
});
}
});
// Custom 401 error handler
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 401) {
var newLocation = 'https://host.com/auth?return="xxx"';
newLocation += encodeURIComponent(document.location.toString());
document.location = newLocation;
} else {
return error;
}
}
});
// Vine's CurrentUserController
sessionChanged: (function() {
if (this.get('session.isAuthenticated')) {
this.userService.currentUser().then(function(user) {
this.set('content', user);
}.bind(this)).catch(function(err) {
// handle session expiry, etc.
});
}
}).observes('session.userId').on('init');
TimelineIndexController = Ember.ArrayController.extend({
itemController: 'post'
});
// POST /api/token
{
grant_type: "password", username: "[email protected]", password: "X"
}
// response 200
{
access_token: "xxSSddsSaa2s",
token_type: "Bearer",
expires_in: 30.days,
user_id: 1
}
GET /api/protected
Headers: "Authorization: Bearer #{acces_token}"
- Containers and Dependency Injection in Ember by Matthew Beale
- How to call A from B in Ember
- Containers and DI by @mixonic
- Dependency Injection in Ember.js - First Steps
The entire Ember framework go through an object called the resolver. The resolver is the part of Ember's dependency injection system that is responsible for determining naming conventions.
The container organises new building blocks.
var container = new Ember.Container();
container.register('workerPool:main', workerPool);
container.lookup('workerPool:main'); // Instance of workerPool
Don't ever use App.__container__
- Hidden features of the
#eachhelper - Ember Arrays and 'observable' state
- Observers and object initialization
- CP is lazy and do not trigger observers
"What the hell" API driven design.
In some places where you may imagine {{render is the best solution, a component could be a better and more re-usable tool.
See Instructure's component examples
MUST include hyphen in the component name!
App.LeafletMapComponent = Ember.Component.extend({
attributeBindings: ['style'],
width: '600px',
height: '400px',
style: function() {
return [
'width:' + this.get('width'),
'height:' + this.get('height')\
].join(';');
}.property('width', 'height'),
didInsertElement: function() {
var map = L.map(this.get('element'));
this.set('map', map);
map.setView([0, 0], 1);
L.tileLayer('http://{s}.title.osm.org/{z}/{x}/{y}.png', { attribution: '©' }).addTo(map);
map.on('move', this.mapDidMove, this);
},
mapDidMove: function() {
Ember.run(this, function() {
var map = this.get('map'),
centre = map.getCenter(),
zoom = map.getZoom();
this.setProperties({
latitude: center.lat,
longitude: center.lng,
zoom: zoom
});
});
},
willRemoveElement: function() {
var map = this.get('map');
if (map) map.remove();
}
});
Components differ from View in the way they isolate behavior and data.
There is no bubbling to the route. Actions are sent only to the component itself.
Ember.testing = true;
App.deferReadiness();
App.Router.reopen({location: 'none'});
- Understand how containers work and injections work.
- Lookup objects as needed from containers. You'll probably want to include some test helpers for this.
- Wire together objects based upon their "needs".
- SparkCasts
- EmpireJS Videos
- Ember Watch: Screencasts
- Hashrocket's Introduction to Ember.js
- Bosten Ember - April 2014
- Testing your Ember applications
- Building an Ember application
- Building web applications with Ember.js and Rails
- How to call A from B in Ember?
- The Promise Land by Stefan Penner
- Ember Sherpa YouTube channel
- Ember NYC YouTube channel
- Modern build workflows with Broccoli
- Modern build workflows with Broccoli @Scotland.js
- Building URL-driven web-apps with Ember.js
- Ember.js NYC - [Apr 14, Mar 14, Feb 14, Jan 14]
- ember-cloaking
- ember-responsive
- ember-pretenderify
What to track?
- Page views:
link-to,this.transitionTo(),this.replaceWith(), URL - Actions:
{{action}},this.send(),this.triggerAction(),this.sendAction()
[Extending Ember with Analytics](http://www.youtube.com/watch? v=G0de3zNEkC4)
App.ApplicationRoute = Ember.Route.extend({
actions: {
didTransition: function() {
Ember.run.once(this, function() {
ga('send', 'page view', this.router.get('url'));
});
}
}
});
- Be aware promises swallowing your rejection. Router promises may swallow.
- jQuery throws exception on certain places, so be careful when doing TDD.
// Error sub-state
{{message}}
{{stack}}
linkToislink-tonow, see here
