Slide 1

Slide 1 text

progressive enhancement for JS apps Garann Means @garannm

Slide 2

Slide 2 text

why this talk?

Slide 3

Slide 3 text

a little case study tons and tons of JS real-time collaborative offline capabilities* fallback: a textarea

Slide 4

Slide 4 text

the fix: progressive enhancement

Slide 5

Slide 5 text

2003-2008

Slide 6

Slide 6 text

2003-2008 “let’s use JS for everything!”

Slide 7

Slide 7 text

2003-2008 “let’s use JS for everything!” “what, people have JS turned off?”

Slide 8

Slide 8 text

2003-2008 “let’s use JS for everything!” “what, people have JS turned off?” “what, browser support is inconsistent?”

Slide 9

Slide 9 text

2003-2008 “let’s use JS for everything!” “what, people have JS turned off?” “what, browser support is inconsistent?” “who cares, our customers are cutting edge”

Slide 10

Slide 10 text

2003-2008 “let’s use JS for everything!” “what, people have JS turned off?” “what, browser support is inconsistent?” “who cares, our customers are cutting edge” from yesterday: “if you’re seeing a broken experience, your environment is broken”

Slide 11

Slide 11 text

2003-2008 “let’s use JS for everything!” “what, people have JS turned off?” “what, browser support is inconsistent?” “who cares, our customers are cutting edge” from yesterday: “if you’re seeing a broken experience, your environment is broken” “oh weird our business failed”

Slide 12

Slide 12 text

progressive enhancement: deliver a usable baseline experience offer an upgraded experience to more powerful browsers

Slide 13

Slide 13 text

vs. graceful degradation code for the most powerful clients problem? deliver a lesser experience

Slide 14

Slide 14 text

why’d we switch? degradation is harder to plan accurately degradation makes things even slower for less powerful clients hope for the best, plan for the worst

Slide 15

Slide 15 text

this is a best practice on the front-end

Slide 16

Slide 16 text

JS apps

Slide 17

Slide 17 text

JS apps “oh, but this isn’t for people who don’t have JavaScript”

Slide 18

Slide 18 text

JS apps “oh, but this isn’t for people who don’t have JavaScript” “..or people returning to the site”

Slide 19

Slide 19 text

JS apps “oh, but this isn’t for people who don’t have JavaScript” “..or people returning to the site” “..or people going through a tunnel on a train”

Slide 20

Slide 20 text

*ahem*

Slide 21

Slide 21 text

have we learned nothing? obviously we want the best experience possible this is the internet; “possible” changes brittle expectations of our users means a worse experience for everyone

Slide 22

Slide 22 text

what to do with that thinking?

Slide 23

Slide 23 text

a method build servers as if every client is text-only build clients as if the internet will disappear tomorrow expect people not to use the app the way you expected

Slide 24

Slide 24 text

some simple principles state is shared between client and server the client takes responsibility for state offline and attempts to sync when it comes online actions, by default, occur on both client and server

Slide 25

Slide 25 text

“computer, enhance!”

Slide 26

Slide 26 text

“computer, enhance!” is JS available? great, run the client-side app

Slide 27

Slide 27 text

“computer, enhance!” is JS available? great, run the client-side app are we online? great, send updates to the server

Slide 28

Slide 28 text

“computer, enhance!” is JS available? great, run the client-side app are we online? great, send updates to the server client and server aren’t in sync? no problem, put them in sync

Slide 29

Slide 29 text

sharing state is more than just sharing state

Slide 30

Slide 30 text

