Wednesday, June 29, 2016

Web Components and Form Submit Fields

I created an app using web components (with Polymer) recently. But the app did not make use of Shadow DOM, arguably the most important part of web components.

Polymer lets you choose between Shadow DOM, which comes natively from the browser, or Shady DOM, which is a polyfill for browsers that do not yet support Shadow DOM natively. 

But the Shady DOM is not a functionally complete polyfill for Shadow DOM. It does not provide as much name hiding or encapsulation as Shadow DOM.

Under Shady DOM I was starting to get some name conflicts. So, since my app only targets Chrome, I decided to switch from Shady DOM to Shadow DOM for the more powerful encapsulation. Polymer allows you to do this easily by just setting a flag. No real code changes are needed.

This got rid of my naming conflicts but it created a whole slew of other problems. Why? Because some of my web components were used in HTML forms, the old fashioned type of form that gets submitted with a submit button (not AJAX). It turns out that any form elements (input, select, textarea) inside of a web component are not submitted.

That's a big deal. Let me repeat:  

Any form elements inside of a web component are not submitted to the server.

Here is an example:

<form ...>

<date-range>

</form>

In this case, date-range is my custom web component. The date-range component consists of two html inputs internally:

From: <input name="d1"> 
To: <input name="d2">

With Shady DOM, the form submitted its data like this:

?d1=2016-06-01&d2=2016-06-30

With Shadow DOM, here is what gets submitted:

?

In other words, nothing. 

This actually makes a little bit of sense when you consider that the whole point of Shadow DOM is encapsulation, i.e. keeping internal stuff hidden.

So how do you get around this problem?

Work Arounds
Here are three possible work arounds:

1. Don't use the old fashioned "form submit" to send your data to the server. Instead use AJAX. This solves the problem because web components will let you expose d1 and d2 as properties which can then be read by JavaScript and submitted programmatically using AJAX. But it's a big pain in the but to change all of your legacy code for just this reason.

2. Use an additional hidden text input (or two in this case) which live outside of the web component (but still inside the form). Then keep the web component in sync with the text input by using a change listener. This works but it's ugly and undoes the elegance of using web components in the first place.

3. Polymer offers a Polymer-specific solution called iron-form. This solution has some problems. For one, it does not emulate native form submission very well. In a normal html form, when you submit, the current page is automatically replaced by whatever is returned by the action URL. This doesn't seem to happen with iron-form. Second. It only allows your component to contribute a single value to the submitted data. In the above mentioned date-range example, I would like two values to be submitted like this:

?d1=2016-06-01&d2=2016-06-30

This can be worked around by doing something like this:

?date-range=2016-06-01|2016-06-30

But this adds extra work on the server-side.

In short, using iron-form requires a major rewrite to your application.

A Better Solution
If you haven't noticed, none of these work arounds seems all that satisfactory. The root problem I think is this: the creators of the web component specs did not really consider how web components would work in the context of html forms. 

Here is what I propose for a more sound solution: The web component spec must provide a way to expose form submit fields similar to the way you currently are allowed to expose properties. Thus a web component's API would consists of 4 things:

  • Methods
  • Properties
  • Events
  • Form Submit Fields
What do you think of this idea?


Summary
I don't know how common it is to use the traditional technique of "form submit" to submit data to the server but at the very least it's common in legacy web apps. If you want to take an app like that and gradually start replacing some of the form elements (inputs, select, etc) with custom web components then you are in for a whole world of hurt.

This is a use-case that clearly was not considered in the design of web components or polymer. Especially when using Shadow DOM.

1 comment:

max said...

Aside from the most basic applications, I don't use forms. I data bind my UI components to an object model and use http post to send JSON to the server. The additional work of changing a URL in an http.post function call vs the action attribute of a form is negligible yet I gain the advantage of posting an object graph along with keeping my model and view in sync with no work required on my behalf. Also easier to handle an asynchronous response from the server.

I would also imagine a Polymer component is meant to be used within another Polymer component even if the parent component is a form. That is the case with the majority of Angular components except for the root or app component. You might want to try making your form a Polymer component.