Using LiveView.JS to manipulate input values

Hi! Is it possible to manipulate input values using LiveView.JS?

In the following example (sample app on GitHub), I can successfully change the value of #my-input to Hello or World depending on which button I click first. Subsequent button clicks do not do anything.

<form name="form">
  <input type="text" id="my-input" />
</form>

<button phx-click={ JS.set_attribute({"value", "hello"}, to: "#my-input") }>Add "Hello"</button>
<button phx-click={ JS.set_attribute({"value", "world"}, to: "#my-input") }>Append "World"</button>

A couple of questions:

  1. How come after the first button click nothing happens?
  2. Is it possible to append to an existing input value so that clicking buttons “Add ‘Hello’” and “Append ‘World’” in sequence would change the value to “helloworld”? (My example doesn’t show this, but I didn’t find anything to access the input’s current value in the docs).

I suspect that JS.set_attribute isn’t the right way to interact with client-side forms this way.
Maybe JS.dispatch is? How else would you go about this?

I want to interact with a form element before having to send information to the server. I can use Alpinejs, but I’m trying to avoid a dependency unless it’s absolutely essential.

Here’s a rough JS Fiddle of what I want achieve:

function append(value) {
  let el = document.getElementById("#my-input")
  el.value = el.value + value
}

function reset() {
  let el = document.getElementById("#my-input")
  el.value = ''
}
<input type="text" id="#my-input" />

<button onclick="append('hello')">Hello</button>
<button onclick="append('world')">World</button>
<button onclick="reset()">Reset</button>
1 Like

set_attribute is for html attributes. Its js equivalent is el.setAttribute("value", …). The value attribute on inputs sets the default value for that input.

el.value = … however sets a property on the dom object behind the html and edits the actual value, not the default.

I hope that explains why you get the behaviour you see.

2 Likes

Thanks, that makes sense. How would you/do you suggest going about this in LiveView? Using dispatch or should I use something like Alpine?

1 Like

I think the LiveView-way of doing this would be to use phx-click and friends, and make a network round-trip. You will have to use the form helpers or bind the input field yourself somehow. You will probably have to make the roundtrip anyway to do live validation. This way you won’t have to add any dependencies, and stay on the LiveView golden path.

1 Like

Hi. In this case, it’s wasteful and adds unnecessary latency to go to the server and back. The server does nothing but update a GenServer and waits on standby to act later. It would be better to mount the state on the client-side first and send it once it can be acted upon by the GenServer. There is no validation during this phase.

I have it working via round-trip. But I find it adds latency, complexity to the GenServer, and no meaningful value (other than working). I need quicker feedback on the client before going to the server.

1 Like

I believe an option would be using JS.dispatch/2 with window event listeners.

2 Likes

I think the LiveView way to do this is indeed via JS.dispatch. The live_beats app is a good reference for these kind of things. For example they have a js_exec method via which you can call arbitrary functions on the target element.

However in your case, since you need to assign a property, it would look something like this though:

window.addEventListener("js:set", e => e.target[e.detail.key] = e.detail.value);

Then use JS.dispatch like this:

JS.dispatch('js:set', to: "#my-input", detail: %{key: "value", value: "hello"})

Or even simpler:

window.addEventListener("js:set_input_value", e => e.target.value = e.detail);
JS.dispatch('js:set_input_value', to: "#my-input", detail: "hello")
4 Likes