This is an alternative site for discovering Elm packages. You may be looking for the official Elm package site instead.
1.0.0

Bootstrap.Modal

Modals are streamlined, but flexible dialog prompts. They support a number of use cases from user notifications to completely custom content and feature a handful of helpful subcomponents, sizes, and more.

type alias Model =
    { modalVisibility : Modal.Visibility }

init : ( Model, Cmd Msg )
init =
    ( { modalVisibility = Modal.hidden }, Cmd.none )

type Msg
    = CloseModal
    | ShowModal

update : Msg -> Model -> ( Model, Cmd msg )
update msg model =
    case msg of
        CloseModal ->
            ( { model | modalVisibility = Modal.hidden }
            , Cmd.none
            )

        ShowModal ->
            ( { model | modalVisibility = Modal.shown }
            , Cmd.none
            )

view : Model -> Html msg
view model =
    Grid.container []
        [ Button.button
            [ Button.attrs [ onClick ShowModal ] ]
            [ text "Show modal" ]
        , Modal.config CloseModal
            |> Modal.small
            |> Modal.h5 [] [ text "Modal header" ]
            |> Modal.body []
                [ Grid.containerFluid []
                    [ Grid.row []
                        [ Grid.col
                            [ Col.xs6 ]
                            [ text "Col 1" ]
                        , Grid.col
                            [ Col.xs6 ]
                            [ text "Col 2" ]
                        ]
                    ]
                ]
            |> Modal.footer []
                [ Button.button
                    [ Button.outlinePrimary
                    , Button.attrs [ onClick CloseModal ]
                    ]
                    [ text "Close" ]
                ]
            |> Modal.view model.modalVisibility
        ]

NOTE: Don't try to open several modals at the same time. It probably won't end well.

Modal

view : Visibility -> Config msg -> Html.Html msg

Create a modal for your application

  • show Whether to display the modal or not (if False the content is still in the dom, but hidden). You need to keep track of this state in your model
  • config View configuration
config : msg -> Config msg

Create an initial modal config. You can enrich the config by using the header, body, footer and option related functions.

type Config msg = Config (ConfigRec msg)

Opaque type representing the view config for a model. Use the config function to create an initial config.

State

hidden : Visibility

The modal should be hidden

shown : Visibility

The modal should be made visible.

type Visibility = Show | StartClose | FadeClose | Hide

Visibility state for the modal

Modal options

small : Config msg -> Config msg

Option to make a modal smaller than the default

large : Config msg -> Config msg

Option to make a modal larger than the default

hideOnBackdropClick : Bool -> Config msg -> Config msg

Option to trigger close message when the user clicks on the modal backdrop. Default True.

Header

header : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Create a header for a modal, typically for titles, but you can be imaginative

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure header for
h1 : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Creates a modal header with a h1 title child element

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure header for
h2 : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Creates a modal header with a h2 title child element

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure header for
h3 : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Creates a modal header with a h3 title child element

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure header for
h4 : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Creates a modal header with a h4 title child element

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure header for
h5 : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Creates a modal header with a h5 title child element

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure header for
h6 : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Creates a modal header with a h6 title child element

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure header for
type Header msg = Header (Item msg)

Opaque type representing a modal header

Body

body : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Create a body for a modal, you would typically always create a body for a modal

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure body for
type Body msg = Body (Item msg)

Opaque type representing a modal body

Footer

footer : List (Html.Attribute msg) -> List (Html.Html msg) -> Config msg -> Config msg

Create a footer for a modal. Normally used for action buttons, but you might be creative

  • attributes List of attributes
  • children List of child elements
  • config configuration settings to configure header for
type Footer msg = Footer (Item msg)

Opaque type representing a modal body

Animated Modals

When you want your modal to support an animation when displayed and closed. There is a few more things you must wire-up and keep in mind.

withAnimation : (Visibility -> msg) -> Config msg -> Config msg

Configure the modal to support fade-in/out animations. You'll need to provide a message to handle animation.

subscriptions : Visibility -> (Visibility -> msg) -> Sub msg

Subscription for handling animations

hiddenAnimated : Visibility

When using animations use this state for handling custom close buttons etc.

Button.button
    [ Button.outlinePrimary
    , Button.attrs [ onClick <| CloseModalAnimated Modal.hiddenAnimated ]
    ]
    [ text "Close" ]

Example

type Msg
    = ShowModal
      -- Note the extra msg constructor needed
    | AnimateModal Modal.Visibility
    | CloseModal

update : Msg -> State -> State
update msg state =
    case msg of
        CloseModal ->
            { state | modalVisibility = Modal.hidden }

        ShowModal ->
            { state | modalVisibility = Modal.shown }

        -- You need to handle the extra animation message
        AnimateModal visibility ->
            { state | modalVisibility = visibility }


-- Animations for modal doesn't work without a subscription.
-- DON´T forget this !

subscriptions : Model -> Sub msg
subscriptions model =
    Sub.batch
        [ Modal.subscriptions model.modalVisibility AnimateModal ]

view : Model -> Html msg
view model =
    Grid.container []
        [ Button.button
            [ Button.attrs [ onClick ShowModal ] ]
            [ text "Show modal" ]
        , Modal.config CloseModal
            |> Modal.h5 [] [ text "Modal header" ]
            |> Modal.body [] [ text "Modal body" ]
            |> Modal.footer []
                [ Button.button
                    [ Button.outlinePrimary
                    , Button.attrs [ onClick <| AnimateModal Modal.hiddenAnimated ]
                    ]
                    [ text "Close" ]
                ]
            |> Modal.view model.modalVisibility
        ]
module Bootstrap.Modal
    exposing
        ( view
        , hidden
        , shown
        , hiddenAnimated
        , Visibility
        , config
        , header
        , body
        , footer
        , small
        , large
        , hideOnBackdropClick
        , withAnimation
        , subscriptions
        , h1
        , h2
        , h3
        , h4
        , h5
        , h6
        , Header
        , Body
        , Footer
        , Config
        )

{-| Modals are streamlined, but flexible dialog prompts. They support a number of use cases from user notifications to completely custom content and feature a handful of helpful subcomponents, sizes, and more.

    type alias Model =
        { modalVisibility : Modal.Visibility }

    init : ( Model, Cmd Msg )
    init =
        ( { modalVisibility = Modal.hidden }, Cmd.none )

    type Msg
        = CloseModal
        | ShowModal

    update : Msg -> Model -> ( Model, Cmd msg )
    update msg model =
        case msg of
            CloseModal ->
                ( { model | modalVisibility = Modal.hidden }
                , Cmd.none
                )

            ShowModal ->
                ( { model | modalVisibility = Modal.shown }
                , Cmd.none
                )

    view : Model -> Html msg
    view model =
        Grid.container []
            [ Button.button
                [ Button.attrs [ onClick ShowModal ] ]
                [ text "Show modal" ]
            , Modal.config CloseModal
                |> Modal.small
                |> Modal.h5 [] [ text "Modal header" ]
                |> Modal.body []
                    [ Grid.containerFluid []
                        [ Grid.row []
                            [ Grid.col
                                [ Col.xs6 ]
                                [ text "Col 1" ]
                            , Grid.col
                                [ Col.xs6 ]
                                [ text "Col 2" ]
                            ]
                        ]
                    ]
                |> Modal.footer []
                    [ Button.button
                        [ Button.outlinePrimary
                        , Button.attrs [ onClick CloseModal ]
                        ]
                        [ text "Close" ]
                    ]
                |> Modal.view model.modalVisibility
            ]

**NOTE:** Don't try to open several modals at the same time. It probably won't end well.


# Modal

@docs view, config, Config


# State

@docs hidden, shown, Visibility


# Modal options

@docs small, large, hideOnBackdropClick


# Header

@docs header, h1, h2, h3, h4, h5, h6, Header


# Body

@docs body, Body


# Footer

@docs footer, Footer


# Animated Modals

When you want your modal to support an animation when displayed and closed. There
is a few more things you must wire-up and keep in mind.

@docs withAnimation, subscriptions, hiddenAnimated


## Example

    type Msg
        = ShowModal
          -- Note the extra msg constructor needed
        | AnimateModal Modal.Visibility
        | CloseModal

    update : Msg -> State -> State
    update msg state =
        case msg of
            CloseModal ->
                { state | modalVisibility = Modal.hidden }

            ShowModal ->
                { state | modalVisibility = Modal.shown }

            -- You need to handle the extra animation message
            AnimateModal visibility ->
                { state | modalVisibility = visibility }


    -- Animations for modal doesn't work without a subscription.
    -- DON´T forget this !

    subscriptions : Model -> Sub msg
    subscriptions model =
        Sub.batch
            [ Modal.subscriptions model.modalVisibility AnimateModal ]

    view : Model -> Html msg
    view model =
        Grid.container []
            [ Button.button
                [ Button.attrs [ onClick ShowModal ] ]
                [ text "Show modal" ]
            , Modal.config CloseModal
                |> Modal.h5 [] [ text "Modal header" ]
                |> Modal.body [] [ text "Modal body" ]
                |> Modal.footer []
                    [ Button.button
                        [ Button.outlinePrimary
                        , Button.attrs [ onClick <| AnimateModal Modal.hiddenAnimated ]
                        ]
                        [ text "Close" ]
                    ]
                |> Modal.view model.modalVisibility
            ]

-}

