-
Notifications
You must be signed in to change notification settings - Fork 378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[idea] Allow HTML Element attributes to accept any type of value, not just strings. #519
Comments
Honestly, I think that’s a better job for frameworks to do rather than the platform. Attributes are meant for simple string usage. If you want something more complex, you can simply have a property that is not bound to an attribute. All I wanted is to have a certain pattern that HTML elements’ property names are guaranteed to never follow. (For example, I want a guarantee that HTML elements’ properties are never going to start with |
@Zambonifofex Why? |
@trusktr When I say that that’s a job for frameworks, I think that they should simply allow |
@Zambonifofex Yes, but now that Custom Element are living thing, part of those "DOM" that you are referring to are JavaScript objects. It no longer makes sense to require converting attributes to strings when those DOM elements receiving the attributes are also JavaScript objects. Allowing non-string attributes makes the DOM API better because JavaScript-based DOM (Custom Elements) won't be hindered by the unnecessary performance cost of string serialization/deserialization... Imagine: if we have 10 layers of ShadowDOM trees (very easy to do in a webcomponent-based app), then that means there can be up to 10 serializations and 10 deserializations (20 total) just for data to flow from element to element from the outter tree to the most inner tree. That is obviously not a good thing. |
@trusktr it will always only be possible for authors to specify strings as values for attributes.
But that’s the point I’m trying to make: you don’t have to do any serialization/deserialization; you can simply have a property that is not bound to an attribute. |
No, not if the spec is changed. The spec was changed in order to add Custom Elements, and it's possible to further change it to allow non-string attributes.
Then, how does the server send data to the client? It sends it as a string, which needs to be deserialized. Those strings can be inside HTML attributes. Deserializing strings is still great, when it comes to parsing an initial HTML payload, but once we have things rolling on the client-side at runtime, we can completely avoid the string-based requirement (assuming we make changes to the spec). Basically, we still need serialization/deserialization for when we want to generate actual HTML to save somewhere or to receive from a server; it is great. We just need to give Custom Elements more freedom. @sebmarkbage (one of React's creators) mentioned this string-based problem:
(@sebmarkbage Your input on this would be highly appreciated!) The criticism here (regarding strings and HTML attributes) is huge; it is one of the things that makes the web less appealing to "native" developers. Example: JavaScript libraries made for animating DOM elements using CSS matrix3d (see some of those libraries listed here) always complain about the awful concept of having to convert matrix number arrays into CSS matrix3d strings just so the strings can be converted back into numbers on the HTML-engine's side. That's flawed, awful, and we shouldn't have to do that. Plain JavaScript properties aren't exactly the same as attributes, they are not accessible through HTML. Libraries like React ultimately send their The suggestion to "just use JS properties" is only a temporary workaround, not a solution. The complete solution is to allow non-string attributes. The explicit benefits are:
This proposed change isn't a difficult change to make, and would bring the web forward more. |
Okay, how do you expect the syntax to be? How would one differentiate a string attribute from a number attribute? And how would you pass more complex things like objects? And honestly, I do not understand what’s that superior about attributes compared to properties.
How do you know it wouldn’t be difficult to make? Imagine all the code in browsers and libraries/frameworks that expect attribute values to be strings. |
As far as HTML syntax, nothing new, that stays the same. For example: <some-element some-attribute="anything here is just a string still">
</some-element> A Custom Element can define any string format they wish (by documenting it). The Custom Element's <some-element some-attribute="0, 30, 0">
</some-element> The class SomeElement extends HTMLElement {
// ...
attributeChangedCallback(name, oldValue, newValue) {
if (name == 'some-attribute') {
if (typeof newValue == 'string') {
// if the attribute is a string of comma-separated numbers,
// convert to an array (i.e. "deserialize").
this.setAttribute('some-attribute', convertToArrayOfNumbers(newValue))
}
else if (newValue instanceof Array) {
// ... work with an actual array ...
}
}
}
}
function convertToArrayOfNumbers(string) {
// ... possibly validate syntax here first ...
return string.split(',').map(str => parseInt(str))
} As you can see from the example, when a
I believe my example gives you an idea of that.
My example shows you can pass anything to Furthermore, it is up to the Custom Element to supply ensure that the item stored in an attribute has a Here's another example that shows how we would convert the comma-separate-number-string into something more complex like a class Coordinates {
constructor(x, y, z) {
this.x = x
this.y = y
this.z = z
}
}
class SomeElement extends HTMLElement {
// ...
attributeChangedCallback(name, oldValue, newValue) {
if (name == 'some-attribute') {
if (typeof newValue == 'string') {
// if the attribute is a string of comma-separated numbers,
// convert to Coordinates object (i.e. "deserialize").
this.setAttribute('some-attribute', convertToCoordinates(newValue))
}
else if (newValue instanceof Coordinates) {
// ... work with the Coordinates object ...
}
}
}
}
function convertToCoordinates(string) {
// ... possibly validate syntax here first ...
const args = string.split(',').map(str => parseInt(str))
return new Coordinates(...args)
}
Can you explain what you mean by "properties"? Maybe I don't understand? Do you mean regular JS properties? If so, plain JS properties aren't hooked into the HTML engine, that's the downside.
With this change, built-in elements can continue to take/give string values, those parts won't break. However, and in my opinion, if the As far as Custom Elements, the API in the wild is v0 right now. It can change. People using Custom Element right now can expect things to break for the better good of the web. |
FWIW React uses properties when it can. E.g. we don't want to first stringify a true/false value and pass the string 'checked' when we can just directly call .checked = true. The problem is that the ecosystem of DOM, as well as the ecosystem of custom elements, rely so heavily on attributes to do things that should also be available as properties. If I had my way, custom elements wouldn't support attributes directly but always translate them to property operations so that custom element authors are encouraged to use them - always. Same goes for events. There should always be a property that corresponds to the event. That is mostly true for the built-in DOM but not for things provided by the ecosystem. |
Good point! That's definitely good to take advantage of when available. It also requires making some sort of runtime whitelist on what is available, which can be avoided for other cases (generic cases that don't have associated properties or accessors) if attributes can accept non-string values.
I think simply allowing non-string attributes is exactly the same solution (but better, because it is already part of spec: DOM elements have attributes and they are set with setAttribute/getAttribute). I also posted a new comment with examples while you were making your comment. What do you think of those (above)? As for events, is EventTarget with addEventListener/removeEventListener not enough? |
@Zambonifofex Updated above examples; I forgot to convert items into numbers. |
Yeah, I mean just regular JavaScript properties. And so what if they aren’t “hooked into the HTML engine”? You could simply read from an attribute and set a property: <my-element data-my-attribute="[1, 2, 3]"></my-element> class MyElement extends HTMLElement
{
// ...
attributeChangedCallback(name, o, n)
{
if(name === "data-my-attribute")
{
this.myProperty = JSON.parse(n);
}
}
} |
@Zambonifofex That works, but it isn't obvious for libraries like There's no standard on how to map plain JS properties to HTML attributes. Could that be a standard? For example, I already made one such proposal. If something like that existed, then React and other libraries could standardize on using those JS accessors. I would be open to that solution as an alternative (although I think I still prefer attributes more, and those are already standard). |
@trusktr That's true for events too. Libraries like Polymer have to make up their own convention like the |
You can keep then with the same name. class MyElement extends HTMLElement
{
// ...
attributeChangedCallback(name, o, n)
{
if(name === "data-my-attribute")
{
this["data-my-attribute"] = JSON.parse(n);
}
}
} Or convert then from dash-case to camel-case and vice-versa. But honestly, I think libraries/frameworks should interact directly with the property instead of with the attribute, having the |
That would work, but it's not a convention that everyone is guaranteed to follow. On the other hand, it is guaranteed that As @sebmarkbage mentioned, they try to use properties when possible in React, and setAttribute is a fallback. If we make the fallback (setAttribute) work with non-string values, libraries wouldn't need to have a runtime whitelist of what properties to use and when to fall back on setAttribute. HTML element attributes are the guaranteed description of what data an characterizes an element, so I think relying on those is better than properties because properties aren't guaranteed to exist, but attributes are. Servers can't set properties, they can only set attributes, and I think that having a client-side library depend on attributes will make interop between server and client better without having to use (possibly erroneous) whitelists. |
Even with properties created based on attributes, libraries will have no idea what these are (they can guess, but it won't be guaranteed). If a library can simply pass an object to |
The problem is that nothing currently is prepared to handle non-string attribute values. Nor are libraries/frameworks, nor are browsers. To make this work would be a lot harder than it actually looks like. Browsers would have to completely change the way they handle attributes, and that’s not trivial.
I don’t think that I understand what you mean. |
What I mean is that attributes exist everywhere (even on the server-side). Attributes are the defacto way to characterize an HTML element. Properties are second to that, so that's why I like the idea of working with setAttribute (and allowing non-strings) directly. |
What if attribute methods just proxied the properties? |
Let's close this. Attributes are strings. Making them something else would make them no longer attributes. You can use properties (and attributes that reflect them) to store other data. |
As a library author who's dealt with this specific problem, I agree with @Zambonifofex and @domenic. Skate, for example, takes cues from React here. The problem with doing this is that you have to come up with an opinionated convention. Having such a convention at a lower-level implementation such as the DOM specification would be very limiting - for something that's supposed to be a core API for libraries to build on top of - and open up huge can of worms because you would have to define behaviour, at the spec level, with what happens to different types of values (and possibly names) when passed as attributes. I'd even be inclined to say that consensus - when trying to converge on said opinionated convention - would be even more difficult to come to. That's definitely something that the spec does not need right now. |
@treshugart I don't agree. As part of web manifesto, custom element authors should be able to override get/setAttribute in a consistent manner. Sure, the super class setAttribute will convert things to strings. I don't have to call If that doesn't work, then the HTML engine design is broken. And it isn't hard to fix! What will break? |
You always make statements like this. It's like saying "just because". Better responses would be appreciated. I'm not trying to be offensive, it's just that you have a history of closing multiple issues of mine (and possibly others') with comments that are honestly not helpful at all. (I'm not going to link any, I'm sure you can remember). That comment is completely useless. I can say that attributes were strings before. Now I'm saying they don't have to be stored in memory as strings, but Otherwise please don't blatantly close issues. (Maybe it feels good to have that power, but I don't think you're using the power to it's full potential). |
We can't change This is also the wrong place to propose changes to core DOM APIs. Those should go to whatwg/dom, but breaking changes of this nature will end up being rejected. |
Whenever you think something is easy I recommend trying to go through the implications and changes to processing models that would be required for the change. How would this change affect mutation observers? How would it affect HTML element definitions that assume attribute values are always strings? How would it affect selectors? Etc. |
Not necessarily. getAttribute can turn any value, and the engine can use ToString on it for use with selecting.
It doesn't: a record is queued when setAttribute is called. The new value (string or not) is stored in the record.
Which element definition for example?
String attributes would be useful here still. Selection doesn't have to work on everything. Also if an attribute isn't a string, it didn't mean the steering result is not predictable. An attribute with a numerical array value is still easy to select for example. What I realized though, is that in my use cases so far, I want this feature in custom elements. I can easily override setAttribute to do this. And per web manifesto, I should be able to. It seems difficult to disagree with the ideal of web manifesto, and as love of the web, I would hope we fight for it. |
It sounds like you don't understand the complexity of what you're proposing and I'm not sure how to make it clear to you. 😟 |
I read this tread some months ago and by chance stumble just now on it again as you're active here: We have the use case that we have component (which draws some nice line-graph and needs data which is a simple js array) this component itself is encapsulated in another higher abstraction component which basically gets the necessary data from an API. |
@l00mi can't the inner component expose some kind of method the outer one can invoke with the data? |
Sure, this is possible? |
Well |
Suppose we want to use @treshugart's SkateJS (with Preact). Now we're using a library (like many others) where handing data to inner components is done by passing data through attributes of inner components. How do we pass numerical values to the inner elements in this idiomatic form? With custom elements (and considering Preact passes values into This is really good because we can do performance optimization like that! Some have proposed that non-string values should be set on properties of the element, but this would be a convention, and not standard and idiomatic in the way that all frameworks and libraries pass data from outer components to inner components via attributes. I guess we can do this with built-in elements too by 🐒 patching the specific element interface I just need DOM not to be so slow! Seems to me the value of an attribute should only ever be converted to string when it is observed. I.e., when dev tools shows you HTML, it can call getAttribute and coerce things to strings. I.e. when the selection engine looks up an attribute, it can call getAttribute and coerce to string. Sure, in this case selection will be slower. But my darn WebGL graphics will be faster, and honestly I'm not going to be selecting things 60 times per second, I will be selecting things once, then using a cached reference inside an animation loop.When animating DOM, converting everything to string so CSS can convert back to numbers, for example, is performance overhead, among other things. TypedOM will.be imperative, not declarative, so I think it is fair to need this performance optimization with attributes. Anyways, I will try monkey patching the built-in interfaces now that I think about it, I think that may be enough. |
Here's a graphical custom-element example which yearns to be faster, it works out every day towards this goal: // Scene rendered by React for example
var xRotation = ...;
var yRotation = ...;
var zRotation = ...;
return <three-dee-scene>
<mesh-geometry rotation={`${xRotation} ${yRotation} ${zRotation}`}>
</mesh-geometry>
</three-dee-scene> As you can see, this is dumb. We have to pass string values that will be converted back to numbers. This is like the element choosing to run a marathon with weights on its ankles and wrist thinking it will be faster. The element was supposed to take the weights off before the marathon, but HTML spec told it not to. Three.js wins, it did not have this restriction, but also Three.js does not have the declarative advantage. Now look at A-Frame @annevk. It is plausible that people will want to stick such elements inside React/Angular/Vue/etc frameworks and libraries. Surely we don't want their apps to have to have absurdly pointless number-to-string-to-number conversions just because they are passing attributes to inner elements. We would love to help make the web faster (especially for modern graphics, which is my use case and why I brought this whole issue up in the first place). I'm not just making generic simple 2D applications. I am trying to make 60fps 3D animated applications. And the declarative paradigm of HTML bring much developer productivity. I'd just like to fine-tune performance, and avoiding number-to-string-to-number conversions is one step towards that. And it has to be done through
|
Improving the setAttribute standard ( @treshugart ) is closer to standard than the suggestion of using instance properties or anything else, and would be "a convention" the least. |
Here's a live example: http://trusktr.io/polydance (open in Chrome, may not yet work in other browsers) Here's the part of source code where the 3D scene is described declaratively: I will be hacking |
Should we have a new method for it, which won't break current system. Just like Why not set property directly through
It should follow the following rules:
Also, it can be initialized by document.createElement('custom-element', {
properties: {
name: 'eidtor'
}
}); For high performance, might we can allow custom element developer declare the type // Just like Polymer, but the difference is that property changes do not trigger rerender
// The whole action handled by `propertyChangedCallback` hook
class MyCustomElement extends HTMLElement {
properties: {
name: {
type: 'boolean',
defaultValue: true
}
}
} The above is just my simple idea. |
People above have asked about the html syntax for the feature:
the Given the syntax is new, it can directly change properties, not the attributes. This way users get choice what they specify. Considering backward compatibility - we can make Effectively making attributes a stringified versions of properties. Can be limited to custom elements only |
@tema3210 the hard questions here aren't about putting expressions into HTML - we can clearly do that with (bad) things like Some of these questions may be addressed in the DOM Parts work, where we would like parts to be able to have initial values as rendered by SSR, but the ideas there are still nascent and we haven't yet discussed how that might work for properties (that I know of). |
#517 covers two things:
I'd like #517 to be about the first idea, and would like to make this issue specifically about the ability for DOM APIs like
setAttribute
/getAttribute
to be able to accept any JavaScript values besides strings so that we can avoid performance costs from serialization/deserialization. We would be able to pass objects, arrays, numbers, instances of custom classes, etc.Please see #517 for details (maybe I can move those details here...).
For example, imagine some error component specialized in displaying errors, who's error attribute can accept an actual instance of an
Error
:Or, using the setter/getter feature described in #517:
I really think making APIs easier to use (f.e. opt-in setters/getters mechanism for attributes on Custom Elements, not requiring serialization/deserialization by allowing attributes to accept values other than strings, etc), is a good goal for Web stack to have!
The text was updated successfully, but these errors were encountered: