Intro
In a previous post, I discussed how to implement a Login user-control. The implementation details of that post had the business logic embedded within the user control itself instead of the user-control’s host.
This post discusses how we can lift the business logic for logging-in from the user-control itself, to the actual parent of the control.
The following code is the updated Login module:
module Controls.Login exposing (..) import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) -- MODEL type alias Model = { username : String , password : String , loggedIn : Bool } model : Model model = Model "" "" False -- UPDATE type Msg = UserInput String | PasswordInput String | Attempt ( String, String ) update : Msg -> Model -> Model update msg model = case msg of UserInput v -> { model | username = v } PasswordInput v -> { model | password = v } Attempt ( username, password ) -> { model | username = username, password = password } -- VIEW view : Model -> Html Msg view model = div [] [ input [ class "signin", type_ "submit", value "Signin", onClick <| Attempt ( model.username, model.password ) ] [] , input [ class "signin", type_ "password", placeholder "password", onInput PasswordInput, value model.password ] [] , input [ class "signin", type_ "text", placeholder "username", onInput UserInput, value model.username ] [] ]
Unlike the last post for building a user-control, the Login module above no longer has business logic for actually processing an attempted login. Instead, I made this the responsibility of this user-control’s parent.
Let’s view the following Update and supporting functions of our Login control’s parent:
update : Msg -> Model -> Model update msg model = case msg of ... OnLogin subMsg -> case subMsg of Login.Attempt v -> let latest = Login.update subMsg model.login in { model | login = attemptLogin latest } Login.UserInput _ -> { model | login = Login.update subMsg model.login } Login.PasswordInput _ -> { model | login = Login.update subMsg model.login }
Note above how our update function now performs pattern matching on the Msg case values that are defined within our Login module.
The supporting functions can be found below:
attemptLogin : Login.Model -> Login.Model attemptLogin credentials = let successful = String.toLower credentials.username == "test" && String.toLower credentials.password == "test" in if successful then { username = credentials.username, password = credentials.password, loggedIn = True } else { username = credentials.username, password = credentials.password, loggedIn = False }
Here’s the entire module implementation:
module Home exposing (..) import Domain.Core exposing (..) import Controls.Login as Login exposing (..) import Html exposing (..) import Html.Attributes exposing (..) main = Html.beginnerProgram { model = model , update = update , view = view } -- MODEL type alias Model = { videos : List Video , articles : List Article , login : Login.Model } model : Model model = { videos = [], articles = [], login = Login.model } init : ( Model, Cmd Msg ) init = ( model, Cmd.none ) -- UPDATE type Msg = Video Video | Article Article | Submitter Submitter | Search String | Register | OnLogin Login.Msg update : Msg -> Model -> Model update msg model = case msg of Video v -> model Article v -> model Submitter v -> model Search v -> model Register -> model OnLogin subMsg -> case subMsg of Login.Attempt v -> let latest = Login.update subMsg model.login in { model | login = attemptLogin latest } Login.UserInput _ -> { model | login = Login.update subMsg model.login } Login.PasswordInput _ -> { model | login = Login.update subMsg model.login } attemptLogin : Login.Model -> Login.Model attemptLogin credentials = let successful = String.toLower credentials.username == "test" && String.toLower credentials.password == "test" in if successful then { username = credentials.username, password = credentials.password, loggedIn = True } else { username = credentials.username, password = credentials.password, loggedIn = False } -- VIEW view : Model -> Html Msg view model = div [] [ header [] [ label [] [ text "Nikeza" ] , model |> sessionUI ] , footer [ class "copyright" ] [ label [] [ text "(c)2017" ] , a [ href "" ] [ text "GitHub" ] ] ] sessionUI : Model -> Html Msg sessionUI model = let loggedIn = model.login.loggedIn welcome = p [] [ text <| "Welcome " ++ model.login.username ++ "!" ] signout = a [ href "" ] [ label [] [ text "Signout" ] ] in if (not loggedIn) then Html.map OnLogin <| Login.view model.login else div [ class "signin" ] [ welcome, signout ]
Conclusion
In conclusion, I discussed how to implement a Login user-control that does not does not have the responsibility for evaluating if an attempted login was successful or not. Thus, I attempted to demonstrate how to implement that responsibility within the host (i.e. parent) of that user-control.