没有框架的 Elm 中的模态

Modal in Elm without framework

我是 ELM 的新手,我想创建一个模式而不使用任何库,例如 Bootstrap 或 ELM-UI。我在网上找到了这个同样使用 JSON 解码的简单示例。是否有可能在没有任何 framework/library 和 JSON 解码的情况下让模态工作?我如何修改代码以简单地获得工作模式?

module Main exposing (main)

import Browser
import Html exposing (Html, Attribute, button, div, span, text)
import Html.Events exposing (onClick, on)
import Html.Attributes exposing (class, style)
import Json.Decode as Decode


type alias Model =
    { isVisible : Bool, count : Int }


initialModel : Model
initialModel =
    { isVisible = False, count = 0 }


type Msg
    = Show
    | Hide
    | Increment
    | Decrement


update : Msg -> Model -> Model
update msg model =
    case msg of
        Show ->
            { model | isVisible = True }

        Hide ->
            { model | isVisible = False }
            
        Increment ->
            { model | count = model.count + 1 }
            
        Decrement ->
            { model | count = model.count - 1 }


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Show ] [ text "Show!" ]
        , if model.isVisible then
            div
                ([ class dialogContainerClass
                 , on "click" (containerClickDecoder Hide)
                 ]
                    ++ dialogContainerStyle
                )
                [ div dialogContentStyle
                    [ span [] [ text "Click anywhere outside this dialog to close it!" ]
                    , span [] [ text "Clicking on anything inside of this dialog works as normal." ]
                    , div []
                        [ button [ onClick Decrement ] [ text "-" ]
                        , text (String.fromInt model.count)
                        , button [ onClick Increment ] [ text "+" ]
                        ]
                    ]
                ]
          else
            div [] []
        ]


dialogContainerClass : String
dialogContainerClass = "dialog-container-class"


containerClickDecoder : msg -> Decode.Decoder msg
containerClickDecoder closeMsg =
    Decode.at [ "target", "className" ] Decode.string
        |> Decode.andThen
            (\c ->
                if String.contains dialogContainerClass c then
                    Decode.succeed closeMsg

                else
                    Decode.fail "ignoring"
            )



dialogContainerStyle : List (Attribute msg)
dialogContainerStyle =
    [ style "position" "absolute"
    , style "top" "0"
    , style "bottom" "0"
    , style "right" "0"
    , style "left" "0"
    , style "display" "flex"
    , style "align-items" "center"
    , style "justify-content" "center"
    , style "background-color" "rgba(33, 43, 54, 0.4)"
    ]
    
    
dialogContentStyle : List (Attribute msg)
dialogContentStyle =
    [ style "border-style" "solid"
    , style "border-radius" "3px"
    , style "border-color" "white"
    , style "background-color" "white"
    , style "height" "120px"
    , style "width" "440px"
    , style "display" "flex"
    , style "flex-direction" "column"
    , style "align-items" "center"
    , style "justify-content" "center"
    ]


main : Program () Model Msg
main =
    Browser.sandbox
        { init = initialModel
        , view = view
        , update = update
        }

如果我对你的问题的理解正确,那么你要解决的问题是在模式外单击以将其关闭。解码事件对象以获取有关 DOM 的信息是 Elm 中的一个小技巧——我认为您尝试避免它是正确的,除非有必要。实现相同目的的一种方法是将带有 stop propagation 的点击事件处理程序添加到模态内容中——这会阻止点击事件在容器中触发时从模态内部触发。

我已将您的示例代码放入 Ellie 中并做了一些小改动:https://ellie-app.com/b9gDPHgtz2ca1

此解决方案使用 Html.Events.stopPropagationOn,它类似于 on,但会调用 event.stopPropagation()。这个函数确实需要你提供解码器,所以恐怕你无法避免导入Json.Decode,但我们使用的是最简单的解码器 – Decode.succeed——而且只满足函数的参数。

我已将 NoOp 变体添加到 Msg,因为单击模态框时无事可做;只需附加此事件处理程序即可阻止 Hide 事件在我们不希望触发时触发。

代码

module Main exposing (main)

import Browser
import Html exposing (Attribute, Html, button, div, span, text)
import Html.Attributes exposing (class, style)
import Html.Events exposing (on, onClick)
import Json.Decode as Decode


type alias Model =
    { isVisible : Bool, count : Int }


initialModel : Model
initialModel =
    { isVisible = False, count = 0 }


type Msg
    = Show
    | Hide
    | Increment
    | Decrement
    | NoOp


update : Msg -> Model -> Model
update msg model =
    case msg of
        Show ->
            { model | isVisible = True }

        Hide ->
            { model | isVisible = False }

        Increment ->
            { model | count = model.count + 1 }

        Decrement ->
            { model | count = model.count - 1 }

        NoOp ->
            model


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Show ] [ text "Show!" ]
        , if model.isVisible then
            div
                (onClick Hide
                    :: dialogContainerStyle
                )
                [ div
                    (onClickStopPropagation NoOp
                        :: dialogContentStyle
                    )
                    [ span [] [ text "Click anywhere outside this dialog to close it!" ]
                    , span [] [ text "Clicking on anything inside of this dialog works as normal." ]
                    , div []
                        [ button [ onClick Decrement ] [ text "-" ]
                        , text (String.fromInt model.count)
                        , button [ onClick Increment ] [ text "+" ]
                        ]
                    ]
                ]

          else
            div [] []
        ]


onClickStopPropagation : msg -> Html.Attribute msg
onClickStopPropagation msg =
    Html.Events.stopPropagationOn "click" <| Decode.succeed ( msg, True )


dialogContainerStyle : List (Attribute msg)
dialogContainerStyle =
    [ style "position" "absolute"
    , style "top" "0"
    , style "bottom" "0"
    , style "right" "0"
    , style "left" "0"
    , style "display" "flex"
    , style "align-items" "center"
    , style "justify-content" "center"
    , style "background-color" "rgba(33, 43, 54, 0.4)"
    ]


dialogContentStyle : List (Attribute msg)
dialogContentStyle =
    [ style "border-style" "solid"
    , style "border-radius" "3px"
    , style "border-color" "white"
    , style "background-color" "white"
    , style "height" "120px"
    , style "width" "440px"
    , style "display" "flex"
    , style "flex-direction" "column"
    , style "align-items" "center"
    , style "justify-content" "center"
    ]


main : Program () Model Msg
main =
    Browser.sandbox
        { init = initialModel
        , view = view
        , update = update
        }