Msgs sent from the quiz component to the parent application. You need to convert them into your application's Msg type using the first argument of the mapView function.
A custom type for quiz results. You can attach detailed explanation in Html Never for educational purpose.
A custom type that represents the user's state regarding a question. You need to maintain this value in your application's model and pass it to the mapView function.
Creates a quiz component with its own Model, separate from your main application's model.
Think of the argument as a simplified version of Browser.sandbox, as it lacks an update function and has a limited Msg.
You can send a Msg using the internally provided modelUpdate, modelUpdateWith and userHasAnswered functions.
The modelUpdated newModel and modelUpdatedWith f functions emit Updated <Question with the model updated>, and the userHasAnswered ans function emits Marked ((Correct|Wrong) (Html Never)) based on whether isCorrect ans returns True or False.
import Html as H exposing (Html)
import Html.Attributes as HA
import Html.Events as HE
type alias QuizModel =
{ userInput : String
, isShowingHint : Bool
}
type alias Answer =
String
quiz : Question
quiz =
let
qInit : QuizModel
qInit = ""
viewQuestion : Html Never
viewQuestion =
span []
[ H.text <| "Write the name of the atom that is represented in atomic symbol "
, H.span [HA.style "font-weight" "bold"]
[H.text "P"]
, H.text "."
]
viewResponseField : {modelUpdated, modelUpdatedWith, userHasAnswered} -> QuizModel -> Html Msg
viewResponseField {modelUpdated, modelUpdatedWith, userHasAnswered} =
\model ->
H.div []
[ H.input
[ HA.value model
, HA.placeholder "input your answer."
, HE.onInput
(\input ->
modelUpdated
{ model | userInput = ans }
)
]
[]
, H.button
[ HE.onClick
(userHasAnswered model.userInput)
]
[ H.text "submit"]
, H.br [] []
, H.button
[ HE.onClick <|
modelUpdated
{ model
| isShowingHint =
not model.isShowingHint
}
]
[ if model.isShowingHint then
H.text "show hint"
, else
H.text "hide hint"
]
, if model.isShowingHint then
H.div [ HA.style "color" "pink" ]
[ H.text "Phosphoric acid contains this element as its center atom."
]
else
H.text ""
]
isCorrect : Answer -> Bool
isCorrect userAnswer =
List.member userAnswer
[ "Phosphorus"
, "phosphorus"
, "PHOSPHORUS"
]
viewExplanation : Answer -> Html Never
viewExplanation userAnswer =
if isCorrect userAnswer then
H.text "Correct!"
else
H.span
[]
[ H.span [ HA.style "font-weight" "bold" ]
[H.text "P"]
, H.text " stands for "
, H.strong []
[ H.text "phosphorus"
]
, H.text
<| ", while you have input "
++ userAnswer
++ "."
]
in
createStateful
{ init = qInit
, viewQuestion = viewQuestion
, viewRespanseField = viewResponseField
, isCorrect = isCorrect
, viewExplanation = viewExplanation
}Creates a stateless quiz component.
import Html as H exposing (Html)
import Html.Attributes as HA
import Html.Events as HE
type alias Answer =
String
quiz : Question
quiz =
let
viewQuestion : Html Never
viewQuestion =
span []
[ H.text <| "Select the name of the atom that is represented in atomic symbol "
, H.span [ HA.style "font-weight" "bold" ]
[ H.text "P" ]
, H.text "."
]
viewResponseField : Html Answer
viewResponseField =
[ "Pluto"
, "Plutinum"
, "Phosphorus"
, "Protoactinium"
]
|> List.map
(\name ->
H.button
[ HE.onClick name ]
[ H.text name ]
)
|> H.div []
isCorrect : Answer -> Bool
isCorrect =
(==) "Phosphorus"
viewExplanation : Answer -> Html Never
viewExplanation userAnswer =
if isCorrect userAnswer then
H.text "Correct!"
else
H.span
[]
[ H.span [ HA.style "font-weight" "bold" ]
[ H.text "P" ]
, H.text " stands for "
, H.strong []
[ H.text "phosphorus"
]
, H.text <|
", while you have selected "
++ userAnswer
++ "."
]
in
createStateless
{ viewQuestion = viewQuestion
, viewRespanseField = viewResponseField
, isCorrect = isCorrect
, viewExplanation = viewExplanation
}Renders the quiz question. The Msgs from the quiz are mapped to your application's Msg using the first argument.
The functions viewQuestion and viewResponseField are provided by the Question value you passed as the last argument.
So if you have lots of Questions and want to show them at once, following code works.
import Array exposing (Array)
import Browser
import Html as H exposing (Html)
import Html.Attributes as HA
import Html.Events as HE
import Questions exposing (..)
type alias AppModel =
{ questions : Array Question
, userStates : Array UserState
}
initialQuestions : Array Question
initialQuestions =
[ createStateless
{ viewQuestion =
H.text "1+1 =?"
, viewResponseField =
List.range 1 9
|> List.map
(\n -> H.button [ HE.onClick n ] [ H.text <| String.fromInt n ])
|> H.div []
, isCorrect = (==) 2
, viewExplanation =
\n ->
if n == 2 then
H.text "Correct!"
else
H.text "Incorrect..."
}
, createStateless
{ viewQuestion =
H.text "2+1 =?"
, viewResponseField =
List.range 1 9
|> List.map
(\n -> H.button [ HE.onClick n ] [ H.text <| String.fromInt n ])
|> H.div []
, isCorrect = (==) 3
, viewExplanation =
\n ->
if n == 3 then
H.text "Correct!"
else
H.text "Incorrect..."
}
]
|> Array.fromList
type AppMsg
= NoOp
| UserHasAnswered Int UserState
| ModelUpdated Int Question
update : AppMsg -> AppModel -> AppModel
update msg model =
case msg of
NoOp ->
model
UserHasAnswered index userState ->
{ model
| userStates =
Array.set index userState model.userStates
}
ModelUpdated index question ->
{ model
| questions =
Array.set index question model.questions
}
view : AppModel -> Html AppMsg
view model =
List.map2
(\a b -> ( a, b ))
(Array.toList model.userStates)
(Array.toList model.questions)
|> List.indexedMap
(\n ( userState, question ) ->
mapView
(\msg ->
case msg of
Updated q ->
ModelUpdated n q
Marked m ->
UserHasAnswered n (HasAnswered m)
)
(\us { viewQuestion, viewResponseField } ->
H.div
[]
[ H.text "Q: "
, viewQuestion
, H.br [] []
, case us of
Answering ->
viewResponseField
HasAnswered m ->
let
( correct, explanation ) =
case m of
Correct e ->
( True, e )
Wrong e ->
( False, e )
in
H.map (always NoOp)
(if correct then
explanation
else
H.span [ HA.style "color" "red" ]
[ explanation ]
)
]
)
userState
question
)
|> H.div []
main : Program () AppModel AppMsg
main =
Browser.sandbox
{ init =
{ questions = initialQuestions
, userStates = Array.repeat (Array.length initialQuestions) Answering
}
, update = update
, view = view
}Returns view of question sentence. Use mapView for rendering purpose.
Returns view of response field where users make their answers. Use mapView for rendering purpose.
An opaque type that represent quiz question component. For now, the inner representation is:
where
Msgis defined below.