└── readme.md
/readme.md:
--------------------------------------------------------------------------------
1 |
2 | # Proposal: Unified Element Properties
3 |
4 | Today's UI frameworks exert considerable effort attempting to reconcile or "paper over" the many differences
5 | between Element properties and attributes. In React, this is manifested as the abstract "props" concept which,
6 | when applied to DOM Elements requires a [complex mapping](https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/DOMProperty.js)
7 | of abstract prop names to corresponding DOM properties or attributes.
8 | In Preact, an "in test" (prop in node) is used to infer whether a given abstract "prop" should be applied to the
9 | DOM as a property or an attribute. This approach handles Custom Element properties more naturally, but it comes at
10 | the cost of some performance and determinism. Other rendering libraries choose to expose the decision of whether to
11 | set a given value as a property or an attribute to the developer - Vue uses prefixes (:prop="value") to explicitly
12 | control this.
13 |
14 | It seems a set of new DOM methods designed specifically to address this case could be impactful. These methods would
15 | establish a consistent approach for setting properties/attributes based on the given "virtual property name".
16 | If widely adopted, this would lead to better interoperability between rendering libraries. It also opens the door for
17 | easier Custom Element upgrades through the use of overridden methods, and potentially provides a starting point for
18 | addressing the attributes → properties upgrade issue faced by today's components.
19 |
20 | ### How is this different from setting DOM properties?
21 |
22 | Setting a DOM (reflected) property immediately triggers any side effects associated with that property's setter.
23 | In the DOM, this can be surprisingly expensive - assigning to the src property of an `
` element causes it
24 | to be loaded, even if the value assigned is identical to the current value. This is true for most DOM properties,
25 | including all properties governing Node contents (`.data`, `.textContent`, etc). This is mostly intuitive when working
26 | directly with the DOM's imperative API, but has resulted in all modern frameworks implementing what are effectively
27 | caches around DOM property access in order to achieve reasonable performance. Furthermore, both reading and writing
28 | DOM properties incurs the cost of a binding traversal, since element behaviors are not implemented in JavaScript.
29 |
30 | Ordering of property assignment can also be tricky to get right. Similar to the `
` issue noted above, assigning
31 | to the `.src` property of an Image and then immediately assigning to the `.crossOrigin` property will cancel the
32 | already-started request and re-issue it with updated CORS constraints (note: this might be a browser bug).
33 |
34 | Similar ordering concerns are present for many DOM properties - setting an Node's contents via `.data` or `.textContent`,
35 | needs to happen prior to setting properties that refer to that content like `.datalist` and `.selected`. The behavior of
36 | attributes can sometimes be more manageable in this regard, since attribute changes are batched. Synchronous code that
37 | modifies attributes generally behaves the same regardless of the order in which they are set, even across multiple elements.
38 |
39 | ## API Sketch
40 |
41 | ```webidl
42 | interface Element {
43 | void setProperty(string propertyName, any value);
44 | any getProperty(string propertyName);
45 | }
46 | ```
47 |
48 | #### Example usage:
49 |
50 | ```js
51 | const element = document.createElement('div');
52 | element.setProperty('class', 'demo');
53 | document.body.appendChild(element);
54 | element.getProperty('class'); // "demo"
55 | element.setProperty('class', 'demo'); // ignored - value is unchanged
56 | ```
57 |
58 | # Additional Opportunities
59 |
60 | ## Referential Equality
61 |
62 | This would also open up an interesting possibility for simplifying how the DOM is manipulated both by developers
63 | and through popular libraries, by providing a method to apply a set of properties to an Element where properties
64 | with referentially-equal (or semantically equal?) values are skipped:
65 |
66 | ```webidl
67 | interface Element {
68 | void setProperty(string propertyName, any value);
69 | any getProperty(string propertyName);
70 | void setProperties(map; properties);
71 | }
72 | ```
73 |
74 | #### Example usage:
75 |
76 | ```js
77 | const element = document.createElement('div');
78 | element.setProperties({
79 | class: 'demo',
80 | id: 'demo-1'
81 | });
82 | document.body.appendChild(element);
83 | element.setProperties({
84 | class: 'demo', // ignored - value is unchanged
85 | style: { backgroundColor: 'red' }
86 | });
87 | ```
88 |
89 | ## Observable Bindings
90 |
91 | Given the existence of a `setProperty()` method on DOM Nodes, it becomes possible to implement automatic one-way
92 | data binding simply by allowing developers to pass an [Observable](https://github.com/surma/observables-with-streams)
93 | as a value. When given an Observable, setProperty obtains the current value of the Observable and applies it as it
94 | would any static value, then subscribes to the observable. Future values propagated through the observable are
95 | reflected automatically as if setProperty had been invoked with the new value.
96 |
97 | ```js
98 | const element = document.createElement('h1');
99 | const title = new Observable('initial title');
100 | element.setProperty('title', title); // assigns h1.title to "initial title"
101 | document.body.appendChild(element);
102 | title.next('updated'); // assigns h1.title to "updated"
103 | ```
104 |
--------------------------------------------------------------------------------