【问题标题】:Elm with webcomponents - Is this a bug or am I doing something wrong?Elm with webcomponents - 这是一个错误还是我做错了什么?
【发布时间】:2021-02-19 21:03:30
【问题描述】:

问题总结 我在 elm 0.19.1 中使用 webcomponents。 目标是当 webcomponent 属性“requeststate”改变时,webcomponent 发出一个事件。 事件已发出(我可以在 webcomponent 构造函数的日志中看到),但 elm 没有正确触发“WebcomponentEvent”。 问题出现在 Windows 10 和 ubuntu 16.04、firefox 和 chrome 上。 未测试旧版 elm。

重现步骤:

  • 我在这个最小的 repo 中重现了这个问题: https://github.com/Derryrover/elm_webcom_bug
  • 使用以下命令编译 elm: elm make src/Main.elm --output elm.js --debug
  • 使用网络服务器(例如“服务”)在本地托管
  • 单击“请求”按钮。这将更改 web 组件上的属性“requeststate”。这反过来会触发 web 组件上的自定义事件“创建”。但是 elm 没有收到这个事件..

打开 elm 调试器或再次单击“请求”按钮将神奇地使事件在 elm 中触发。奇怪的。 我也做了分支'tom-experiment'。在这个分支中,我从 webcomponent-click 工作中得到了事件,但前提是我直接点击了 webcomponent 本身。

这个问题的重要性 为什么我想通过更改属性来触发 Web 组件上的事件? 这种方法的目标也是 JavaScript 互操作。 例如,我现在可以使用这个 web 组件来创建日期时间或 uuid 或做一些其他的 javascript 魔术。这样我就可以完全解决端口问题。 这个问题的解决方案可能会解决整个 Javascript 互操作讨论!

代码

这是我的 Main.elm:

module Main exposing (..)

import Browser
import Html
import Html.Attributes
import Html.Events
import Json.Decode

main =
    Browser.sandbox
        { init = init
        , view = view
        , update = update
        }

type alias Model =
    { request : Bool
    , received: Bool
    }

init : Model
init = { request = False
       , received = False 
       }


type Msg
    = Request
    | Reset
    | WebcomponentEvent


update : Msg -> Model -> Model
update msg model =
    case msg of
        WebcomponentEvent ->
            { model | received = True}
        Request ->
            { model | request = True }
        Reset -> 
            { model | request = False , received = False}
        


view : Model -> Html.Html Msg
view model =
    Html.div []
        [ Html.button [Html.Events.onClick Request] [Html.text "Request"]
        , Html.div 
            [] 
            [ Html.text "requested:"
            , Html.text (if model.request then "true" else "false")
            ]
        , Html.div 
            [] 
            [ Html.text "received:"
            , Html.text (if model.received then "true" else "false")
            ]
        , Html.button [Html.Events.onClick Reset] [Html.text "Reset"]
        , Html.node "webcomponent-test" 
            [ Html.Events.on "created" (Json.Decode.succeed WebcomponentEvent) 
            , Html.Attributes.attribute "requeststate" (if model.request == True then "requested" else "idle")
            ] []
        ]

这是 webcomponent.js

class Webcomponent extends HTMLElement {

  static get observedAttributes() { 
    return [
    "requeststate"
    ]; 
  }
  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case  "requeststate":
        if (newValue === "requested") {
          console.log(`requested in webcomponent triggered`);
          const customEvent = new CustomEvent('created', {detail: ""});
          this.dispatchEvent(customEvent);
        }
    }
  }
  
  constructor() {
    super();
    
    this.addEventListener('created', function (e) {
      console.log("event triggered as sensed by javascript: ", e.detail, e);
    }, false, true);
    
  }
}

customElements.define('webcomponent-test', Webcomponent);

这是我的 index.html

<!doctype html>
<html>
  <head>
    <script type="module" src="./webcomponent.js"></script>
    <script src="./elm.js"></script>
  </head>
  <body>
    <div id="elm_element"></div>
    <script>
    var app = Elm.Main.init({
      node: document.getElementById('elm_element')
    });
    </script>
  </body>
</html>

【问题讨论】:

    标签: web-component elm


    【解决方案1】:

    这在 Elm Slack 上进行了讨论。这个问题最终成为时间问题。决议是从改变

    this.dispatchEvent(customEvent)
    

    requestAnimationFrame(() => this.dispatchEvent(customEvent))
    

    在自定义元素中,可以在这个 ellie https://ellie-app.com/cqGkT6xgwqKa1 中看到。

    感谢@antew 提供最终解决方案。

    【讨论】:

    • 作为对在这里找到自己方式的其他人的附加说明,Elm 确实有关于如何将自定义 Web 组件合并到 Elm 应用程序的建议:guide.elm-lang.org/interop/custom_elements.html 用于使用 Intl 等浏览器 API 并在此处演示.该页面上的链接中还有其他示例。
    【解决方案2】:

    还有一个潜在的原因:

    这个attributeChangedCallback运行之前元素连接到DOM

    attributeChangedCallback(name, oldValue, newValue) {
        switch (name) {
          case  "requeststate":
            if (newValue === "requested") {
              console.log(`requested in webcomponent triggered`);
              const customEvent = new CustomEvent('created', {detail: ""});
              this.dispatchEvent(customEvent);
            }
        }
      }
    

    添加

     connectedCallback(){
        console.log("Now I am ready to emit Events");
     }
    

    验证您的dispatchEvent 不会运行得太快。

    requestAnimationFrame(或setTimeout)是等到事件循环为空(因此 connectedCallback 运行)的解决方法

    (我不知道你的用例)你也可以在你的attributeChangedCallback 中测试oldValue === nullthis.isConnected

    另请注意,当涉及 shadowDOM 时,您可能需要在该 CustomEvent 上使用 bubbles:truecomposed:true

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-13
      • 1970-01-01
      • 1970-01-01
      • 2012-10-14
      • 1970-01-01
      • 1970-01-01
      • 2017-09-28
      • 1970-01-01
      相关资源
      最近更新 更多