Skip to content

Add JS.ignore_attributes#3765

Merged
SteffenDE merged 5 commits intomainfrom
sd-ignore-attrs
May 11, 2025
Merged

Add JS.ignore_attributes#3765
SteffenDE merged 5 commits intomainfrom
sd-ignore-attrs

Conversation

@SteffenDE
Copy link
Collaborator

Sometimes it is useful to ignore updates to specific attributes. One famous example is the "new"-ish HTML dialog element that relies on an open attribute to determine if it is open or closed. The same applies to details elements. Importantly, when opened by a user, the browser sets this attribute by itself and LiveView should not overwrite it on patches, otherwise it would accidentally close it again.

Previously, the recommended way to handle such cases was to add a function to the onBeforeElUpdated dom option of the liveSocket. This is cumbersome though and especially for libraries leads to more friction, as more steps are necessary to install them.

Now, one can use JS.ignore_attributes in instead, for example:

<details phx-mounted={JS.ignore_attributes(["open"])}>
  <summary>...</summary>
  ...
</details>

And then the details element will always retain the previous open state, regardless of what the server does.

Relates to: #3741
Relates to: #2349
Relates to: #3574

@SteffenDE SteffenDE added this to the v1.1 milestone Apr 16, 2025
@bcardarella
Copy link
Contributor

bcardarella commented Apr 16, 2025

@SteffenDE I haven't tried this but isn't the open attribute transient, in that LiveView will write to the DOM directly. So let's say a dialog is opened but starts closed, a LV update comes in and presumably would close the dialog again. Does this PR restore that state or does it expect to preserve the attribute back into the DOM?

I think my original issue was about the details element too, not dialog but I suspect this would apply to both.

@bcardarella
Copy link
Contributor

fwiw, one thing we've been looking into with LVN is state preservation of certain elements that get restored by certain actions. We are packing these into the history API state object (we re-implemented history API in our headless browser). It's been working well as there are certain UI states that make sense to only do client-side, like dialog open/close, that don't make sense to capture server side. But you may want to restore when backbutton'ing

@SteffenDE
Copy link
Collaborator Author

So let's say a dialog is opened but starts closed, a LV update comes in and presumably would close the dialog again. Does this PR restore that state or does it expect to preserve the attribute back into the DOM?

I'm not sure if I understand your question correctly, but since for details and dialog the open state is based on the open attribute, this PR works by ensuring that the attribute is kept across patches. This is basically the same as #2349 (comment), just without needing a global option.

fwiw, one thing we've been looking into with LVN is state preservation of certain elements that get restored by certain actions. We are packing these into the history API state object

Do you have an example where this might make sense for a LiveView app? Usually, when changing the history, a round trip to the server is necessary too, so I'm not sure when this would be beneficial, for example when thinking about client side only dialogs.

@bcardarella
Copy link
Contributor

Do you have an example where this might make sense for a LiveView app?

An example may be if you have a scrollable section of a page, if you navigate away, then back button to it whatever the scroll position of that section would be lost without state restoration. This is a common pattern in native that web doesn't really have too much expectations around. But I do think it adds to the user experience

@SteffenDE
Copy link
Collaborator Author

LiveView does restore the global scroll position, but for individual containers it is not done automatically. There’s a separate issue for that: #2107 :)

@SteffenDE SteffenDE marked this pull request as ready for review April 17, 2025 18:38
@bcardarella
Copy link
Contributor

bcardarella commented Apr 17, 2025

I'll review that issue but our implementation may provide some guidance here. The state restoration is opt-in for each view. So it can apply to more than just scroll but any client-specific state that isn't be tracked server-side and is blown away due to navigation. We have unique IDs for each node in the view tree that our parser injects as a _id="..." value and we can look them up based upon this. If the _id changes then state cannot be found and isn't restored. This could lead to a false/positive that if a new _id is generated it may change which element it is generated for. For that reason we are considering that this should ony apply to elements that have a specified id= attribute that would (up to the developer) be guaranteed to be unique for that page. We have the benefit of having wrapped all SwiftUI views and have control over how to restore state of each.

Sometimes it is useful to ignore updates to specific attributes. One
famous example is the "new"-ish HTML `dialog` element that relies on
an `open` attribute to determine if it is open or closed. The same
applies to `details` elements. Importantly, when opened by a user, the
browser sets this attribute by itself and LiveView should not overwrite
it on patches, otherwise it would accidentally close it again.

Previously, the recommended way to handle such cases was to add a function
to the `onBeforeElUpdated` dom option of the `liveSocket`. This is
cumbersome though and especially for libraries leads to more friction, as
more steps are necessary to install them.

Now, one can use `JS.ignore_attributes` in instead, for example:

```heex
<details phx-mounted={JS.ignore_attributes(["open"])}>
  <summary>...</summary>
  ...
</details>
````

And then the details element will always retain the previous open state,
regardless of what the server does.

Relates to: #3741
Relates to: #2349
Relates to: #3574
@SteffenDE SteffenDE merged commit 62174d3 into main May 11, 2025
16 checks passed
@SteffenDE SteffenDE deleted the sd-ignore-attrs branch May 11, 2025 12:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants