The example is as follows:
Subscribe to mouse clicks anywhere on screen. Maybe you need to create a custom drop down. You could listen for clicks when it is open, letting you know if someone clicked out of it:
import Browser.Events as Events
import Json.Decode as D
type Msg = ClickOut
subscriptions : Model -> Sub Msg
subscriptions model =
case model.dropDown of
Closed _ ->
Sub.none
Open _ ->
Events.onClick (D.succeed ClickOut)
The stated use case is exactly what I wanted to achieve. So I tried, but it seems not working.
https://ellie-app.com/4r8RQNZX4P4a1
module Main exposing (main)
import Browser
import Browser.Events
import Html exposing (..)
import Html.Attributes exposing (style)
import Html.Events exposing (onClick)
import Json.Decode exposing (succeed)
type alias Model =
{ dropDown : Bool }
initialModel : () -> ( Model, Cmd Msg )
initialModel _ =
( { dropDown = False }, Cmd.none )
type Msg
= DropDown Bool
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
DropDown isOpen ->
( { model | dropDown = isOpen }, Cmd.none )
view : Model -> Html Msg
view model =
div []
[ button [ onClick (DropDown (not model.dropDown)) ] [ text "Drop Down" ]
, ul
[ style "border" "solid black 1px"
, style "display" <|
if model.dropDown then
"block"
else
"none"
]
[ li [] [ text "Item 1" ]
, li [] [ text "Item 2" ]
]
]
subscriptions : Model -> Sub Msg
subscriptions model =
let
_ =
Debug.log "subCalled" model
in
if model.dropDown then
-- Browser.Events.onClick (succeed (DropDown False)) -- Toggle this line to see the bug
Sub.none
else
Sub.none
main : Program () Model Msg
main =
Browser.element
{ init = initialModel
, view = view
, update = update
, subscriptions = subscriptions
}
When you uncomment the commented line above to provide "click out", the dropdown button itself stops working.
Dropdown part is not shown at all even if you click the button.
Apparently, a click event is evaluated twice, within a single frame, before AND after model update?
To prove that, I introduced "step" state before activating "click out" subscription:
https://ellie-app.com/4r9djjXTZ4Fa1
module Main exposing (main)
import Browser
import Browser.Events
import Html exposing (..)
import Html.Attributes exposing (style)
import Html.Events exposing (onClick)
import Json.Decode exposing (succeed)
type alias Model =
{ dropDown : DD }
type DD
= JustOpened
| ReadyToClose
| Closed
initialModel : () -> ( Model, Cmd Msg )
initialModel _ =
( { dropDown = Closed }, Cmd.none )
type Msg
= Open
| GetReady
| Close
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Open ->
( { model | dropDown = JustOpened }, Cmd.none )
GetReady ->
( { model | dropDown = ReadyToClose }, Cmd.none )
Close ->
( { model | dropDown = Closed }, Cmd.none )
view : Model -> Html Msg
view model =
div []
[ button
[ onClick <|
case model.dropDown of
Closed ->
Open
_ ->
Close
]
[ text "Drop Down" ]
, ul
[ style "border" "solid black 1px"
, style "display" <|
case model.dropDown of
Closed ->
"none"
_ ->
"block"
]
[ li [] [ text "Item 1" ]
, li [] [ text "Item 2" ]
]
]
subscriptions : Model -> Sub Msg
subscriptions model =
case model.dropDown of
JustOpened ->
Browser.Events.onAnimationFrame (\_ -> GetReady)
ReadyToClose ->
Browser.Events.onClick (succeed Close)
Closed ->
Sub.none
main : Program () Model Msg
main =
Browser.element
{ init = initialModel
, view = view
, update = update
, subscriptions = subscriptions
}
This works, since it avoids suspected "double evaluation" by delaying state transition with onAnimationFrame
.
Actually, this problem had alreaday reported when it was Mouse.clicks
in Elm 0.18.
https://discourse.elm-lang.org/t/mouse-clicks-subscription-created-and-executed-following-click-event/1067
The problem continued in Browser.Events.onClick
but I do not see the issue on the repository, so let me file it here.