如何创建一个像表单元素一样的Web组件?

9

我正在尝试创建一个可以在表单元素中使用的 Web 组件,该组件具有 namevalue。我知道可以创建一个扩展了 HTMLInputElement 的 Web 组件:

<input is="very-extended">

但我正在尝试创建一个全新的元素。

创建普通 web 组件时,您可以从常规 HTMLElement (HTMLElement.prototype) 的原型中创建它。这让我认为我可以使用 HTMLInputElement (HTMLInputElement.prototype) 的原型创建不同的元素。实际上,您在扩展输入元素的 API 时使用了该原型,那么为什么我不能使用该原型来创建完全适用于表单的新元素?

如果您查看常规输入字段的阴影 DOM:

enter image description here

您会发现里面有一个 div。我理解这个 HTMLInputElement 有方法和属性、getter/setter 等等。那么当我尝试创建我的组件时,为什么它无法成为表单中找到的名称-值对的一部分呢?

以下是我尝试创建此 Web 组件的示例:

请注意,这应在支持 Web 组件的浏览器中进行测试。

(function() {

  var iconDoc = (document._currentScript || document.currentScript).ownerDocument;
  var objectPrototype = Object.create(HTMLInputElement.prototype);

  Object.defineProperty(objectPrototype, 'name', {
    writable: true
  });

  Object.defineProperty(objectPrototype, 'value', {
    writable: true
  });

  objectPrototype.createdCallback = function() {
    var shadow = this.createShadowRoot();
    var template = iconDoc.querySelector('#test');
    shadow.appendChild(template.content.cloneNode(true));
  };

  document.registerElement('custom-input', {
    prototype: objectPrototype
  });

})();

console.log(
  $('form').serialize()
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<template id="test">
  <div>This is a special input</div>
</template>

<form>
  <input name="regular" value="input">
  <custom-input name="foo" value="bar"></custom-input>
</form>

为什么名称和值对在表单中找不到,如何创建自定义表单元素?
3个回答

5
您可以创建一个<custom-input>自定义元素,它将被一个form解析,只需在您的template中添加一个hiddeninput元素和您想要的namevalue对即可。
<template>
    <input type="hidden" name="foo" value="defaultVal">
</template>

默认的value(和name)可以通过您的自定义元素内部逻辑进行更新。

为了被容器form检测到,这个隐藏的input 不能被插入到 Shadow DOM 中。

(function() {

  var iconDoc = (document._currentScript || document.currentScript).ownerDocument;
  var objectPrototype = Object.create(HTMLInputElement.prototype);

  objectPrototype.createdCallback = function() {
    //var shadow = this.createShadowRoot();
    var template = iconDoc.querySelector('#test');
    this.appendChild(template.content.cloneNode(true));
  };

  document.registerElement('custom-input', {
    prototype: objectPrototype
  });

})();

console.log(
  $('form').serialize()
)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<template id="test">
  <input type="hidden" name="foo" value="bar">
</template>

<form>
  <input name="regular" value="input">
  <custom-input name="foo" value="bar"></custom-input>
</form>


1
好主意。唯一的缺点是,由于隐藏输入必须在自定义输入的Light-DOM中而不是Shadow-DOM中,因此它对JavaScript和CSS不是隐藏的。此外,您必须保持常规输入的内容与自定义输入的内容同步更新。双向:自定义输入还应该监视其内部隐藏输入的外部更改。其他第三方脚本将无法将自定义输入识别为输入。无论如何,为什么需要这样做?我想这是因为大多数人都讨厌“is”符号。 - MarcG
1
这不是缺点,而是优势。外部脚本和库可以看到输入。更新对于定制的输入来说是固有的。 - Supersharp
1
从我所看到的来看,如果你扩展HTMLInputElement原型并指定extends: input,你仍然无法添加到输入框的shadow DOM中。这似乎非常有限制性。如果你想要向DOM中添加东西,似乎没有选择余地。 - Walt
1
@Supersharp,你能发一个例子吗?我认为这会更有助于理解这个答案。我还没有看到访问现有阴影根的方法。如果你尝试创建自己的阴影根,你会收到一个错误提示:无法在已经托管用户代理阴影树的主机上创建阴影根 - Walt
@WaltWeidner,你说得对。似乎无法为此元素添加阴影。据我所知,使用隐藏输入是唯一有效的方法。 - Supersharp
@WaltWeidner 我相信你现在会遇到这个错误,是因为影子 DOM 规范在 V0 和 V1 之间最近发生了变化。请参见此处的“多个 Shadow Roots”:http://hayato.io/2016/shadowdomv1/ - MarcG

2

KevBot,

您似乎认为元素会在表单中包含自身。但事实并非如此。表单通过标签名称搜索其子元素,以确定它应该包含哪些元素。它将简单地忽略那些具有未知标记名称的元素。

您的custom-input名称不在表单搜索的元素之列,因此它不会包含在表单中。不管自定义元素的原型如何,这都没有关系。这就是为什么如果使用is,它能工作,因为这样可以维护标记名称。

当然,如果您想要,您可以实现自己的custom-form来进行不同的行为。


我尝试在扩展内置元素的自定义元素中设置值。例如<input is="calendar">,但始终无法使其工作。你能做到吗? - Walt
Walt,你的自定义输入原型应该扩展HTMLInputElement。然后你应该正常设置输入值(value属性)。它应该可以工作,是的。 - MarcG
看起来我只是在回调函数创建之前尝试设置了值。这导致Chrome抛出了一个关于非法访问的错误。 - Walt

2
你可以这样做:

(function() {
  var newInputExtended = Object.create(HTMLInputElement.prototype);

  newInputExtended.createdCallback = function() {
    this.value = 'baz';
  };

  document.registerElement('foo-input', {
    extends: 'input',
    prototype: newInputExtended
  });
  
  
  window.something = function(form, event) {
    $('<p>')
    .text($(form).serialize())
    .appendTo('body')
  
    event.preventDefault();
  }
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form onsubmit="something(this, event)">
  <input is="foo-input" name="foo" value="bar">
  <button>Hi</button>
</form>

但是,如果您尝试创建新的影子根,就会出现错误。看起来您只能扩展元素用户代理影子根周围的数据/逻辑。


实际上,在允许多个 Shadow DOM 的情况下,可以在输入元素上添加 Shadow DOM,但是他们更改了规范(我猜是为了获得多供应商共识),现在不再可能了(正如您自己所指出的那样)。 - Supersharp

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