Elm - How Do I Detect Current Focus

elm

Solution

The elm-lang/dom package allows setting focus on an element given an ID but it does not allow you to fetch the currently focused element. It hints that you can use `document.activeElement` for this. To do that, you'll have to use ports.

Here is a contrived example. Let's say you have a `Model` that contains the currently selected id and a list of all ids of some textboxes we'll soon create.

type alias Model =
    { selected : Maybe String
    , ids : List String
    }

The Msgs we will use will be able to inquire about the focus as well as use the Dom library to set focus:

type Msg
    = NoOp
    | FetchFocused
    | FocusedFetched (Maybe String)
    | Focus (Maybe String)

For that, we will need two ports:

port focusedFetched : (Maybe String -> msg) -> Sub msg

port fetchFocused : () -> Cmd msg

The javascript calling these ports will report on the current `document.activeElement`:

var app = Elm.Main.fullscreen()
app.ports.fetchFocused.subscribe(function() {
  var id = document.activeElement ? document.activeElement.id : null;
  app.ports.focusedFetched.send(id);
});

The view displays the currently selected id, provides a list of buttons that will set the focus on one of the numbered textboxes below.

view : Model -> Html Msg
view model =
    div []
        [ div [] [ text ("Currently selected: " ++ toString model.selected) ]
        , div [] (List.map viewButton model.ids)
        , div [] (List.map viewInput model.ids)
        ]


viewButton : String -> Html Msg
viewButton id =
    button [ onClick (Focus (Just id)) ] [ text id ]


viewInput : String -> Html Msg
viewInput idstr =
    div [] [ input [ id idstr, placeholder idstr, onFocus FetchFocused ] [] ]

The `update` function ties it all together:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NoOp ->
            model ! []

        FetchFocused ->
            model ! [ fetchFocused () ]

        FocusedFetched selected ->
            { model | selected = selected } ! []

        Focus (Just selected) ->
            model ! [ Task.attempt (always NoOp) (Dom.focus selected), fetchFocused () ]

        Focus Nothing ->
            { model | selected = Nothing } ! [ fetchFocused () ]

Here is a working example on ellie-app.com.

Problem

How do you get the current focus in Elm? I know how to set focus with Elm, but I can't find any functionality to detect what currently has focus.

Original source