Make-or-break the company
Four Easy Pieces
type Model = {username : String ,password : String ,lastError : Maybe Error ,credentials : Maybe AuthToken}
type Action = Submit | ChangeUsername String | ChangePassword String | LoginResponse (Result Error AuthToken)
update : Action -> Model -> Model update action model = case action of ChangeUsername s -> {model | username <- s} ChangePassword s -> {model | password <- s} ...
submitForm : Model -> Effects (Result Error AuthToken) submitForm model = encodeForm model |> post decodeAuthToken "/auth/login" |> asEffect decodeAuthToken : String -> AuthToken decodeAuthToken = ...
update : Action -> Model -> (Model, Effects Action) update action model = case action of ChangeUsername s -> ({model | username <- s}, none) ChangePassword s -> ({model | password <- s}, none) ...
Submit -> ({model | loading <- True , lastError <- Nothing} ,submitForm model.username model.password |> Effects.map LoginResponse)
LoginResponse (Ok token) -> ({loading = False ,lastError = Nothing ,credentials = Just token} ,none) LoginResponse (Err error) -> ({loading = False ,lastError = Just error ,credentials = None} ,none)
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 btn-block" ,type' "button" ,disabled (model.username == "" || model.password == "")] [text "Log In"]]
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 btn-block" ,type' "button" ,disabled (model.username == "" || model.password == "") ,onClick address Submit] [text "Log In"]]
Four Easy Pieces
Model
Address Action -> Model -> Html
Action
Action -> Model -> (Model, Effects)
{ 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])
var sendEvent = F2(function (success, event) { return Task.asyncFunction(function(callback) { ga('send', 'event', event.category, event.action); return callback(Task.succeed(success)); }); });
Time permitting…
Beeline
http://krisajenkins.github.io/beeline-demo/
Blog
Sewing Browser
Lunar Lander Game
http://krisajenkins.github.io/lunarlander
Learn!