You are on page 1of 50

Controlling Route Traversal

With Flows
nathanhammond

/login/one-time-password/authenticate
/login/one-time-password/select-delivery-method
/statements/august
/transfers/78

/login

/login/electronic-consent
/contact

/login/reset-password

URLs

/login/one-time-password/setup

/login/terms-of-service-consent

/accounts/12

/tour

/login/complete
/recover-username
/transfers
/accounts

/statements
/logout

/recover-password

/login/one-time-password/register-device

/login/one-time-password/authenticate
/login/one-time-password/select-delivery-method
/statements/august
/transfers/78

/login/reset-password

/login

/tour

/login/complete
/recover-username

Resources
/login/electronic-consent
/transfers
/contact

/login/one-time-password/setup

/login/terms-of-service-consent

/accounts/12

/accounts

/statements
/logout

/recover-password

/login/one-time-password/register-device

/login/one-time-password/authenticate
/login/one-time-password/select-delivery-method /tour
/statements/august
/transfers/78

/login

/login/electronic-consent
/contact

/login/reset-password

Actions

/login/one-time-password/setup

/login/complete
/recover-username
/transfers
/accounts

/statements
/logout

/login/terms-of-service-consent /accounts/12 /recover-password


/login/one-time-password/register-device

Flows?
login

otp.
authenticate

accounts

Comparison
Resource

Action

Flow

Route name is a noun.

Route name is a verb.

Appears in the history stack.

Appears in the history stack.

Doesn't appreciably change


application state.

Changes application state.

Route name is a verb.


Depends. One or more entries in
the history stack.
Flow state incrementally or
transactionally committed.
Next step, results page, or the
finalized resource.
Possibly drop the user back into
the state machine.
Reset typical after completion.

Newly rendered page.


Reload the model state.
No reset necessary after view.

Flash messaging and/or the


newly created resource.
Reload a model (if any) and
possibly the controller state.
Reset typical after completion.

Designing a Flow

Flow Spaghetti

Step 1:
Inventory Your Routes

Router.map(function() {
this.resource('authenticated', { path: '/' }, function() {
this.resource('accounts', { path: '/' }, function() {
this.route('details', { path : 'account/:account_id' });
});
this.resource('transfers');
this.resource('statements', function() {});
this.route('tour');
});
!

this.resource('login', function() {
this.resource('one-time-password', function() {
this.route('setup');
this.route('select-delivery-method');
this.route('authenticate');
this.route('register-device');
});
this.route('electronic-consent');
this.route('terms-of-service-consent');
this.route('reset-password');
this.route('complete');
});
!

this.route('logout');
this.route('recover-username');
this.route('recover-password');
this.route('contact');
});

https://github.com/alexdiliberto/emberconf-2014-demo/blob/master/app/router.js

Step 2:
List Linear Paths

Description

Steps

Already logged in.

login.index

accounts.index

Login is clean.

login.index

login.electronicconsent?

login.terms-of-serviceconsent?

Invalid credentials.

login.index

login.index!
flash message

Dismiss message on
keypress.

tour?

accounts.index

login.electronicconsent?

login.terms-of-serviceconsent?

tour?

accounts.index

Login, secondary challenge, no OTP login.index


method.

contact

Login, secondary challenge, single


OTP method.

login.index

one-timeone-timepassword.authenticate password.registerdevice

Login, secondary challenge,


multiple OTP methods.

login.index

one-timepassword.selectdelivery-method

one-timeone-timepassword.authenticate password.registerdevice

login.electronicconsent?

login.terms-of-serviceconsent?

tour?

Login, needs to set up OTP.

login.index

one-timepassword.setup

one-timepassword.registerdevice

login.electronicconsent?

login.terms-of-serviceconsent?

tour?

accounts.index

Login using a temporary password. login.index

login.reset-password

login.reset-passwordcomplete

login.electronicconsent?

login.terms-of-serviceconsent?

tour?

accounts.index

Login using a temporary password, login.index


needs to set up OTP

login.reset-password

login.reset-passwordcomplete

one-timepassword.setup

one-timepassword.registerdevice

login.electronicconsent?

login.terms-of-serviceconsent?

tour?

accounts.index

Login using a temporary password, login.index


secondary challenge, no OTP
method.

contact

Login using a temporary password, login.index


secondary challenge, single OTP
method.

one-timeone-timepassword.authenticate password.registerdevice

login.reset-password

login.reset-passwordcomplete

login.electronicconsent?

login.terms-of-serviceconsent?

tour?

accounts.index

Login using a temporary password, login.index


secondary challenge, multiple OTP
methods.

one-timepassword.selectdelivery-method

login.reset-password

login.reset-passwordcomplete

login.electronicconsent?

login.terms-of-serviceconsent?

tour?

Recover Username

login.index!
flash message

login.electronicconsent?

login.terms-of-serviceconsent?

tour?

accounts.index
tour?

recover-username

one-timeone-timepassword.authenticate password.registerdevice

Recover Password, no OTP method. recover-password

contact

Recover Password, single OTP


method.

recover-password

one-timelogin.reset-password
password.authenticate

Recover Password, multiple OTP


methods.

recover-password

one-timepassword.selectdelivery-method

one-timelogin.reset-password
password.authenticate

login.reset-passwordcomplete

login.electronicconsent?

login.terms-of-serviceconsent?

