Controlling Route Traversal

50
Controlling Route Traversal With Flows nathanhammond

description

Controlling Route Traversal

Transcript of Controlling Route Traversal

Controlling Route Traversal With Flows

nathanhammond

URLs/accounts

/accounts/12

/transfers

/transfers/78

/statements

/statements/august

/tour

/contact

/login

/login/one-time-password/setup

/login/one-time-password/select-delivery-method

/login/one-time-password/authenticate

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

/login/electronic-consent

/login/terms-of-service-consent

/login/reset-password /login/complete

/logout

/recover-username

/recover-password

Resources/accounts

/accounts/12

/transfers

/transfers/78

/statements

/statements/august

/tour

/contact

/login

/login/one-time-password/setup

/login/one-time-password/select-delivery-method

/login/one-time-password/authenticate

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

/login/electronic-consent

/login/terms-of-service-consent

/login/reset-password /login/complete

/logout

/recover-username

/recover-password

Actions/accounts

/accounts/12

/transfers

/transfers/78

/statements

/statements/august

/tour

/contact

/login

/login/one-time-password/setup

/login/one-time-password/select-delivery-method

/login/one-time-password/authenticate

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

/login/electronic-consent

/login/terms-of-service-consent

/login/reset-password /login/complete

/logout

/recover-username

/recover-password

Flows?login otp.

authenticate accounts

ComparisonResource Action Flow

Route name is a noun. Route name is a verb. Route name is a verb.

Appears in the history stack. Appears in the history stack. Depends. One or more entries in the history stack.

Doesn't appreciably change application state. Changes application state. Flow state incrementally or

transactionally committed.

Newly rendered page. Flash messaging and/or the newly created resource.

Next step, results page, or the finalized resource.

Reload the model state. Reload a model (if any) and possibly the controller state.

Possibly drop the user back into the state machine.

No reset necessary after view. Reset typical after completion. 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

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

Description Steps

Already logged in. login.index accounts.index

Login is clean. login.index login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Invalid credentials. login.index login.index!flash message

Dismiss message on keypress.

Login, secondary challenge, no OTP method.

login.index contact

Login, secondary challenge, single OTP method.

login.index one-time-password.authenticate

one-time-password.register-device

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Login, secondary challenge, multiple OTP methods.

login.index one-time-password.select-delivery-method

one-time-password.authenticate

one-time-password.register-device

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Login, needs to set up OTP. login.index one-time-password.setup

one-time-password.register-device

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Login using a temporary password. login.index login.reset-password login.reset-password-complete

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Login using a temporary password, needs to set up OTP

login.index login.reset-password login.reset-password-complete

one-time-password.setup

one-time-password.register-device

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Login using a temporary password, secondary challenge, no OTP method.

login.index contact

Login using a temporary password, secondary challenge, single OTP method.

login.index one-time-password.authenticate

one-time-password.register-device

login.reset-password login.reset-password-complete

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Login using a temporary password, secondary challenge, multiple OTP methods.

login.index one-time-password.select-delivery-method

one-time-password.authenticate

one-time-password.register-device

login.reset-password login.reset-password-complete

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Recover Username recover-username login.index!flash message

Recover Password, no OTP method. recover-password contact

Recover Password, single OTP method.

recover-password one-time-password.authenticate

login.reset-password login.reset-password-complete

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Recover Password, multiple OTP methods.

recover-password one-time-password.select-delivery-method

one-time-password.authenticate

login.reset-password login.reset-password-complete

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Login, forced OTP review, login.index one-time-password.setup?edit=true

one-time-password.register-device

login.electronic-consent?

login.terms-of-service-consent?

tour? accounts.index

Step 3: Convert Paths Into Node Graph

login

otp.select-delivery-method

otp.setup otp. authenticate

otp. register-device

accounts - or -

deep linkelectronic-

consentterms-of-service-consent

tourcomplete

Step 4: Describe Edge Traversal

login

otp.select-delivery-method

otp.setup otp. authenticate

otp. register-device

accounts - or -

deep linkelectronic-

consentterms-of-service-consent

tourcomplete

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

Step 5: Identify Backwards Traversals

login

otp.select-delivery-method

otp.setup otp. authenticate

otp. register-device

accounts - or -

deep linkelectronic-

consentterms-of-service-consent

tourcomplete

Step 6: Done!

login

otp.select-delivery-method

otp.setup otp. authenticate

otp. register-device

accounts - or -

deep linkelectronic-

consentterms-of-service-consent

tourcomplete

reset-password

recover-password

reset-password-complete

A Few Important Things

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

#1:

#2: Don't break the web.

#3: Ember.Router is awesome.

A Quick Demo

Playing Alonghttp://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 Approachgit 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 Statevar 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: replaceWithIt'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 ApproachThe "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 Flowvar 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-flowshttps://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/