Elm: User Control and Business Logic

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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: