-
Notifications
You must be signed in to change notification settings - Fork 0
Building a User Control
Here's a video on this post.
The following is the code for the Login control:
module Domain.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 ) ->
if String.toLower username == "test" && String.toLower password == "test" then
{ model | loggedIn = True }
else
{ model | loggedIn = False }
-- 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 ] [] ]
Note that we have the following messages within the code above:
type Msg = UserInput String | PasswordInput String | Attempt ( String, String )
The messages (i.e. union cases) above that have their own attached data are used to update the state of our Login model. Therefore, we can examine the code below to understand observe how the messages get sent:
[code language="CSharp"] 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 ] []
]
Note that the code for the Login control follows the standard structure of Elm's (Model, View, Update) architecture. Hence, there's no new paradigm being introduced for that code. However, there is a little bit of ceremony within the parent control that will embed the login control.
The following code reflects the parent control of the Login control:module Home exposing (..)
import Domain.Core exposing (..)
import Domain.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
| Login 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
Login subMsg ->
let
newState =
Login.update subMsg model.login
in
{ model | login = newState }
-- 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 Login <| Login.view model.login
else
div [ class "signin" ] [ welcome, signout ]
Let's first break down the plumbing required for the parent control to use the embedded Login control.
We add a property (i.e. login) to our Model record that is of type Login.Model which is defined within our embedded Login user-control.Here's the code:
type alias Model =
{ videos : List Video
, articles : List Article
, login : Login.Model
}
Here's the code:
type Msg
= ...
| Login Login.Msg
So in the discriminated union above (i.e. Msg), we declared a message case value named "Login" and associate an argument of type Msg that references the embedded Login control's Msg type.
Once our message case value is declared (which references the Msg type of the Login control), we then write code for our Update function on our parent control.Here's the code:
update : Msg -> Model -> Model
update msg model =
case msg of
...
Login subMsg ->
let
newState =
Login.update subMsg model.login
in
{ model | login = newState }
The code above takes the value of our "Login" case value and applies it to the Update function of the Login module along with the required model argument.
In order for the view to embed the Login user-control, we take leverage the Html.map function. In the Html.map function we supply the message and the result from invoking the user-control's view function. When invoking the view function, we provide the login property of our parent's model.Here's the code:
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 ]