Skip to content

Conversation

azizk
Copy link

@azizk azizk commented May 6, 2025

The set_property/3 function allows developers to programmatically set DOM properties as opposed to attributes on target elements.

While set_attribute/3 is useful for static HTML attributes, some UI interactions require manipulating properties, such as value, checked, or disabled.

@SteffenDE
Copy link
Collaborator

Thank you, @azizk!

I'm not sure if this is so common that we need to have JS.set_property. We have JS commands for quite a while now and this has not come up yet. Also, #2159 is related and mentions one big problem with this approach: JS commands are sticky. So if you'd use them to mark a checkbox as checked and then interact with the checkbox normally to uncheck it, the next DOM patch would check it again. I've seen cases where one wants to manipulate the value of an input with JS commands, but those should not be sticky:

def set_value(js \\ %JS{}, selector, value) do
  JS.dispatch(js, "input:set_value", to: selector, detail: %{"value" => value})
end
window.addEventListener("input:set_value", (e) => {
    const input = e.target;
    input.value = e.detail.value;
    input.dispatchEvent(new Event("input", { bubbles: true }));
    input.dispatchEvent(new Event("change", { bubbles: true }));
});

Note that we explicitly combine setting the value with triggering an input event to let the regular LV form logic do its thing.

I think in cases where you need to manipulate properties, JS.dispatch should be the first choice.

@azizk
Copy link
Author

azizk commented May 7, 2025

Thanks for explaining your reasoning and providing the code snippet. I ran into this use case and I think it's no so uncommon:

<button
  type="button"
  phx-click={
    JS.set_property({"value", ""}, to: "##{input_id(f, :name)}")
    |> JS.dispatch("input", to: "##{input_id(f, :name)}")
  }
>
  ×
</button>

I ended up with this temp solution because I wanted to get on with the actual task:

<button
  type="button"
  phx-click={JS.dispatch("input", to: "##{input_id(f, :name)}")}
  onclick={"document.getElementById('#{input_id(f, :name)}').value = ''"}
>
  ×
</button>

It works but it's not very elegant. A simpler way to do this would be to just use phx-click="clear_name" and handle the event on the server. I don't really like that solution because you need to add many more lines in order to manipulate the changeset. It should be possible to do this in one or two lines colocated with the button (nicer for components).

After digging a lot you can find solutions similar to yours. The problem is you have to do this research and you don't quite know if what you find is the best idiomatic solution. It would be great if LV provided this out of the box or at least showed an idiomatic pattern in the docs. And in any case, JS.set_attribute/1 should definitely warn that it will not work for properties (like value/disabled/etc.)

I was also thinking about parts of the DOM that are managed by JS libraries where this could be useful. But I guess in those cases you'd simply write an "on"-event-handler in JavaScript...

@josevalim
Copy link
Member

👍 for updating the docs here. I think we do talk about JS.dispatch and its use cases, so that part is documented, but I personally had no idea you cannot use JS.attribute for value/disabled/checked. So it is good to mention it as a pitfall.

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