Our tools suck.
type Model = Model {username : String ,password : String ,serverError : Maybe Http.Error}
type Action = ChangeUsername String | ChangePassword String | Submit | LoginResponse (Result Error AuthToken)
update : Action -> Model -> Model
update : Action -> Model -> Model update action model = case action of ... ChangeUsername s -> {model | username <- s} ...
Sometimes we need to schedule future actions.
update : Action -> Model -> Model
…becomes:
update : Action -> Model -> (Model, Effects Action)
update : Action -> Model -> (Model, Effects Action) update action model = case action of ... Submit -> (model ,postForm "/login" model.username model.password) ... postForm : ... -> Effects Action postForm ... = LoginResponse ( ... )
update : Action -> Model -> (Model, Effects Action) update action model = case action of ... Submit -> ({model | loading <- True} ,postForm "/login" model.username model.password) ...
view : Model -> Html
loginForm : Model -> Html loginForm model = form [] [input [type' "text" ,class "form-control" ,autofocus True] [] [input [type' "password" ,class "form-control"] [] ,button [class "btn btn-primary" ,type' "button" ,disabled (model.username == "" || model.password == "")] [text "Log In"]]
An HTML UI is an event source.
view : Model -> Html
…becomes:
view : Address Action -> Model -> Html
loginForm : Address Action -> Model -> Html loginForm address model = form [] [input [type' "text" ,class "form-control" ,onChange address Username ,autofocus True] [] [input [type' "password" ,class "form-control" ,onChange address Password] [] ,button [class "btn btn-primary" ,type' "button" ,disabled (model.username == "" || model.password == "") ,onClick address Submit] [text "Log In"]]
type Model type Action update : Action -> Model -> (Model, Effects Action) view : Address Action -> Model -> Html
Elm | MVC |
---|---|
Model | Model |
Action | - |
View | View |
Update | Controller |
M --- C --- V
M --- C --- V / \ / \ / \ / \ / \ / \ M - C - V M - C - V / \ / \ / \ / \ / \ / \ M - C - V M - C - V
Simple:
view : Address Action -> Model -> Html
Grows to
view : Address Action -> Model -> Everything
M / \ / o m / \ m m C / \ / o c / \ c c V / \ / \ v v \ \ v M --- C --- V
{ spatialReference: { wkid: 4326, latestWkid: 4326 }, candidates: [ { address: "Royal Festival Hall", location: { x: -0.11599726799954624, y: 51.50532882800047 }, score: 100, attributes: { }, extent: { xmin: -0.120998, ymin: 51.500329, xmax: -0.110998, ymax: 51.510329 } } ] }
type alias Place = {address: String ,latitude: Float ,longitude: Float}
decodePlaces : Decoder (List Candidate) decodePlaces = "candidates" := (list decodePlace)
decodePlace : Decoder Place decodePlace = Place `map` ("address" := string) `apply` (at ["location", "x"] float) `apply` (at ["location", "y"] float)
type alias AnalyticsEvent = {category : String ,action : String}
toAnalyticsEvent : Action -> Maybe AnalyticsEvent toAnalyticsEvent action = case action of BuyProduct id -> Just {category = "Buy", action = "Product"} ShareProduct Twitter id -> Just {category = "Share", action = "Twitter"} ... _ -> Nothing
toAnalyticsEffect : Action -> Effects Action toAnalyticsEffect action = case toAnalyticsEvent action of Nothing -> none Just event -> sendEvent AnalyticsSent event
updateWithAnalytics : Action -> Model -> (Model, Effects Action) updateWithAnalytics action model = let (newModel,newFx) = update action model in (newModel, batch [newFx, toAnalyticsEffect action])
Beeline
http://krisajenkins.github.io/beeline-demo/
Blog
Sewing Browser
Lunar Lander Game
http://krisajenkins.github.io/lunarlander
Learn!