Skip to content

Conversation

lydell
Copy link

@lydell lydell commented Jun 1, 2025

Intro

This PR is a companion to elm/virtual-dom#187. That elm/virtual-dom PR works without this elm/html PR, but virtualization won’t work 100 % without it.

This PR removes the stringProperty helper function, and uses attributes for the exposed functions that used it instead. The boolProperty function is kept, and I added a comment for why.

Details

Most functions in Html.Attributes that take a String used this helper function:

stringProperty : String -> String -> Attribute msg
stringProperty key string =
  Elm.Kernel.VirtualDom.property key (Json.string string)

For example, href:

href : String -> Attribute msg
href url =
  stringProperty "href" (Elm.Kernel.VirtualDom.noJavaScriptUri url)

In other words, lots of the Html.Attributes functions were implemented by setting properties.

This PR removes stringProperty, and instead prefers attributes over properties. Here’s href in this PR:

href : String -> Attribute msg
href url =
  Elm.Kernel.VirtualDom.attribute "href" (Elm.Kernel.VirtualDom.noJavaScriptUri url)

In short, attributes are preferred because:

  1. Attributes can be removed, while properties often cannot. Closes elm/html#228, closes elm/html#148, closes elm/virtual-dom#122, closes elm/virtual-dom#169
  2. Virtualization is way easier when most Html.Attributes functions are attributes. Closes elm/virtual-dom#144
  3. Some properties are read-only and therefore throw errors if you try to set them.
  4. Attributes are easier to diff.

I explain those points more in the properties-vs-attributes.md file (which I updated in this PR).

Comment on lines -786 to -788
downloadAs : String -> Attribute msg
downloadAs =
stringProperty "download"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was unused (not exposed).

Comment on lines +463 to +468
value string =
-- Note: `.value` has no corresponding attribute, so we have to set it
-- using a property. It can also be modified by the user by typing in inputs.
-- Properties are diffed against the actual DOM, not the virtual DOM, so
-- this ensures that the DOM is up-to-date with the model.
Elm.Kernel.VirtualDom.property "value" (Json.string string)
Copy link
Author

@lydell lydell Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value is an exception where we have to use a property, not an attribute.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properties are diffed against the actual DOM, not the virtual DOM, so this ensures that the DOM is up-to-date with the model.

This refers to the implementation in elm/virtual-dom#187

2. When initializing an Elm app, you give it a DOM node to render into, or the `<body>` node is used. What happens if the node to render into isn’t empty? Elm then _virtualizes_ the DOM into virtual DOM. If the DOM nodes match what your app’s first render, the virtual DOM diffing won’t find any changes to make. This allows for server side rendering HTML, and then have Elm take over that content and avoiding lots of work at startup. Let’s take `<a href="/about">` as an example again. When virtualizing, Elm has to make a guess: Did you use `property "href" (Json.Encode.string "/about")` or `attribute "href" "/about"` in your Elm code? If we guess wrong, the first render will do an unnecessary DOM update, and if we guess right the first render won’t do anything as expected. Another complication is that attributes and properties don’t always have the same name, as mentioned above. When virtualizing DOM nodes, it’s possible to loop over all _attributes_ that are set, but it’s not possible to loop over properties. For `<div class="user-info">`, a loop over attributes would get that `class` attribute, but if `Html.Attributes.class` is implemented with a property, we would have to translate `class` into `className` with a lookup table. By primarily using attributes in `Html.Attributes`, we don’t need a lookup table (except for a couple of edge cases).

3. Some properties are read only – if you try to assign them an error is thrown. The most notable example of this is trying to do `.className = "my-class"` on an SVG element. That throws an error, while `.setAttribute("class", "my-class")` works. In Elm, both `Html msg` and `Svg msg` are type aliases for the same virtual DOM node type, so you can mix functions from elm/html and elm/svg without getting type errors. `Html.Attributes.class` used to be implemented by setting the `className` property, and a common mistake was accidentally using using `Html.Attributes.class` on an SVG element, instead of `Svg.Attributes.class` (which is implemented by setting the `class` attribute), which would then cause hard to debug runtime errors. By setting the `class` attribute in `Html.Attributes.class` this issue is avoided.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related: elm/svg#24 is not really needed anymore, since Html.Attributes.classList now works on SVG too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant