name: inverse class: center, middle, inverse layout: true .header[.floatleft[.teal[Christopher Biggs] — Functional Frontends].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[ylj18] May 2018]] --- name: callout class: center, middle, italic, bulletul layout: true .header[.floatleft[.teal[Christopher Biggs] — Functional Frontends].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[ylj18] Jan 2018]] --- layout: true template: callout .header[.floatleft[.teal[Christopher Biggs] — Functional Frontends].floatright[.teal[@unixbigot] .logo[@accelerando_au]]] .footer[.floatleft[.hashtag[ylj18] Jan 2018]] --- class: center, middle template: inverse # Functional Frontends in Elm ## .green[Fast productive web programming with no baggage] .bottom.right[ Christopher Biggs, .logo[Accelerando Consulting]
@unixbigot .logo[@accelerando_au] ] ??? G'day folks, I'm Christopher Biggs. --- layout: true template: callout .crumb[ # Welcome ] --- # Who am I? ## Christopher Biggs — .teal[@unixbigot] — .logo[@accelerando_au] * Brisbane, Australia * Founder, .logo[Accelerando Consulting] * 20+ years in IT as developer, architect, manager * Accelerando is a "full service" consultancy - chips to cloud * ***IoT, DevOps, Big Data*** ??? I've been involved with electronic gadgets since I was a teenager, and throughout 20 years as a software engineer, architect and manager. I operate Accelerando Consulting which helps businesses use technology to reduce stress. Really, that's what I believe, that technology is the gradual process of freeing humanity from drudgery and fear, and I started Accelerando because I wanted to contribute to a future that I will be excited to live in. --- layout: true template: callout .crumb[ # Welcome # Whinging ] --- class: center, middle template: inverse # Frontend stresses me out ??? Which is not to say that I'm a style maven. Like many highly technical people elegance does not come naturally to me. Today I want to talk about how those of us who do not live and breathe UX can build pleasant user interfaces with a minimum of fuss, without having to learn a billion javascript tools and frameworks, yet still have access to some very sophisticated and stylish interface elements. --- # I need to visualise complex data ## But I'm from Mars, and front-end toolchains are from Venus ??? My desire to tell you this comes from pragmatism. I started working life as a C coder. You'd think that means I'd be safe from having to write web user interfaces. Not so much. I spent a total of nearly a nearly ten years doing web interfaces in C, in two different jobs, in two different decades. It wasn't pretty. The webpages weren't pretty either. --- # Also, When I first saw Comic Sans, I liked it ### (True story, I have questionable aesthetic judgement) ??? Now I'm not saying that it's impossible to write good looking web pages in C. But I sure didn't. As soon as I could I convinced both those workplaces to move to a more appropriate platform. So in one case we wrote a Java applet shortly before the time it became apparrent that that was a really bad idea, and then second time around we moved to PHP right around the time that the twin meteors of cloud computing and sophisticated javascript frameworks brought the curtain down the era of templated based server side rendering. --- # Can't I just be productive? ## I want a toolchain to help me, not confuse me ??? So now Javascript is where it's at. I've said before that Javascript isn't the language anyone would have chosen to become the one true universal platform, but it's the one we got. The good news is the additions to javascript in the last 5 years have rendered it quite pleasant. But the tools. Huge dependency graphs. Version conflicts. Complicated build processes. Packaging systems that would make Escher dizzy. --- # I heard about Elm ## Full props to @jessitron et.al. ??? A couple of years ago I heard about Elm. A nice simple functional language that compiles to Javascript, and has a friendly toolchain that does its best to help you out with comprehensible error messages. Yeah, right I thought. Nice toy language. I saw a presentation at YOW in 2016 from Jessica Kerr, where she built a simple user interface in the course of a 40 minute talk. It looked kinda fun, so I put it on my someday maybe list. --- # Then I gave it a try ??? Last year I consulted on a project involving indoor location data. Picture the Marauder's Map from Harry Potter. I wanted to do a throw away proof of concept to confirm that the data I was working with made sense, so I took an opportunity to try out Elm. --- # I am not a functional zealot ## But I've **seen** things, folks. ??? This is not that talk where I tell you that the clouds parted and a great beam of light shone upon the land, rainbows formed, unicorns frolicked, and programmers everywhere forgot how to swear. This was my first pure functional language. Nothing made sense for about a week. There are easier languages to learn, even though Elm's documentation, tutorials and books are excellent. But I've seen things in journey through the javascript revolution, folks. Class hierarchies where it's hunt-the-code-beneath-the-wrappers. Abstraction layer cakes so deep you need scuba gear. Code that prioritises friendly to machines over friendly to developers. --- # FURB ## The "Functional Unicorns-and-Rainbows Reality Bubble" ??? When people set out to create a tool that rights all the wrongs in the world and brings programmer Nirvana, I find you often get a tool that is beautiful, but too beautiful to persist in the real world. If you should find shangri la, you're told that in order to remain you may never again have any contact with the outside world. --- # Isn't there a middle ground? ## Clean, Pragmatic, Extensible. ??? So what would a tool look like that that lets you do real work, but at least smells a little bit of unicorn. I'm going to do you the credit of presuming that you've at least heard of functional reactive programming. A lot of our problems as programmers stem from unexpected change. Safe use of multithreading is hard. Handling exceptions and pointer types is hard. Variable aliasing and uninitialised values are a pain. So Elm's goal is to contain the danger safely. And by danger I mean you, the carbon impurity at the front of the keyboard. --- class: center, middle template: inverse # Whirlwind Precis of Elm --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm ] --- # This talk is **not** an introduction to Elm. ## But, for those who came late... a precis * Model * Update * View **That's it.** * .footnote[sorta] ??? You have exactly one variable, your model. Events from the outside world change your model, and the model reacts to those changes. You don't have to worry about updating the screen, because the language handles it. Each time the model changes your view routine is called, and then the language runtime efficiently updates the document object to render the new view. ---  .spacedown[ # First iteration ## A model, a way to change it, a way to see it ] ??? So back to that rapid prototype. We have a building and a bunch of floor plans, and we want to show the locations of people and machines. Really we're only tracking the machines, but we're taking the short-cut of presuming that a many of those machines, the ones we call telephones, are suckered on to people much of the time. --- # A model ## The only variable in your entire program .code[ ```elm type alias Model = { floors : List Floor , current : String } type alias Floor = { floor_id : String , name : String , plan_url : String } initialModel = ( Model [] "select a floor", Cmd.none ) main = Html.program { init = initialModel , view = view , update = update } ``` ] ??? Now Elm really encourages you to refactor constantly, and to start with the simplest possible model that expresses your problem. This is a really close match to how I work in any case, with a series of exploratory prototypes, so I find this approach rather pleasant. Sorry about the commas, you'll have to get used to those. Elm folks like to line things up like this, and while it's not essential, it sort of makes sense after a while. This is my model at the end of day two. Day one was even simpler. I have a building. It has floors, floors have maps. We expect the user to select one particular floor. We start out with no floor selected, so we use a little prompt in place of the name. That's cheating, but I'll come back to that. --- # An update function ## Receive message, alter model, emit response .code[ ```elm type Msg = GetFloors | GotFloors (Result Http.Error (List Floor)) update msg model = case msg of GetFloors -> (model, getFloors) GotFloors (Ok fl) -> ({model | floors = fl} , Cmd.none) GotFloors (Err _) -> (model, Cmd.none) ``` ] ??? Of course our model needs to change. Our functional programs consist of inputs and outputs, otherwise they're just constants. In elm we define in advance what inputs we will process. We do this by defining one type called message, which is the union of all the things we know how to process. The elm runtime calls our update function with a message whenever it has input for us. Our update function is typically going to use that input to modify the model in some way, and possibly return a command, which is a way to send messages to the outside world. We don't need to send a command to render our view, the elm runtime implicitly invokes the view function any time the model changes. --- ## A View: Render a representation of the model in browser .code[ ```elm view model = Html.main_ [] [ Html.h1 [] [Html.text "Building Map"] , Html.div [Html.Attributes.class "sidebar"] [floor_summaries model] , Html.div [Html.Attributes.class "mainpanel"] [floor_detail model] ] floor_summaries model = Html.ul [] (List.map floor_summary model.floors) floor_summary floor = Html.li [] [Html.text floor.name] floor_detail model = Html.div [ Html.Attributes.class "floor" ] [ Html.h2 [] [Html.text model.current] , Html.canvas [] [] , Html.h3 [] [Html.text (model.floor ++ " details")] ] ``` ] ??? Our view function is responsible for presenting our model to the browser as HTML. We don't have to care about which part of the screen needs to be updated, we simply render everthing into a virtual document, and the runtime takes care of working out what is different. This sounds insane, but I've not had any performance problems. I realise that if you don't already know about elm I'm going too fast for you, I'm not trying to teach you elm here, just take a lap around the block. If you want to learn Elm, I recommend you watch Jessitron's video, which I will link to at the end. --- ## Oh yeah, those pesky side-effects .code[ ```elm getFloors = Http.send GotFloors Http.request { url = "http://localhost:8080/floor" -- * see footnote , method = "GET" , headers = [Http.header "Accept" "application/json"] , body = Http.emptyBody , expect = Http.expectJson decodeFloorSummary } decodeFloorSummary = list (map3 FloorSummary (field "floor_id" string) (field "name" string) (field "plan_url" string)) ``` ] .footnote[we'll come back to this hack] ??? Now before I said that our model update can return a command along with the new model. This is the way, besides the view, that we can affect the outside world. A common example is as here where we're making an API call to retrieve our maps. We're invoking a HTTP request and passing two critical pieces of information. First a response parse, which is the expectJson function plus a description of the expected data. Secondly, that Got Floors argument there is the type of the message. So what happens here is the elm runtime constructs a http request, sends it, passes the result to a parser, and then passes the output of the parser as a message to our update function. --- # Error handling is a type switch .code[```elm type Msg = SelectFloor String | GetFloors | GotFloors (Result Http.Error (List Floor)) update msg model = case msg of GotFloors (Ok fl) -> ({model | floors = fl} , Cmd.none) GotFloors (Err _) -> (model, Cmd.none) ```] ??? One last detail on updates, of course interactions with the outside world could result in errors. When this is possible we declare or response message as a union type, typically our expected result along with some kind of error. The benefit of this is that the compiler will ensure that we are dealing with all the possible cases. This is part of Elm's boast that you will never see a runtime error. It's not bulletproof but its damn close. Of course sometimes you are reduced begging the compiler for mercy. --- # Types ## Sloppy coding isn't just a bad idea, it's against the law. .code[ ```elm init = ( Model [] "select a floor" , getFloors ) ``` ] ^ I cheated ??? This use of types as a software quality enforcement permeates elm. I told you that I cheated earlier when I made the default floor selection a prompt to select a floor. I had to put something, because I declared that field in the model as a string, so it MUST contain a string. --- # You can't do this .code[ ```elm init = ( Model [] Nothing , getFloors ) ``` ] ^ Nothing (Elm-speak for 'null') is not a String ??? If I tried to put a null in there, it wouldn't compile. Empty string would work but that's even more cheaty. --- # But Maybe* you'll like this .code[ ```elm type alias Model = { floors : Maybe List Floor , current : Maybe String } init = ( Model Nothing Nothing, getFloors ) ``` ] ^ Type "Maybe X" means "union (Nothing, X)" .footnote[See what I did, there?] ??? The way you express optional data is with a union consisting of null and something else. This is so common that there's a special mechanism for it called the Maybe type. A Maybe is something that can either be null (or Nothing in elm-speak) or Just a value. --- # If I access a value, I must handle *all* its possible types ## "No run-time exceptions, ever*" .code[ ```elm view model = Html.main_ [] [ Html.h1 [] [Html.text "Building Map"] , case model.current of Nothing -> Html.h2 [] ["Select a floor"] Just f -> Html.h2 [] ["Level " ++ f] ] ``` ] ^ that **case** expression must handle every possible type of the value .footnote[or double your money back] ??? Whenever you are dealing with any maybe type, the compiler makes you explicitly say what happens when you get nothing. Once again this can be infuriating, but it becomes second nature after not very long. So in our mapping example, the way to do that prompt is to have a type switch, if there is no current selection we show a prompt, and if there is, we show what is selected. Our switch lets us declare a new name for the safety checked value, so we can't ever accidentally refer to the maybe null value. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm # Flags ] --- class: center, middle template: inverse # Works fine on my machine --- # Interlude: Flags You might have noticed I did this: .code[ ```elm getFloors = Http.send GotFloors Http.request { url = "http://localhost:8080/floor" ```] ??? Before we move on to the meat of this talk, I want to come back to another one of the nasty cheats I snuck past you. --- .fig50[ ] .spacedown.smallcode[ ```elm Http.request { url = "http://localhost:8080/floor" -- etc } ``` ]] ??? Did anyone catch me hard coding the url of my API? That's not going to fly in production. But I wouldn't want to hard code a production url either. Typically in web programming your webserver passes this kind of infomation in as environment or template variables. --- # Accepting flags .code[```elm type alias Flags = { apiRoot: String } type alias Model = { flags : Flags , floors : Maybe List Floor , current : Maybe String } main = Html.programWithFlags { init = initialModel , update = update , view = view } initialModel flags = ( Model flags Nothing Nothing, Cmd.none ) ```] ??? Elm has a mechanism for this too, which it calls flags. And once again, they're strongly typed. When you declare a model, you can declare that it accepts flags, and what the names and types of the flags are. --- # Passing flags .code[```html
```] ??? Then your build process or your webserver arranges to pass those flags to elm when the runtime is initialised. Here I'm using an embedded webserver written in Go, so I'm using Go's templating language to set my API root. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm # Flags # Input ] --- class: center, middle template: inverse # Reality Bites --- # But, wait, what about reality ## Some of us have work to get done, hipster! ??? So over the course of a week or so my little user interface grows from a menu and a map, to a complete application with dialogs and toolbars and icons and searching and hooks into the cloud provider's authentication system. But I had help. Elm actually has about 900 communitiy packages containing libraries to do everything from video games to a complete implemtation of the google material design toolkit, which you may recognis from sites such as Google maps and gMail. --- # Accepting user input ### (Filthy little non-functional Hobbitses) .code[```elm type Msg = SelectFloor String | GetFloors | GotFloors (Result Http.Error (List Floor)) update msg model = case msg of SelectFloor f -> ({model | current = Just f}, getFloorDetails f) GetFloors -> (model, getFloors) -- etc. ```] ??? Elm's modules are another way of keeping the risky parts of your program as small as possible. And this includes user input. A web programming language that couldn't respond to input would be fairly useless. The way you accept input is by defining a message type that encodes the information that you want. --- # Enabling user input ### Page elements can emit messages (think 'onClick()' etc) .smallcode[```elm view model = Html.main_ [] [ Html.div [ Html.Attributes.class "header"] [ Html.h1 [] [Html.text "Building Map"] ] , floor_summaries model , floor_details model ] floor_summaries model = Html.div [ Html.Attributes.class "sidebar" ] [ Html.h2 [] [ Html.text "Floors" ] , Html.p [] ( List.map floor_summary (Maybe.WithDefault [] model.floors) ) ] floor_summary floor = Html.button [ Html.Events.onClick (SelectFloor floor.floor_id) ] [ Html.text floor.name ] ```] ??? Now in our view we can declare user elements that emit messages. Here we we have a menu button that sends a message with the name of the selection. That's going to arrive in our update function and alter our model. --- # Accepting input from non-user events ### Elm corrals 'side-effects' as tightly as possible * Libraries can encapsulate external events * Your main program subscribes to the ones it needs .code[```elm subscriptions model = Sub.batch [ Window.resizes GotWindowSize , Time.every (60 * Time.second) UpdateClock ] main = Html.program { init = init , view = view , update = update , subscriptions = subscriptions } ```] ??? There are other kinds of inputs besides users of course. The mechanism for these is called subscriptions, you name a subsription that you want to receive, and pass the message that it will send. Then these system inputs such as window changes or websocket input or timer ticks arrives at your update function just like everything else. So that's a lightning tour of the elm architecture. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm # Flags # Input # Ports ] --- class: center, middle template: inverse # I need to womble, but there's no elm-womble ??? Now, I told you that story, to tell you this one. Sooner or later you will want to do something for which there is no Elm library. You have two choices. You can write an elm library. Or you can adopt a non-elm library and punch a hole in the walls of reality to let your functional reality denial bubble interact with the great unwashed mugglespace. Elm, being pragmatic, provides a way to do this as safely as possible. --- # Ports - A telegraph line to Shangri La ## Elm's admission that there is a world outside the **FURB** * * The elm universe sends messages across the Veil * The code on the Other Side sends messages back .footnote[Functional Unicorns-and-rainbows Reality Bubble] ??? Elm defines a mechanism called ports, which are another kind of subscription and message. But instead of the interaction being between your elm code and the elm runtime, you have a one way typed message to or from the Javascript runtime. If you've programmed in go, it's very similar to Go's channels. Ports let us interact with any piece of non-elm code that we want to use, but give the best effort to ensure safe values. We can't protect against outside libraries doing crazy stuff, but we can make sure that if things go splatter, we don't get any on us. --- # A pivot ### Today building maps, tomorrow THE WORLD!!!* .footnote[Bwhahahahahahaha] ??? With the inexplicable xenophobia afflicting the United Kingdom, it has come to the attention of the authorities that there is an entirely undocumented population of Wombles inhabiting the UK. What is worse, it is believed they must be immigrants given the large number of eastern european names used by these creatures. So the opportunity arose to implement a global womble registration and tracking system. It seems no matter how fast i run evil catches up with me. --- .fig60[ ] .spacedown[ # Let's get concrete. ## Here* be Wombles. .footnote[No, that's not Wimbledon Common. Did you even read the books?.] ] ??? So, now Accelerando has accepted a commission to expand our marauders map from a building, to a whole country. Oh, and this isn't shangri la, so people can leave the country and maybe we want to know where in the world they are because we're creepy and have boundary issues. Along with some remedial ethics training I'm gonna need some kickass grade mapping technology. It'll probably be super hard to learn and cost a brazillion dollars. --- .fig60[ ] .spacedown[ # Leaflet.js ## Yay, Christopher gets to the point ] ??? So how much code do I need to display a global scrollable and zoomable street map, and place markers and text representing moving objects. Well, about that much. Oh and it's free. But this is a plain Javascript library, and I want to talk to it from inside my unicorn bubble. --- # Ports, the quick way ## (Inside the bubble, sending out) .code[ ```elm port module Update port toLeaflet : String -> Cmd msg update msg model = case msg of LoginSucceeded -> ( {model | authenticated = True}, (toLeaflet "{\"Init\":\"map\"}") ) ``` ] ??? So here is about the simplest possible way to send information outside the bubble. We defined a port, that carries strings, and we sent a string, that happens to be a bit of JSON out through the port, where it will be received by the javascript engine. --- # Ports, the quick way ## (Outside the bubble, receiving from inside) .code[ ```js var app = Elm.Main.embed(document.getElementById("elmApp")); var map; app.ports.toLeaflet.subscribe(function(msg) { cmd = JSON.parse(msg); if (cmd.hasOwnProperty('Init')) { map = L.map(cmd.Init).setView([0, -100], 1); L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map); } if (cmd.hasOwnProperty('FlyTo')) { map.flyTo(cmd.FlyTo, 13, {animate: true, duration: 4}) } }); ``` ] ??? In our javascript code, we subsribe to each port by supplying callback function, and we have some confidence that only well formed messages will appear. --- # Back the other way ## (Outside the bubble, sending in) .code[ ```elm L.marker( latlng, { icon: mapMarker, title: feature.properties.Name, pane: filter }).addTo(map).on('click', clicked_womble => { app.ports.fromLeaflet.send(`{"selectWomble": "${clicked_womble.properties.Name}"}`) }); ``` ] ??? Let's look at the other way. We want to use some sort of plain javascript library that generates events. Here it's the leaflet mapping engine. We add a marker to the map, and give it an onclick handler that sends some data through a port to our Elm UI. --- # Back the other way ## (Inside the bubble, receiving from outside) .code[ ```elm port fromLeaflet : (String -> msg) -> Sub msg subscriptions model = Sub.batch [ Window.resizes GotWindowSize , Material.subscriptions Mdl model , Layout.subs Mdl model.mdl , fromLeaflet FromLeaflet ] type Msg = ToLeaflet String | FromLeaflet String | -- moreMessagesHere update : Msg -> Model -> (Model, Cmd Msg) update msg model = case msg of FromLeaflet s -> -- decode the json and trigger a page change.... --- more cases here ``` ] ??? On the elm side it looks like this. We use the same subscription engine that we use to accept side-effects like timers, random numbers, and window changes to accept messages from ports. --- # What's wrong with that ## The types, all the *beautiful* types 😠??? BUT. The drawback of doing what I did here is that I'm just using strings that happen to carry JSON. We can do a lot better than that. --- # Once more, with finess ## The more typed your channel, the safer .code[ ```elm port loginStateNotify : (Bool -> msg) -> Sub msg port layerStateNotify : ((String, Bool) -> msg) -> Sub msg port layerSizeNotify : ((String, Int) -> msg) -> Sub msg port zonePopulationNotify : ((String, Int) -> msg) -> Sub msg port wombleMarkerClick : (String -> msg) -> Sub msg ``` ] ??? Here's a more realistic example. We have two major chunks of non-elm javascript, the Amazon Cognito authentication library, and the Leaflet engine. For each interaction between these libraries and Elm, we create a port, or maybe a pair of ports. It may look like a lot of work but I find the opposite, when you explicity think about every path of interaction between your components you get a nicely testable architecture. --- .fig70[ ] .spacedn[ # Final* demo Elm fetches population data from an API server, renders a user interface, and animates map transitions to selected Wombles. In 150 lines of Elm and 30 lines of Javascript. ] .footnote[not final] ??? So here's one I prepared earlier. We have a pretty ordinary rest API, happens to be written in Go, but whatever. Our elm program pulls down the known location and movements of wombles-of-interest and allows us to track these on a map. --- ## Distribution is one HTML file, one JavaScript, one CSS. # 65 kilobytes* .footnote[zipped, 368k plain] ??? And the whole thing would fit on a floppy disk if you remember what that is. For me the important thing is it will also fit on the flash memory of a five dollar wifi system on chip, and I don't need to devote my life to the study of javascript to make a web page. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm # Flags # Input # Ports # Single-Page-Apps ] --- class: center, middle template: inverse # But that's still a toy. ??? This is where many elm talks stop. We have a one-page website that does some cool stuff, but how do you move beyond that. To go bigger you're going to want some more gadgets, like URL routing and some high level user interface elements. --- # Hop in the SPA ## Let's make a **S**ingle-**P**age-**A**pp * Avoid costly reloads * We want our browser navigation to DWYM * Support deep linking to content pages ??? So lets look at how we add URL routing. We want to be able to serve a single pageload that presents a multi-page user interface where the URL bar, back button and deep linking work just how we expect them to. --- # Navigation ## Apps that know where you are and where you've been .code[ ```bash $ elm-package install elm-lang/Navigation $ elm-package install evancz/url-parser ``` ] .code[ ```elm main = Navigation.program UrlChange {...} type alias Model = { history : List Navigation.Location } init location = ( Model [ location ] , Cmd.none ) type Msg = UrlChange Navigation.Location | YourApplicationMessagesHere... update msg model = case msg of UrlChange location -> ( { model | history = location :: model.history } , Cmd.none ) ``` ] ??? Elm gives us a navigation library that requires just one addition to our program. We receive our location at startup, and we need two new messages, one to initiate a change of URL, and one to respond to changes initiated by the user. --- # Locations ## Just a wrapper around Document.location .code[ ```elm type alias Location = { href : String , host : String , hostname : String , protocol : String , origin : String , port_ : String , pathname : String , search : String , hash : String , username : String , password : String } ``` ] ??? That location type is just a wrapper around Document.location from the HTML document object model. There's far too much voodoo there for my taste. --- # Too complicated ## Lets simplify **Locations** to "**Routes**" .code[ ```elm type Route = Signin | WombleFinder | MovementReport String makeRoute : Url.Parser (Route -> a) a makeRoute = Url.oneOf [ Url.map Signin Url.top , Url.map WombleFinder (Url.s "search") , Url.map MovementReport (Url.s "name" > Url.string) ] routeFor : Navigation.Location -> Route routeFor loc = Url.parsePath makeRoute loc urlFor : Route -> String urlFor route = case route of Signin -> "/" WombleFinder -> "/search" MovementReport n ->"/report/" ++ n ``` ] ??? So lets simplify. We define a type called Route which encapsulates a page within our app. Somepeople call the type page. Next we define two functions to turn locations into routes, and vice versa. --- # Now we only care about Routes ## And the route type carries any path parameters, too .code[ ```elm type alias Model = { flags : Flags , history : List Route , moreModelFieldsHere } initialModel : Flags -> Navigation.Location -> Model initialModel flags location = let route = Url.parsePath makeRoute location in { flags = flags , history = [route] , otherModelDataHere = Nothing } ``` ] ??? We throw away all the location details and just keep our high level route data. Our route type carries any parameters associated with the URL. --- # Updating with routes .left[ * When we initiate a route change, we tell the browser the URL * When the browser informs us of an URL change, we update our route ] .code[ ```elm type Msg SetRoute Route | UrlChange Navigation.Location | YourOtherMessagesHere update : Msg -> Model -> (Model, Cmd Msg) update msg model = case msg of SetRoute route -> ( { model | history = route :: model.history } , Navigation.newUrl (urlFor route) ) UrlChange location -> ({ model | history = (routeFor location) || model.history }, Cmd.none) ``` ] ??? In our message handling, we only need to add two messages. One that our elm user interface can emit to request the browser to navigate, and one that triggers when it's done, or when the user hits the back button. --- # View with routes .code[ ```elm view : Model -> Html Msg view model = case List.head model.history of Signin -> viewSignin WombleFinder -> viewSearch MovementReports name -> viewReport name ``` ] ??? In our view, we simply have a big switch on the current route to decide what to view. Elm's dynamic DOM diffing makes this really fast. --- layout: true template: callout .crumb[ # Welcome # Whinging # Elm # Flags # Input # Ports # Single-Page-Apps # Refactoring ] --- class: center, middle template: inverse # Lost in a twisty maze of update messages ??? Now, there's just two more things I want to talk about, but I'm going to hit them both with one stone. Firstly, the more pages you write, the more your model will start to look like spaghetti, and secondly, if like me you entirely lack aesthetic judgement, you might want someone else to take care of the details of user experience. --- # When your Model and Msg types get humongous ## Time to compartmentalise, but * I **don't** recommend doing this from a standing start * Begin with the dumbest code that could possibly work * Use the live reload * Refactor once you have working code ??? So the way you cope with that is you encapsulate one area of your user interface into its own model, along with its own update and view. There are a bunch of "how to write a single page app in elm" tutorials out there that give you a template into which you can just drop your routes, and generates a complete model/update/view skeleton for every page you write. I don't really recommend doing that. It's so easy to refactor in elm that I start with spaghetti, and refactor it into ravioli later. Don't go too complex to soon. --- # Encapsulation - A model/updater in a box .code[ ```bash $ elm-package install debois/elm-mdl ``` ] .code[ ```elm import Material import Material.Layout as Layout type alias Model = { flags : Flags , history : List Route , mdl : Material.Model -- etc } type Msg SetRoute Route | UrlChange Navigation.Location | Mdl (Material.Msg Msg) | --- more messages here ``` ] ??? Let's make this more concrete, to steal a phrase from Yaron's Keynote. Has anyone here heard of a company called google? Few hands. OK if you've used their websites, you might notice they all look kind of similar. That look and feel is called Material Design, and it's not so much a framework as a user experience specfication that is implemented by a number of frameworks. Including one in elm, which is called Material Design Lite, or MDL. By adding one fairly opaque sub-model to our model, and passing along certain messages to it, we magically delegate aesthetic judgement to google. It's ok, they're not evil. --- # Updating and viewing encapsulated models .code[ ```elm update : Msg -> Model -> (Model, Cmd Msg) update msg model = case msg of SetRoute route -> ... UrlChange location -> ... Mdl msg_ -> Material.update Mdl msg_ model view : Model -> Html Msg view model = Layout.render Mdl model.mdl [ Layout.selectedTab model.selectedTab , Layout.onSelectTab SelectTab ] { header = viewHeader model , drawer = viewDrawer model , tabs = viewTabs model , main = viewBody model } ``` ] ??? To use MDL we make that model addition from the previous slide, and we also add one Message kind for MDL. Anything tagged as an MDL message goes to MDL's update function, and any messages it emits for its own purposes will be tagged. In our view, we have access to a rich set of user interface elements, sidebars, tabs, buttons, cards, tables and what have you. --- .fig70[ ] .spacedn[ # (actual) Final demo Google Material Design gives us best-practice UX and a library of interface elements and icons. Still under 300 lines of Elm. ] ??? Here's one last example, the same program as before but with page routing and material design added. Not a toy any more, our app has really bloated out. Nearly six pages of code. And it's still under 100k. --- .fig30[  ] # Recap .nolm[ * Frontend framework overload * Elm is a *simple* environment for **Functional Reactive Programming** * Input via **Flags**, **Events** and **Subscriptions** * **Ports** are typed channels through the bubble wall * You can build a whole application in one pageload * Easy to refactor to **encapsulated modules** for growth ] --- # Resources, Questions ## Related talks - [http://christopher.biggs.id.au/#talks](http://christopher.biggs.id.au/#talks) ## Me - Christopher Biggs - Twitter: .blue[@accelerando_au] - Email: .blue[christopher@biggs.id.au] - Slides, and getting my advice: http://christopher.biggs.id.au/ - Accelerando Consulting - IoT, DevOps, Big Data - https://accelerando.com.au/ ??? Thanks for your time today, I'm happy to take questions in the few moments remaining and you can catch me at lunch if you want to chat. --- # Links * Elm - [elm-lang.org](http://elm-lang.org/) * Leaflet - [leafletjs.com](http://leafletjs.com) * Adventures in Elm by @jessitron - [Slides](http://yowconference.com.au/slides/yow2016/Kerr-AdventuresElm.pdf) - [Video](https://www.youtube.com/watch?v=xrmeU1JNt7s) * Elm-MDL - A beautiful and simple UI framework - [demo](https://debois.github.io/elm-mdl/) - [doco](http://package.elm-lang.org/packages/debois/elm-mdl/latest)