Login, forced OTP review,

login.index

one-timepassword.setup?
edit=true

one-timepassword.registerdevice

login.terms-of-serviceconsent?

tour?

accounts.index

login.reset-passwordcomplete

login.electronicconsent?

accounts.index

accounts.index

accounts.index

https://docs.google.com/spreadsheet/ccc?key=0ApnSRONV3LTVdGJuLUlnOGxzY1FadGZHN050ZVJXcWc

Step 3:
Convert Paths Into Node Graph

login
otp.setup

otp.selectdeliverymethod

electronicconsent

terms-ofserviceconsent

otp.
authenticate

complete

otp.
registerdevice

tour

accounts
- or deep link

Step 4:
Describe Edge Traversal

login
otp.setup

otp.selectdeliverymethod

otp.
authenticate

otp.
registerdevice

{
isIdentified: true,
isAuthenticated: false,
hasOneDeliveryMethod:
deliveryMethods.length,
}
electronicconsent

terms-ofserviceconsent

complete

tour

accounts
- or deep link

Step 5:
Identify Backwards Traversals

login
otp.setup

otp.selectdeliverymethod

electronicconsent

terms-ofserviceconsent

otp.
authenticate

complete

otp.
registerdevice

tour

accounts
- or deep link

Step 6:
Done!

resetpassword

login
otp.setup

otp.selectdeliverymethod

otp.
authenticate

otp.
registerdevice
resetpasswordcomplete

electronicconsent

recoverpassword

terms-ofserviceconsent

complete

tour

accounts
- or deep link

A Few Important Things

#1:

http://www.flickr.com/photos/joi/8224269021

#2:
Don't break the web.

#3:
Ember.Router is awesome.

A Quick Demo

Playing Along
http://alexdiliberto.com/emberconf-2014-demo
https://github.com/alexdiliberto/emberconf-2014-demo

http://alexdiliberto.com/emberconf-2014-demo/

// Direct Login
ic.ajax.defineFixture('/session', {
response: {
/* Successfully identified username and password. */
isIdentified: true,
!

/* Related to one-time-passcode. */
willSetupOTP: false,
isAuthenticated: true,
!

hasElectronicConsent: true,
hasTermsOfServiceConsent: true,
showTour: false

},
textStatus: 'success'
});

// One Time Password with selection.


ic.ajax.defineFixture('/session', {
response: {
/* Successfully identified username and password. */
isIdentified: true,
!

/* Related to one-time-passcode. */
willSetupOTP: false,
isAuthenticated: false,
!

hasElectronicConsent: true,
hasTermsOfServiceConsent: true,
showTour: false

},
textStatus: 'success'
});

Coding a Flow

A Naive Approach
git clone https://github.com/alexdiliberto/emberconf-2014-demo
git checkout tags/naive

General Strategy

Load the session state from a route-global injection.

Reset the controller on setupController.

Delegate identification of where to go next to the current route.

Traverse the longest possible path through the application so that


every route can identify whether or not they should be stopped at.

Inject The Session State


var session = Ember.Object.extend();
Ember.Application.initializer({
name: 'session',
initialize: function(container, app) {
app.register('session:main', session);
app.inject('route', 'session', 'session:main');
}
});

beforeModel
beforeModel: function() {
if (this.get('session.isAuthenticated')) {
this.replaceWith('one-time-password.register-device');
}
}

Tangent: replaceWith
It's awesome. Use it.

{{action}}
actions: {
authenticate: function() {
ic.ajax.raw('/authenticate').then(function(result) {
if (result.response.isAuthenticated) {
this.set('session.isAuthenticated', true);
this.replaceWith('one-time-password.register-device');
}
});
}
}

An Improved Approach
The "Using Flows" part of the presentation.

General Strategy

Define all progression in a separate location.

Load the Flow and its state from a route-global injection.

Rely on Alex Matchneer's new primitive for repopulating controller state?

Delegate identification of where to go next to the Flow itself.

Call back into the Flow to progress.

Your flow tracks which edges you've traversed in case you need that
information in your application.

Definitions
LoginFlow.addEdge({
from: 'login.index',
to: 'accounts.index',
weight: 1,
conditions: ['isIdentified', 'isAuthenticated']
});

Inject The Login Flow


var login = Ember.Object.extend();
Ember.Application.initializer({
name: 'login',
initialize: function(container, app) {
app.register('flow:login', login);
app.inject('route', 'login', 'flow:login');
}
});

beforeModel
beforeModel: function() {
// Looks up the current flow.
// Identifies where the user should be.
this.get('flow').check();
}

{{action}}
actions: {
authenticate: function() {
var Flow = this.get('flow');
!

ic.ajax.raw('/authenticate').then(function(result) {
if (result.response.isAuthenticated) {
Flow.set('isAuthenticated', true);
Flow.progress();
}
});
}
}

ember-flows
https://github.com/nathanhammond/ember-flows

Resources

https://signalvnoise.com/posts/1926-a-shorthand-for-designing-ui-flows

https://docs.google.com/spreadsheet/ccc?
key=0ApnSRONV3LTVdGJuLUlnOGxzY1FadGZHN050ZVJXcWc

http://alexdiliberto.com/emberconf-2014-demo

https://github.com/alexdiliberto/emberconf-2014-demo

https://github.com/nathanhammond/ember-flows

https://code.google.com/p/libphonenumber/