9

I am trying to create a web component that is usable in a form element specifically, that has a name, and a value. I recognize that I can create a web component that extendsan HTMLInputElement:

<input is="very-extended">

but I am trying to create an entirely new element.

When creating a regular web component, you can create it from the prototype of a regular HTMLElement (HTMLElement.prototype). Which leads me to assume that I can create a different element with the prototype of an HTMLInputElement (HTMLInputElement.prototype). You actually use that prototype when extending the API of input elements, so why can't I use that prototype to create an entirely new element that works in a form?

If you look at the shadow dom of a regular input field:

enter image description here

you can see that there is a div inside of there. I understand that this HTMLInputElement has methods and attributes, getters/setters, etc. So why is it that when I attempt to create my component, it fails to be part of the name, value pairs found in the form?

Here is an example of how I am trying to create this web component:

Please note that his should be tested in a browser that supports web components.

(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>

Why is the name, value pair not found among the form, and how can I create a custom form element?

Supersharp
  • 29,002
  • 9
  • 92
  • 134
KevBot
  • 17,900
  • 5
  • 50
  • 68

3 Answers3

5

You can create your <custom-input> custom element that will be interpreted by a form, just by adding inside your template a hidden input element with the name and value pair your want.

<template>
    <input type="hidden" name="foo" value="defaultVal">
</template>

The default value (and name) can by updated by your custom element internal logic.

This hidden input must not be inserted inside a Shadow DOM to be detected by the container form.

(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>
Supersharp
  • 29,002
  • 9
  • 92
  • 134
  • 1
    Good idea. The only drawback is that since the hidden input must be in the custom-input's Light-DOM, not the Shadow-DOM, it's not hidden from JavaScript nor CSS. Also, you have to keep the contents of the regular input updated with the contents of your custom-input. Both-ways: The custom-input should also watch for outside changes to its inner hidden input. And other third-party scripts won't recognize the custom-input as an input. In any case why is this necessary? I guess it's because most people just hate the `is` notation. – Marcelo Glasberg Aug 03 '16 at 23:20
  • 1
    It's not a drawback, it'a an advantage. The input is seen by the external scripts and libs. The updates are inherent to a customized input anyway. – Supersharp Aug 04 '16 at 07:17
  • 1
    @MarcG From what I saw, if you extend the HTMLInputElement prototype and designate the `extends: input` you still cannot add to the shadow DOM of the input. This seems pretty limiting. Doesn't seem like you have a choice if you want to add stuff to the DOM. – Walt Aug 04 '16 at 22:38
  • 1
    @Supersharp can you post an example? I think that would help provide more insight into this answer. I haven't seen a way to access the existing shadow root. If you try and create your own you get an error stating `Shadow root cannot be created on a host which already hosts an user-agent shadow tree` – Walt Aug 05 '16 at 15:23
  • @WaltWeidner So you're right. It's seems no possible to add a Shadow to this element. The use of an hidden input is the only valid way AFAIK. – Supersharp Aug 05 '16 at 15:47
  • @WaltWeidner I believe you are now getting this error because this was recently changed between V0 and V1 of the Shadow DOM specification. See here at "Multiple Shadow Roots": http://hayato.io/2016/shadowdomv1/ – Marcelo Glasberg Aug 08 '16 at 22:04
2

KevBot,

You seem to think that the element includes itself in the form. That is not the case. It is the form that searches its children elements by tag name, to decide which elements it should include. It will simply ignore those with unknown tag names.

Your custom-input name is not among the elements the form searches. Therefore, it is not included on the form. It doesn't matter the custom element's prototype. That's why it works if you use is, since then the tag name is maintained.

Of course, you may implement your own custom-form that behaves differently, if you want.

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
  • I tried setting the value from inside of a custom element that extends a built in element. `` for example and could never get it to work. Have you been able to? – Walt Jul 28 '16 at 23:08
  • Walt, your custom input's prototype should extend `HTMLInputElement.` And then you should set the value of the input normally (`value` attribute). It should work, yes. – Marcelo Glasberg Aug 03 '16 at 23:05
  • Looks like I was just trying to set the value before the created callback was firing. That was causing Chrome to throw an error about illegal access. – Walt Aug 04 '16 at 22:33
2

You can do this:

(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>

But you get an error if you try to create a new shadow root. It looks like you are limited to only extending the data/logic around the element's user-agent shadow root.

Walt
  • 1,521
  • 2
  • 13
  • 29
  • Actually it was possible to add a Shadow DOM on input element when multiple Shadow DOMs were permitted, but they changed the specification (to get a multivendor consensus I guess) and now it is not possible any more (as you noted yourself). – Supersharp Aug 06 '16 at 11:03