this is a state $( “#form-step-2” ).show();

Slide 31

Slide 31 text

(this is a shared state) body.registration-step-2 #form-step-2 { display: block; }

Slide 32

Slide 32 text

this one isn’t being shared $( “#form-step-2” ).show();

Slide 33

Slide 33 text

state runs the app interface changes fetching data app logic updates the state state changes trigger everything else

Slide 34

Slide 34 text

we can still do this $( “#form-step-2” ).show();

Slide 35

Slide 35 text

it just works differently $( “#form-step-2” )[ registration_step == 2 ? “show” : “hide” ]();

Slide 36

Slide 36 text

protip: CSS is an observer by default

Slide 37

Slide 37 text

same on the server app.get( “/register/:step?”, function( req, res ) { var step = req.params.step || registration_step; res.render( “reg_step_” + step, registration_data ); });

Slide 38

Slide 38 text

question: where is registration_step set?

Slide 39

Slide 39 text

glad you asked! updates about state are separate from updates as a result of state data updates are chunked state updates are instant (if possible)

Slide 40

Slide 40 text

separate state updates make syncing easier

Slide 41

Slide 41 text

let’s say our user goes offline > my_app.state.registration_step 1

Slide 42

Slide 42 text

then clicks submit $( “#form-step-1” ).on( “submit”, function(){ set_registration_step( 2 ); return false; });

Slide 43

Slide 43 text

it ’s fine > my_app.state.registration_step 2

Slide 44

Slide 44 text

everything handled function set_registration_step( step ) { my_app.state.registration_step = step; update_state( “registration_step_” + step ); var data = get_data( step - 1 ); save_data( ( step - 1 ), data ); $( “form-step-” + step ).show(); }

Slide 45

Slide 45 text

and stored function save_data( step, data ) { db.put( data, “registration_” + step, function( err, response ) { if ( err ) { show_error( “Data not saved!” ); } }); }

Slide 46

Slide 46 text

note: plan your storage small apps may be able to be stored entirely only the current workflow in large apps may be able to be stored the more changes happen offline, the less room for potentially used data

Slide 47

Slide 47 text

if you accept data, you have to hang onto it

Slide 48

Slide 48 text

how does the client know it ’s offline? poll for server updates catch a socket error sending data via normal XHR wait for an error saving

Slide 49

Slide 49 text

hope for the best, plan for the worst function update_state( state ) { state_cache.push( state ) var req = $.ajax({ type: “POST”, url: “/update_state”, data: state_cache }).done( function() { state_cache = []; }).fail( function( xhr, err ) { if ( err == “timeout” ) start_heartbeat(); }); }

Slide 50

Slide 50 text

offline, the client is on its own

Slide 51

Slide 51 text

how do know you’re back? try it navigator.onLine probably some sort of heartbeat no matter your strategy

Slide 52

Slide 52 text

and then: send client’s current state follow a defined path from server’s current state to client’s or send specific steps from the client sync server data with client

Slide 53

Slide 53 text

tl;dr, offline is scary

Slide 54

Slide 54 text

so let ’s talk actions

Slide 55

Slide 55 text

actions in the interface never trigger actions directly actions are triggered by state changes state changes update client and server then the action itself occurs

Slide 56

Slide 56 text

this guy $( “#form-step-2” ).show();

Slide 57

Slide 57 text

and this one $( “#form-step-2” ).save();

Slide 58

Slide 58 text

saving (for example) fetches user-entered data from the interface stores it in an application variable adds it to the local data store notifies the server that a save occurred syncs data

Slide 59

Slide 59 text

..then the server default: fetches user-entered data from the client enhanced: syncs with client DB stores it in an application variable adds it to the backend data store notifies any other connected clients that there are changes

Slide 60

Slide 60 text

same thing happens on both sides, why?

Slide 61

Slide 61 text

other connected clients & reloads fetches user-entered data from the server stores it in an application variable adds it to its local data store updates its interface

Slide 62

Slide 62 text

if JS disappears we use HTML behaviors (links, buttons) to skip right to the server step concurrent users receive immediately-expiring data

Slide 63

Slide 63 text

we need state reflected in: client and server variables, ofc the URL the CSS

Slide 64

Slide 64 text

tada: it ’s progressive! our server has a version of the app and can render its interface client side can continue without the server, in case we go offline real-time communication and multiple concurrent users follow the same pattern

Slide 65

Slide 65 text

our app is now usable by multiple clients in multiple locations and easier to continue extending by layering functionality, as with progressive enhancement

Slide 66

Slide 66 text

thanks! @garannm [email protected]