Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ember: when the lights go on
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Aaron Chambers
September 24, 2014
Programming
0
200
ember: when the lights go on
My BSkyB Engineering Offsite presentation about our journey with Ember and beyond
Aaron Chambers
September 24, 2014
Tweet
Share
More Decks by Aaron Chambers
See All by Aaron Chambers
{{should-avoid this.renderModifiers butHow=true}}
achambers
0
190
one command to deploy them all
achambers
0
65
The Legend of Zelda: A {{link-to}} the past
achambers
0
66
ember-cli-deploy
achambers
0
91
git-branching
achambers
0
67
Other Decks in Programming
See All in Programming
CSC307 Lecture 07
javiergs
PRO
0
540
例外処理とどう使い分ける?Result型を使ったエラー設計 #burikaigi
kajitack
16
5.9k
組織で育むオブザーバビリティ
ryota_hnk
0
160
疑似コードによるプロンプト記述、どのくらい正確に実行される?
kokuyouwind
0
370
Rust 製のコードエディタ “Zed” を使ってみた
nearme_tech
PRO
0
110
CSC307 Lecture 03
javiergs
PRO
1
490
Grafana:建立系統全知視角的捷徑
blueswen
0
320
AIエージェントの設計で注意するべきポイント6選
har1101
7
3.4k
20260127_試行錯誤の結晶を1冊に。著者が解説 先輩データサイエンティストからの指南書 / author's_commentary_ds_instructions_guide
nash_efp
0
830
AI によるインシデント初動調査の自動化を行う AI インシデントコマンダーを作った話
azukiazusa1
1
650
プロダクトオーナーから見たSOC2 _SOC2ゆるミートアップ#2
kekekenta
0
180
余白を設計しフロントエンド開発を 加速させる
tsukuha
7
2.1k
Featured
See All Featured
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
350
Leo the Paperboy
mayatellez
4
1.4k
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
The Pragmatic Product Professional
lauravandoore
37
7.1k
Making Projects Easy
brettharned
120
6.6k
Thoughts on Productivity
jonyablonski
74
5k
Navigating Weather and Climate Data
rabernat
0
89
The Curse of the Amulet
leimatthew05
1
8.1k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
230
The Language of Interfaces
destraynor
162
26k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
7.9k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Transcript
ember:&when&the&lights&go&on @grandazz github.com/achambers
None
how$ember$saved$my$life @grandazz github.com/achambers
how$ember$saved$my$life @grandazz github.com/achambers
ember:&when&the&lights&go&on @grandazz github.com/achambers
Wenguins
None
Noah%update
None
this%is%not an#ember#is#be(er#than#everything#else presenta(on
browser'dev'boiled'down (shamelessley)borrowed)from)@ryanflorence) interac(ng*with*data*stores rendering'data'to'ui responding*to*user*interac/on router&&&urls project(stuff:(file(organisa3on,(build(tools(etc
None
our$journey$with ember
lightbulb!moments
we#didn't#just#jump#in
we#needed#to validate the$switch
None
None
Service'Status
None
// app/assets/javascripts/application.js function bindViewMoreDetailsLinks() { $('.more-details').hide(); $('.view-more').click(function(e) { e.preventDefault(); var
viewMoreLink = $(this); var moreDetails = viewMoreLink.parents('.issue').find('.more-details'); if (moreDetails.is(':visible')) { moreDetails.hide(); viewMoreLink.text('More details'); } else { moreDetails.show(); viewMoreLink.text('Less details'); } viewMoreLink.toggleClass('opened'); }); }
// app/assets/javascripts/application.js function toggleElementsForEditors() { $.ajax({ url: '/sessions/current', dataType: 'json',
success: function(data) { var loggedInStatus = data.logged_in; toggleNewIssueLink(loggedInStatus); toggleHeader(loggedInStatus); toggleAdditionalLinks(loggedInStatus); toggleSidebar(loggedInStatus); toggleFooter(loggedInStatus); toggleEditIssueLinks(loggedInStatus); toggleResolveIssueLinks(loggedInStatus); } }); }
// app/assets/javascripts/application.js function toggleHeader(loggedInStatus) { $('#skycom-masthead-wrapper').toggleClass('hidden', loggedInStatus); } function toggleAdditionalLinks(loggedInStatus)
{ $('.additional-links').toggleClass('hidden', loggedInStatus); } function toggleSidebar(loggedInStatus) { $('.sidebar').toggleClass('hidden', loggedInStatus); } function toggleFooter(loggedInStatus) { $('#skycom-footer-wrapper').toggleClass('hidden', loggedInStatus); } function toggleEditIssueLinks(loggedInStatus) { $('.btn.edit-issue').toggleClass('hidden', !loggedInStatus); } function toggleResolveIssueLinks(loggedInStatus) { $('.btn.resolve-issue').toggleClass('hidden', !loggedInStatus); }
None
data$based$ui$manipula.on
// app/views/issues/index.html.haml function microfilterCheck() { var deferred = $.Deferred(); $.getJSON('/sessions/current',
function(json) { if (json.logged_in) { deferred.resolve() } else { Hub.runMicrofilterTest() .done(function() { deferred.reject(); }) .fail(function() { deferred.resolve(); }); } }); return deferred; }
// app/views/issues/index.html.haml function restrictionCheck() { var deferred = $.Deferred(); $.getJSON('/customers/current',
function(json) { if (json.customer.has_debt_restriction) { deferred.reject('debt'); } else if (json.customer.has_high_spend_restriction) { deferred.reject('spend'); } else { deferred.resolve(); } }, function() { deferred.resolve(); }); return deferred; }
// app/views/issues/index.html.haml $(function() { var microfilterDeferred = microfilterCheck() var restrictionDeferred
= restrictionCheck() $.when(microfilterDeferred, restrictionDeferred).always(function() { var args = Array.prototype.slice.call(arguments); if (restrictionDeferred.state() == 'rejected') { if (args.indexOf('spend') !== -1) { $('.high-spend-restriction-alert').removeClass('hidden'); } else if (args.indexOf('debt') !== -1) { $('.debt-restriction-alert').removeClass('hidden'); } } else if (microfilterDeferred.state() == 'rejected') { $('.microfilter-alert').removeClass('hidden'); } showIssues(); }); });
then%the%very%next%commit...
// app/views/issues/index.html.haml $(function() { IssuesIndexController.init(); });
None
controllers
// app/assets/javasctipts/User.js var User = { isAnEditor: function() { var
deferred = $.Deferred(); $.ajax({ dataType: 'json', url: '/sessions/current', success: function(data) { deferred.resolve(data.logged_in); }, error: function() { deferred.reject(); } }); return deferred.promise(); } }
// app/assets/javascripts/Diagnostics.js var Diagnostics = { run: function() { //run
some diagnostics goodness } } // app/assets/javascripts/CBS.js var CBS = { run: function() { //run some monstrosity of a service, //return a massive payload we don't need //and take forever to do so } }
None
models & interac(ng*with*data*stores
// app/assets/javascripts/views/IssueView.js (function() { window.IssuesView = { render: function() {
this._showIssues(); this._toggleStatusMessage(); } // ...snip... }; })(); // app/assets/javascripts/views/DebtAlertView.js (function() { window.DebtAlertView = { render: function() { this._showAlert(); this._hideProducts(); this._hideSidebar(); this._informScreenReader(); } // ...snip... }; })();
None
views
{!-- app/assets/javascripts/templates/outages.hbs --}} {{#each outages}} <article class="issue"> <h3>{{description}}</h3> <p> <span
class="label">Date issue was reported</span> </br> <strong>{{startedOn}}</strong> </p> </article> {{/each}}
None
templa'ng
we#started#to#build#our#own#framework
we#started#to#build#our#own#framework BADLY
Use$a$framework,$or$don't.$Either$ way,$you$will. —"Someone"Awesome
conven&on'over'configura&on this.resource('posts', { path: '/posts' }) PostsRoute PostsController PostsView posts.hbs
no#more#globals var Diagnostics = { run: function() { // do
stuff } }; window.IssuesIndexController = { init: function() { // do stuff; } };
no#more#globals ServiceStatus.GoIssuesController = Em.ArrayController.extend({ status: function() { //do stuff }.property('hasProblem')
}); ServiceStatus.Outage = DS.Model.extend({ //attributes here }); ServiceStatus.SsAccordionComponent = Em.Component.extend({ collapsed: false, //other properties here });
data$binding // models/tv_issue.js ServiceStatus.TvIssue = DS.Model.extend( name: DS.attr('string') ); //
controllers.tv_issues.js ServiceStatus.TvIssuesController = Em.ArrayController.extend({ }); <!-- templates/tv_issues.hbs --> <ul> <#each> <li>{{name}}</li> </each> </ul>
lightbulb(moments didn't&stop there
None
None
leave%your%assump-ons at#the#door
None
None
None
None
None
None
None
don't&fight&the&framework
If#something#feels#harder#than#it# should#be,#it#probably#is.#You're# probably#doing#it#wrong. —"Someone"Awesome
None
None
ui#error#logging
ui#error#logging Ember.onerror = handleError; Ember.RSVP.on('error', handleError); function handleError(error) { Em.$.ajax('/error',
{ type: 'POST', data: { stack: error.stack } }); }
None
None
build&tools
ember%cli npm install -g ember-cli
ember%cli file$organisa,on ember new my-project - /app - /components -
/controllers - /helpers - /models - /routes - /styles - /templates - /views - app.js - index.html - router.js - /config - /public - /tests
ember%cli es6$modules
ember%cli tes$ng
ember%cli asset%pipeline
ember%cli generators
ember%cli h"p$mock)server
ember%cli addons
None
None
separa&on)of ui#and#api
clearer&separa)on&between&data and$user$interac,on
think¬&of&your&api&in&terms&of&ui& interac2ons,&but&in&terms&of&data& and&rela2onships
your%app%is%a client'of'your'api
None
None
deployment
deploying*to*heroku and$serving$from$rails is#cool#and#all
but$we've$been$doing$that$for a"long"'me
we#can#do be#er
None
None
index.html app#1234.js app#5678.css
None
lets%push%this one$step$further
None
None
deployment*becomes*super*quick
just%push%some%sta,c%assets%to%S3 and$push$index.html$into$redis
not$to$men'on$cu*ng$over
None
None
pact%file%tes*ng
how$do$we$test$that$changes to#the#api
how$do$we$test$that$changes to#the#api don't&break&the&ui?
pact%(noun) A%formal%agreement%between% individuals%or%par8es. —%The%Google
the$rela(onship$of$the$web$app$consists$of: the$consumer$(ui) the$provider$(api)
Step%1%'%Define%the%consumer%expecta4ons
Step%2%'%Verify%expecta0ons%on%provider
imagine'if CBS verified'their'services'against'our'pact'files every%&me%they%released?
None
Thats&a&wrap
@grandazz github.com/achambers
any$ques)ons? @grandazz github.com/achambers
thankyou