This repository considers the possibility of introducing new DOM API methods to efficiently set multiple HTML element properties at once, on either a single element or a collection of elements identified with ids.
Example:
const div = document.createElement('div');
div.applyProperties({
attributes: {
role: 'main'
},
style: {
color: 'red'
},
textContent: 'Hello, world.'
});The above would be exactly equivalent to:
const div = document.createElement('div');
div.setAttribute('role', 'main');
div.style.color = 'red';
div.textContent = 'Hello, world.';In either case, the result would be the same:
<div role="main" style="color: red;">Hello, world.<div>- Generally facilitate population and manipulation of DOM from JavaScript, e.g., in Functional-Reactive Programming (FRP) architectures. Rather that introducing new capabilities, this simply formalizes very common JavaScript patterns for creating and updating DOM elements.
- Support fast updates by allowing the browser to update multiple properties and elements in a single DOM call.
- Allow web components to efficiently reflect state changes in their shadow trees.
The applyProperties method would be added to Element.prototype. This method applies a dictionary of properties to the indicated element.
The Element prototype exposes several properties that expose a collection that can be modified but not set directly: attributes, childNodes, classList, and (for HTMLElement and SVGElement) style. Although these properties cannot be directly set (you can't write element.attributes = newAttributes), we can define simple and useful write semantics for updating these properties in the context of the applyProperties method.
In each case, we define the write semantics in terms of existing DOM write methods:
-
attributesproperty: Sets multiple attributes at once. This takes a subdictionary in which eachname: valueis equivalent to callingsetAttribute(key, value). Passing a nullishvalueacts likeremoveAttribute(key). Known Boolean attributes (e.g.,disabled) are slightly different: a truthyvaluehas the effects ofsetAttribute(key, ''), and a falsyvalueacts likeremoveAttribute(key). -
childNodesproperty: Sets an element'schildNodes. This takes aNodeListor array ofNodeelements. This is equivalent to callingremoveChild()on any nodes not in the supplied value, then callingappendChild()for each node in the supplied value. -
classListproperty: Sets/clears multiple classes at once. This takes a subdictionary in which eachname: valueis equivalent to callingclassList.toggle(name, value). -
styleproperty: Sets multiple style values at once. This takes a subdictionary in which eachname: valueis equivalent to callingstyle[name] = value. Attempting to updatestyleon something other than aHTMLElementorSVGElementthrows an exception.
Note that applyProperties is updating the indicated properties, leaving in place any other existing element attributes, classes, or styles not specifically referenced in the dictionary:
const div = document.createElement('div');
div.classList.add('foo bar');
div.applyProperties({
classList: {
bar: false, // Removes bar
baz: true // Adds baz
}
});
div.classList.value // "foo baz"The ability to update childNodes facilitates construction of DOM through code that, for example, maps an array of model objects to an array of DOM elements that should be added to the DOM.
const objects = [...];
const elements = objects.map(object =>
document.createElement('div', { properties })
);
document.body.applyProperties({
childNodes: elements
});All other property dictionary keys result in invoking the property with the corresponding name. E.g.,
element.applyProperties({ foo: 'bar' });is equivalent to
element.foo = 'bar';This can be used to set both native HTML element properties as well as custom element properties.
A related method, applyPropertiesById, allows the application of properties to a set of elements identified by id. This method would be exposed on Document and DocumentFragment.
<body>
<div id="foo">
<div id="bar"></div>
</div>
</body>document.applyPropertiesById({
foo: {
style: {
color: 'red'
}
},
bar: {
textContent: 'Hello, world.'
}
});For each key: value in the supplied dictionary, applyPropertiesById takes the key as an id, and finds the corresponding element in the relevant tree via getElementById(key). If the element is found, it passes the value as a property dictionary to applyProperties(element, value).
The above code is exactly equivalent to:
const foo = document.getElementById('foo');
foo.style.color = 'red';
const bar = document.getElementById('bar');
bar.textContent = 'Hello, world.';In both cases, the result is:
<body>
<div id="foo" style="color: red;">
<div id="bar">Hello, world.</div>
</div>
</body>The use of applyPropertiesById is particularly useful in web components that want to reflect component state in their shadow tree. E.g., when component state changes, it might invoke
const changes = {
element1: { /* changes for element1 */ },
element2: { /* changes for element2 */ },
...
}
this.shadowRoot.applyPropertiesById(changes);It would be useful to accept the same properties dictionary as an options parameter to createElement/createElementNS. The example at the top of this document could be written more concisely as:
const element = document.createElement('div', {
attributes: {
role: 'main'
},
style: {
color: 'red'
},
textContent: 'Hello, world.'
});Note: createElement/createElementNS currently accept an additional argument with options, which at the moment is just the standard (but not universally supported) is option. There are likely several ways the existing options parameter could be reconciled with the suggestion above.