Send JavaScript variables from client to server

Hi I have the same issue that this person had here, but I do not think the solution is really relevant to what I am looking for and I don’t understand it that well without context. I tried to ask for clarification there, but I was not able to receive a response.

I just simply need a way to pass a variable from the client-side JS to the server-rendered views/templates.

I did look into this, but that honestly just appears to be above and beyond what I need and I’m wondering if there is a simpler way.

I am using Phoenix LiveView with Surface UI. I am new to elixir/phoenix. Thank you for any help.

1 Like

It depends on how complex your use-case is. If you need the Javascript variable to go through any logic on the server, then checkout LiveView Javascript Hooks, which work very well and you can create GenServer-like LiveControllers that react to client events. Most of the boilerplate is already setup in the included LiveView javascript.

However, if you simply want to display the values then the server can render an empty div with a specific id (tag) and the client-side JS can then use getElemementByTagName and set the value setting the innerHTML on the returned element.

1 Like

Thank you for the quick response. I have read through those JS interoperability docs in the past several times and did not see a clear way to achieve what I am setting out to do.

I don’t want to render the value directly into the HTML, I want to simply use the value to create dynamic HTML by using the value in a surface UI conditional

It’s definitely not that complex of a use case, it is literally just one JS variable value that stores a boolean that I want to pass to the view on the server so I can render some HTML conditionally

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#push_event/3

This is the function that exists for pushing a message to JS.

1 Like

If you want a more precise/descriptive answer, it helps to ask a more precise/descriptive answer.

For example, is the boolean set by a user interaction such as clicking a toggle or checkbox in the UI? If so, then you may be able to use the LiveView’s built-in phx-click binding.

Also, if “render some HTML conditionally” means just showing/hiding some HTML, then take a look at LiveView’s built-in JS commands which would let you show and hide elements on the page without even needing to make a round trip to the server.

Without more details, push_event would be the most flexible basic route to go as @benwilson512 noted above.

This is a minimal working example.

in your template for the liveview.

<div phx-hook="Testing">This will trigger registered hook Testing Hooks.Testing in your app.js file</div>

in your app.js file

Hooks.Testing = {
   mounted() {
/* this will trigger a push_event with some data beeing sent to your backend - you will need to define a handle for that in your live view .ex file - it will be triggered when page is loaded and the dom is "mounted" */
   this.pushEvent("test_event",
                                  {data: "Some data" })
}
}

in your live view .ex file

def handle_info(%{test_event: %{data: your_data}}, socket) do
  IO.inspect("this is the data sent from client: #{inspect your_data}")
{:noreply, socket}
end

hopefully this hacky example gives you some help. But as someone above pointed out, your question is very broad and after reading your responses I’m still not sure what it is you need help with.

Best regards
David

Thank you for the response, but that is the opposite of what I want to do. I already have an event listener registered on the window in app.js that fires upon a push_event() call, I just don’t know how to transmit the variable that I defined in that JS handler there to Elixir/Phoenix for further processing

this should be handle_event("test_event", %{"data" => your_data}, socket)

1 Like

Thanks for the response and sorry, I was assuming it was a simple question with a simple answer and I also thought the forum question I linked to would provide enough context here.

The boolean in the JS event handler is not set by user interaction. It is derived from window.matchMedia().matches.

I don’t really know enough about those JS commands you mentioned but it looks like it could possibly work for my needs.

I already have push_event in the phoenix/elixir code causing the JS event to fire in app.js, what I need is the “push_event” type of functionality within the handler in app.js to send something as a response to Elixir after the JS handler runs.

Thanks for your response. This is basically what I need, but is there a way to get that pushEvent() functionality without using hooks?

like do this instead of using hooks:

I took a look through some of those JS commands and they don’t look like they would work. I need more specific functionality than that. I do indeed need an element to be conditionally rendered, but I need to preserve all it’s child elements when it’s not rendered, like this:

{#if @isMobileViewport}
  <div>
    <p></p>
    <p></p>
  </div>
{#else}
 <p></p>
 <p></p>
{/if}

If I use the show/hide() functions from the JS commands on the <div>, I will lose those <p>'s as well when the div is not rendered, which I don’t want

you could always use the bindings functionality.

https://hexdocs.pm/phoenix_live_view/bindings.html

It sounds like it is what you are looking for.

You are able to expose “things” if you bind it to the window. So perhaps, in your mount;

Hooks.Testing = {
mount: {
window.my_special_thing = this
}
}

then you should be able to in console type something like; window.my_special_thing.pushEvent(“bla bla”, {data: “bla bla bla…”})

I appreciate it, but I don’t think it would work because there is no event that needs to be binded to any of those elements I provided in that HTML snippet

Can you elaborate about why hooks are a poor fit?

1 Like

With push_event/3 you can also send events directly to hooks. So, if I understood you use-case correctly, here’s what you could do:

your hook:

{
   mounted() { 
    this.handleEvent("event-from-server", data_from_server => {
      // do something client-side with data_from_server, compute response_from_client

     this.pushEvent("response-from-client", response_from_client);
    })
  }  
}

in your LV:

def handle_event("some-event", _params, socket) do
  socket = push_event(socket, "event-from-server", data_from_server)

  {:noreply, socket}
end

def handle_event("response-from-client", response_from_client, socket) do 
  # do something with the response from the client

 {:noreply, socket}
end

I have not used them for any of my previous JS event listeners and there is some additional setup with it, drobban mentioned this above Hooks.Testing = { mount: { window.my_special_thing = this } } but it looks like they are more so meant to be binded to elements. The event in question here does not fire due to any element interactions, I just call push_event() within def mount(). It looks like I may have to try and make hooks work though because there does not appear to be any other way.

thank you, I’m going to try this. Looks like you can’t do something similar without hooks by just using a plain window.addEventListener() and pushing data back to the server in the handler

That’s because pushing on mount can’t work. Mount runs before the page has rendered, which means it’s before any Js has had a chance to run. If you push out an event when mount returns it’s very likely the JS handlers won’t exist yet.

Instead the more reliable way is to have a JS hook, then have the hook tell the live view process it’s ready, and then the live view process can send it info.