import Html
import Html.Attributes as Attr
import Html.Events as Events
import Bootstrap.General.Internal exposing (ScreenSize(..), screenSizeOption)
import AnimationFrame
import Json.Decode as Json
import DOM


{-| Visibility state for the modal
-}
type Visibility
    = Show
    | StartClose
    | FadeClose
    | Hide


{-| The modal should be made visible.
-}
shown : Visibility
shown =
    Show


{-| The modal should be hidden
-}
hidden : Visibility
hidden =
    Hide


{-| When using animations use this state for handling custom close buttons etc.

    Button.button
        [ Button.outlinePrimary
        , Button.attrs [ onClick <| CloseModalAnimated Modal.hiddenAnimated ]
        ]
        [ text "Close" ]

-}
hiddenAnimated : Visibility
hiddenAnimated =
    StartClose


{-| Subscription for handling animations
-}
subscriptions : Visibility -> (Visibility -> msg) -> Sub msg
subscriptions visibility animateMsg =
    case visibility of
        StartClose ->
            AnimationFrame.times (\_ -> animateMsg FadeClose)

        _ ->
            Sub.none


{-| Opaque type representing the view config for a model. Use the [`config`](#config) function to create an initial config.
-}
type Config msg
    = Config (ConfigRec msg)


type alias ConfigRec msg =
    { closeMsg : msg
    , withAnimation : Maybe (Visibility -> msg)
    , header : Maybe (Header msg)
    , body : Maybe (Body msg)
    , footer : Maybe (Footer msg)
    , options : Options
    }


type alias Options =
    { modalSize : Maybe ScreenSize
    , hideOnBackdropClick : Bool
    , centered : Bool
    }


{-| Opaque type representing a modal header
-}
type Header msg
    = Header (Item msg)


{-| Opaque type representing a modal body
-}
type Body msg
    = Body (Item msg)


{-| Opaque type representing a modal body
-}
type Footer msg
    = Footer (Item msg)


type alias Item msg =
    { attributes : List (Html.Attribute msg)
    , children : List (Html.Html msg)
    }


{-| Option to make a modal smaller than the default
-}
small : Config msg -> Config msg
small (Config ({ options } as config)) =
    Config { config | options = { options | modalSize = Just SM } }


{-| Option to make a modal larger than the default
-}
large : Config msg -> Config msg
large (Config ({ options } as config)) =
    Config { config | options = { options | modalSize = Just LG } }


{-| Option to trigger close message when the user clicks on the modal backdrop. Default True.
-}
hideOnBackdropClick : Bool -> Config msg -> Config msg
hideOnBackdropClick hide (Config ({ options } as config)) =
    Config { config | options = { options | hideOnBackdropClick = hide } }


{-| Configure the modal to support fade-in/out animations. You'll need to provide
a message to handle animation.
-}
withAnimation : (Visibility -> msg) -> Config msg -> Config msg
withAnimation animateMsg (Config config) =
    Config { config | withAnimation = Just animateMsg }


{-| Create a modal for your application

  - `show` Whether to display the modal or not (if `False` the content is still in the dom, but hidden). You need to keep track of this state in your model
  - `config` View configuration

-}
view :
    Visibility
    -> Config msg
    -> Html.Html msg
view visibility (Config ({ body, footer, options } as config)) =
    Html.div
        []
        ([ Html.div
            ([ Attr.tabindex -1] ++ display visibility config)
            [ Html.div
                ([ Attr.attribute "role" "document"
                 , Attr.class "elm-bootstrap-modal"
                 ]
                    ++ modalAttributes options
                    ++ (if options.hideOnBackdropClick then
                            [ Events.on "click" (containerClickDecoder config.closeMsg) ]
                        else
                            []
                       )
                )
                [ Html.div
                    [ Attr.class "modal-content" ]
                    (List.filterMap
                        identity
                        [ renderHeader config
                        , renderBody body
                        , renderFooter footer
                        ]
                    )
                ]
            ]
         ]
            ++ backdrop visibility config
        )


containerClickDecoder: msg -> Json.Decoder msg
containerClickDecoder closeMsg =
    DOM.target DOM.className
        |> Json.andThen
            (\c ->
                if String.contains "elm-bootstrap-modal" c then
                    Json.succeed closeMsg
                else
                    Json.fail "ignoring"
            )


display : Visibility -> ConfigRec msg -> List (Html.Attribute msg)
display visibility config =
    case visibility of
        Show ->
            [ Attr.style
                [ ( "pointer-events", "none" )
                , ( "display", "block" )
                ]
            , Attr.classList
                [ ( "modal", True )
                , ( "fade", isFade config )
                , ( "show", True )
                ]
            ]

        StartClose ->
            [ Attr.style
                [ ( "pointer-events", "none" )
                , ( "display", "block" )
                ]
            , Attr.classList
                [ ( "modal", True )
                , ( "fade", True )
                , ( "show", True )
                ]
            ]

        FadeClose ->
            [ Attr.style
                [ ( "pointer-events", "none" )
                , ( "display", "block" )
                ]
            , Attr.classList
                [ ( "modal", True )
                , ( "fade", True )
                , ( "show", False )
                ]
            , Events.on "transitionend" (Json.succeed config.closeMsg)
            ]

        Hide ->
            [ Attr.style [ ( "height", "0px" ), ( "display", "block" ) ]
            , Attr.classList
                [ ( "modal", True )
                , ( "fade", isFade config )
                , ( "show", False )
                ]
            ]


isFade : ConfigRec msg -> Bool
isFade config =
    Maybe.map (\_ -> True) config.withAnimation |> Maybe.withDefault False


{-| Create an initial modal config. You can enrich the config by using the header, body, footer and option related functions.
-}
config : msg -> Config msg
config closeMsg =
    Config
        { closeMsg = closeMsg
        , withAnimation = Nothing
        , options =
            { modalSize = Nothing
            , hideOnBackdropClick = True
            , centered = True
            }
        , header = Nothing
        , body = Nothing
        , footer = Nothing
        }


{-| Create a header for a modal, typically for titles, but you can be imaginative

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure header for

-}
header :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
header attributes children (Config config) =
    Config
        { config
            | header =
                Just <|
                    Header
                        { attributes = attributes
                        , children = children
                        }
        }


{-| Creates a modal header with a h1 title child element

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure header for

-}
h1 :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
h1 =
    titledHeader Html.h1


{-| Creates a modal header with a h2 title child element

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure header for

-}
h2 :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
h2 =
    titledHeader Html.h2


{-| Creates a modal header with a h3 title child element

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure header for

-}
h3 :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
h3 =
    titledHeader Html.h3


{-| Creates a modal header with a h4 title child element

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure header for

-}
h4 :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
h4 =
    titledHeader Html.h4


{-| Creates a modal header with a h5 title child element

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure header for

-}
h5 :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
h5 =
    titledHeader Html.h5


{-| Creates a modal header with a h6 title child element

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure header for

-}
h6 :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
h6 =
    titledHeader Html.h6


titledHeader :
    (List (Html.Attribute msg) -> List (Html.Html msg) -> Html.Html msg)
    -> List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
titledHeader itemFn attributes children =
    header []
        [ itemFn (Attr.class "modal-title" :: attributes) children ]


{-| Create a body for a modal, you would typically always create a body for a modal

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure body for

-}
body :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
body attributes children (Config config) =
    { config
        | body =
            Just <|
                Body
                    { attributes = attributes
                    , children = children
                    }
    }
        |> Config


{-| Create a footer for a modal. Normally used for action buttons, but you might be creative

  - `attributes` List of attributes
  - `children` List of child elements
  - `config` configuration settings to configure header for

-}
footer :
    List (Html.Attribute msg)
    -> List (Html.Html msg)
    -> Config msg
    -> Config msg
footer attributes children (Config config) =
    { config
        | footer =
            Just <|
                Footer
                    { attributes = attributes
                    , children = children
                    }
    }
        |> Config


modalAttributes : Options -> List (Html.Attribute msg)
modalAttributes options =
    [ Attr.classList
        [ ( "modal-dialog", True )
        , ( "modal-dialog-centered", options.centered )
        ]
    , Attr.style [ ( "pointer-events", "auto" ) ]
    ]
        ++ (Maybe.map modalClass options.modalSize
                |> Maybe.withDefault []
           )


modalClass : ScreenSize -> List (Html.Attribute msg)
modalClass size =
    case screenSizeOption size of
        Just s ->
            [ Attr.class <| "modal-" ++ s ]

        Nothing ->
            []


renderHeader : ConfigRec msg -> Maybe (Html.Html msg)
renderHeader ({ header } as config) =
    case header of
        Just (Header cfg) ->
            Html.div
                (Attr.class "modal-header" :: cfg.attributes)
                (cfg.children ++ [ closeButton <| getCloseMsg config ])
                |> Just

        Nothing ->
            Nothing


getCloseMsg : ConfigRec msg -> msg
getCloseMsg config =
    case config.withAnimation of
        Just animationMsg ->
            animationMsg StartClose

        Nothing ->
            config.closeMsg


renderBody : Maybe (Body msg) -> Maybe (Html.Html msg)
renderBody maybeBody =
    case maybeBody of
        Just (Body cfg) ->
            Html.div
                (Attr.class "modal-body" :: cfg.attributes)
                cfg.children
                |> Just

        Nothing ->
            Nothing


renderFooter : Maybe (Footer msg) -> Maybe (Html.Html msg)
renderFooter maybeFooter =
    case maybeFooter of
        Just (Footer cfg) ->
            Html.div
                (Attr.class "modal-footer" :: cfg.attributes)
                cfg.children
                |> Just

        Nothing ->
            Nothing


closeButton : msg -> Html.Html msg
closeButton closeMsg =
    Html.button
        [ Attr.class "close", Events.onClick <| closeMsg ]
        [ Html.text "×" ]


backdrop : Visibility -> ConfigRec msg -> List (Html.Html msg)
backdrop visibility config =
    let
        attributes =
            case visibility of
                Show ->
                    [ Attr.classList
                        [ ( "modal-backdrop", True )
                        , ( "fade", isFade config )
                        , ( "show", True )
                        ]
                    ]
                        ++ if config.options.hideOnBackdropClick then
                            [ Events.onClick <| getCloseMsg config ]
                           else
                            []

                StartClose ->
                    [ Attr.classList
                        [ ( "modal-backdrop", True )
                        , ( "fade", True )
                        , ( "show", True )
                        ]
                    ]

                FadeClose ->
                    [ Attr.classList
                        [ ( "modal-backdrop", True )
                        , ( "fade", True )
                        , ( "show", False )
                        ]
                    ]

                Hide ->
                    [ Attr.classList
                        [ ( "modal-backdrop", False )
                        , ( "fade", isFade config )
                        , ( "show", False )
                        ]
                    ]
    in
        [ Html.div attributes [] ]