如何在Elm中设置元素的焦点?

47

如何在Elm中设置HTML元素的焦点? 我尝试在元素上设置autofocus属性,但它只会在页面加载时设置焦点。

6个回答

56

focus函数在elm-lang/dom包中使用Task来设置焦点(不使用任何port或JavaScript)。

内部它使用requestAnimationFrame来确保任何新的DOM更新在尝试查找要聚焦的DOM节点之前都已呈现。

一个使用示例:

type Msg
    = FocusOn String
    | FocusResult (Result Dom.Error ())

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        FocusOn id ->
            ( model, Dom.focus id |> Task.attempt FocusResult )

        FocusResult result ->
            -- handle success or failure here
            case result of
                Err (Dom.NotFound id) ->
                    -- unable to find dom 'id'
                Ok () ->
                    -- successfully focus the dom

Ellie上完整的示例


注意,这个确实有效,但是如果元素是动态添加的,我发现它可能会在意想不到的时候悄悄失败。 - AdrianoFerrari
你有一个这个失败的例子吗? - robertjlooby
很不幸,我现在没有可重现的例子。如果我找到了另一个,我会再次发布。总的来说,这绝对是正确的做法,所以在我找到具体情况之前请忽略我的评论 :) - AdrianoFerrari
2
如果元素不可见,设置焦点可能会悄悄失败。请参考:http://package.elm-lang.org/packages/elm-lang/dom/latest/Dom#focus - R D
7
如果使用 Elm 0.19 版本,该包现在已成为 elm/browser 的一部分。 - Ty Cobb

10

解决此问题的方法是使用Mutation Observers。将此JavaScript插入到您的主HTML页面或Elm代码的主视图中:

var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    handleAutofocus(mutation.addedNodes);
  });
});
var target = document.querySelector('body > div');
var config = { childList: true, subtree: true };
observer.observe(target, config);

function handleAutofocus(nodeList) {
  for (var i = 0; i < nodeList.length; i++) {
    var node = nodeList[i];
    if (node instanceof Element && node.hasAttribute('data-autofocus')) {
      node.focus();
      break;
    } else {
      handleAutofocus(node.childNodes);
    }
  }
}

然后通过包含Html.Attributes.attribute "data-autofocus" ""来创建HTML元素。

5

这似乎不太可靠,我在这里开了一个问题。 - davetapley
是的,Dom.focus id |> Task.attempt FocusResult 更加可靠。 - Natim
这也需要在文件中导入Browser.Dom,示例在此处 https://package.elm-lang.org/packages/elm/core/latest/Task#attempt - Nick

4

我最近花了很多时间探索这个问题。不幸的是,我认为使用现有的elm-html库是不可能的。然而,我想出了一个方法,利用css动画来触发事件并将其嵌入到纯js中。

以下是我在Elm中使用script节点和style节点的hack。我认为它非常丑陋。

import Html exposing (div, button, text, input, node)
import Html.Events exposing (onClick)
import Html.Attributes exposing (type', class)
import StartApp.Simple

main =
  StartApp.Simple.start { model = model, view = view, update = update }

model = []

view address model =
  -- View now starts with a <style> and <script> (hacky)
  (node "style" [] [ Html.text style ]) ::
  (node "script" [] [Html.text script ]) ::
  (button [ onClick address AddInput ] [ text "Add Input" ]) ::
  model |>
  div []    

type Action = AddInput 

update action model =
  case action of
    AddInput -> (Html.p [] [input [type' "text", class "focus"] []]) :: model

-- Use pure string css (hacky)

style = """
.focus {
  animation-name: set-focus;
  animation-duration: 0.001s;
  -webkit-animation-name: set-focus;
  -webkit-animation-duration: 0.001s;
}
@-webkit-keyframes set-focus {
    0%   {color: #fff}
}
@keyframes set-focus {
    0%   {color: #fff}
}
"""

-- Cheating by embedding pure javascript... (hacky)

script = """
var insertListener = function(event){
 if (event.animationName == "set-focus") {
   event.target.focus();
 }               
}
document.addEventListener("animationstart", insertListener, false); // standard + firefox
document.addEventListener("MSAnimationStart", insertListener, false); // IE
document.addEventListener("webkitAnimationStart", insertListener, false); // Chrome + Safari
"""

3
在 Elm 0.19 中,要使用 Browser.Dom.focus
import Browser.Dom as Dom
import Task

type Msg
    = NoOp

focusSearchBox : Cmd Msg
focusSearchBox =
    Task.attempt (\_ -> NoOp) (Dom.focus "search-box")

如果像上面一样聚焦失败,您可以选择忽略,或通过触发更新消息来采取行动。


1
Elm 0.19的Browser.Dom.focus是现代化的解决方案。
import Browser.Dom as Dom
import Task

type Msg
    = NoOp
    | Focus String

focusElement : String -> Cmd Msg
focusElement htmlId =
    Task.attempt (\_ -> NoOp) (Dom.focus htmlId)


update : Msg -> Model -> (Model, Cmd Msg)
update msg =
    case msg of
        Focus htmlId ->
            ( model, focusElement htmlId )
        NoOp ->
            ( model, Cmd.none )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接