Модальное окно в Elm без фреймворка

Я новичок в ELM и хочу создать модальное окно без использования каких-либо библиотек, таких как Bootstrap или ELM-UI. Я нашел этот простой пример в Интернете, который также использует декодирование JSON. Есть ли возможность, чтобы модальная работа работала просто без каких-либо фреймворков/библиотек и декодирования 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
        }

person EverydayDeveloper    schedule 04.10.2020    source источник


Ответы (1)


Если я правильно понимаю ваш вопрос, проблема, которую вы пытаетесь решить, заключается в том, чтобы щелкнуть за пределами модального окна, чтобы закрыть его. Декодирование объекта события для получения информации о 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
        }
person Igid    schedule 04.10.2020
comment
Как мне создать кнопку закрытия в модальном окне, которая поможет мне выйти из модального окна? Будет ли это решением не использовать декодирование JSON? Не могли бы вы, если возможно, обновить код с помощью кнопки закрытия внутри модального окна, которая закроет его? @Игид - person EverydayDeveloper; 05.10.2020
comment
Судя по вашему коду, вы знаете, как создать кнопку, которая выдает сообщение при нажатии. Поэтому вместо того, чтобы иметь onClick для каждого div, просто используйте его на кнопке x. Это, безусловно, избавило бы от необходимости импортировать Json.Decode. Но могу я спросить, почему вы изо всех сил пытаетесь избежать этого модуля/пакета? Если у вас есть приложение Elm любого разумного размера, вам рано или поздно придется его использовать тем или иным образом. - person Igid; 05.10.2020
comment
PS. Я отредактировал свой ответ, чтобы прояснить, что декодирование объекта event само по себе не является хакерским, оно использует его для получения информации/доступа к DOM. - person Igid; 05.10.2020
comment
Спасибо большое за вашу помощь. Я пытаюсь избежать JSON.Decode, потому что обычно мне нужна кнопка закрытия, которая выводит меня из модального окна. Я проверил предоставленный вами код, но он все еще не ответил на мой вопрос. В модальном окне, где есть кнопка увеличения/уменьшения, ниже я хочу создать кнопку закрытия, которая поможет мне закрыть модальное окно, а не щелкнуть снаружи. Не могли бы вы подсказать мне, как это сделать? Примером может служить prnt.sc/utg839. - person EverydayDeveloper; 05.10.2020