EPA-WG/custom-element

Form, app url, slice change events

Opened this issue · 8 comments

slice is a section in element's dataset tree. Its content is defined by element value where the slot attribute is defined, a "hi" string in this sample:

<input slot="user-input" value="hi">

Elements with slice attribute like local-storage in current implementation on slice data change notifies the parent tree invoking parent.onSlice .

            for( let parent = this.parentElement; parent; parent = parent.parentElement)
                  if( parent.onSlice ) 
                       return parent.onSlice(
                        {     detail: string2value( type, localStorage.getItem( attr( this, 'key' ) ) )
                        ,     target: this
                        } );

For HTML native elements (along with custom elements) with value the slice update has to be triggered by change or another event .

🤔 unified approach for DCE embedded elements like local-storage and native HTML elements is achievable via

  • defining the event which would trigger the slice update. By default it would be a change event. Via slice-update="change" attribute
  • the slice data updates taken from value element property

default slice update event and value prop

Should slice synchronization event be explicitly defined on slice-update with slice-prop?

For minimization and DX reasons, NO. Without slice-update the engine can assume its value is change event and the value property is the data source. When there is a need to change,

  • slice-update attribute defines the event. For example, instead of default change event set it to blur
  • slice-prop attribute defined the accessor property. For example, instead of default value prop set it to validity, a ValidityState object or selection, an artificial accessor for input and textarea utilizing the selectionStart and selectionEnd.

slice-property attribute

Would define the data accessor for slice update. By default it is value which matches the HTML convention for all form input fields.

form data vs individual fields

The slice data can be bound as to individual fields as on form level. HTMLFormElement gives the form's elements property which can be mapped to object filled by form element name : value .

From another hand, the FormData object is used by fetch layer and can be used as is without transformation when slice is bound by http-request and constructable from form element:

const formData = new FormData(someFormElement);

slot initialization

  • from external data (when slice is mapped to another)
  • from instantiated HTML
  • ? from detached HTML

Would be whole form data changed on individual input change?

multiple slices and events associated with same element

While the simple value by change event

    <input  slice="user-input" />

is sufficient for many cases, often there is a need for more sophisticated behavior and accessing varios dynamic props on difeerent events.

For example, different UX reaction on invalid input should be during entering the value and after its completion. The change event meant for editing phase and blur for completed. The validation part might be looking as into full text value as into internal validity state.

implementation options

HTML allows multiple accociations for same element as in for attribute with space-separated IDs like in output element. But for multiple slices updated by own events this approach does not work as multiple props have to be coupled with slices by own events.

  1. couple the event and data source prop in single attribute allowing multiple (event : prop : slice) triplets
<input slice-map="change:value:user-input ; blur:validity:user-input-error">

Which for some reused params can be shortened

<input slice="user-input" slice-update="change blur">
  1. introduce slice-update element with for, event, value, and slice .
<input id="inp1">
<slice-update for="inp1" event="change" value="value" slice="user-input">
<slice-update for="inp1" event="blur" value="validity" slice="user-input-error">

While 1st approach is shorter, it looks more like impirical approach with custom data format 😖 To make it worth the existing pattern of style attribute with key:value is not working as need to list triplets.

The declarative is more verbose, but some can be shortened by default mapping and encapsulation:

<slice-update event="blur" value="validity" slice="user-input-error">
    <input  slice="user-input" />
</slice-update>

Here the user-input-error slice taken from 1st embedded element with value prop by this element change event. The for relation is replaced by embedding relation.

The user-input slice is taken from same element value by its change event.

slice-update vs generic event

<input id="inp1">
<slice-event for="inp1" event="keydown" slice="user-input-keydown">

slice-update and slice-event look similar but serve the different data. The 1st takes data from the input element property, the later from event data itself.

Alternative option is to use artificial "event" value:

<slice-update event="blur" value="event" slice="user-input-event">

all variations are compatible

Just question what would be simpler to implement first and what would be demanded by devs community?

Candidates for implementation

The simplest use, defaults for prop and event , with slice-update and slice-prop

    <input  slice="user-input" />
    <input  slice="user-input" slice-update="blur change" slice-prop="validity"/>

This case is convenient for validation of individual field. For the access the value the slot from <form slot="xxx"> is more suited.

For multiple events/props reflection the slice-update element with all defined ^^ parameters.

Events in slice set

The slice is filled only during event and used in follow up transformation only. Further transformations related to slices change would not include the event data.

The reason of temporary use of event data is the event "trigger" nature. The each event has to be served and processed independently.

To use the event data later, those have to be transferred into another persistent slice by slice-copy.

<input slice="xxx" slice-prop="validity" />
<slice-copy from="//slice/xxx" to="//slice/yyy" />
<xsl:if test="//slice/yyy/patternMismatch">
    <em>Pattern mismatch error</em>
</xsl:if>

Form validation flow

each input on chage or blur would trigger either native ( by element implementation ) or DCE event-driven declarative validation.

The form level validation is trigerred by form submission flow.

The `change` event is available on the `FORM` element
<script type="module" >
    document.querySelector('form').addEventListener('change', (event) => {
        console.log(`Form element ${event.target.name} has changed.`);
    });
</script>

This is a natural choice for validation trigger on the form level and propagate the formData when slice is defined on the form element.

slot initialization

<el slot=SSS value=VAL>PAYLOAD can be either

  • (form?) input with value
  • -||- with value taken from payload as in <textarea>PAYLOAD</textarea>
  • Custom Element with value property (attribute?)
  • element without value prop/attr

During DCE instantiation( 1st render ) the form inputs provide the source for data slot. Later the value change would update the slot data which would trigger re-render cycle. The presence of data in the slot would prevent the re-nitialization loop.

There is no need to attach the DOM during the initial value extraction as it would follow with re-render cycle.

What if the value=//slot/sss... in template would override the value of element?

The value would be picked during initial render. The changed by user value is propagated into data slot but following render would preserve the element and would not re-inject it with the new value.

In general, it is possible to detect that value depends on data or have been changed and re-generate element or re-assign its value.

Should the change event be suppressed in case of re-assigment as would cause the infinite render loop?

Yes. Even with safeguards on cycle count, the loop has to be stopped eventually. To avoid variations of output dependent on number of re-render loop, it has to be prevented from first hand.

Preserving of elements

is essential for Custom Elements as the state can be not serializeable via HTML/attributes. Hence the attributes ( all, not just value) update should be done via assigning instead of re-render.

What about payload change?

Same. The re-rendered payload would be re-assigned.

Keeping elements without replacement after re-render

When updating the Document Object Model (DOM), it is better to merge the new content with the existing one, instead of replacing the entire DOM for preserving of elements reason described above. However, in order to do this, the system needs to be able to identify the specific element that needs to be updated during sequential transformations. One way to achieve this is by tagging the element markup in the template with generate-id() or a simple element counter. However, this method only works when the markup generates a single element. If the loop or recurrent call generates the same element multiple times, it is necessary to modify the element tag to reflect the instance number.

JSX for loops and collection iterations requires a key property explicitly to be defined. This key can be used as unique identifier for element in the context of parent.

While it can be a required attribute for element in template to be preserved across re-render cycles, in most cases it can be replaced by idex in iteration which can be auto-calculated. Also, the id attribute nature can serve the same purpose.

So, when the key is not presented, its value can be

  • taken from id
  • computed as the same element on template index.