Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LiveView interfering with custom js #81

Closed
jamilabreu opened this issue Mar 17, 2019 · 4 comments

Comments

Projects
None yet
5 participants
@jamilabreu
Copy link
Contributor

commented Mar 17, 2019

Expected behavior

Any custom javascript should execute either after LiveView loads, or maybe have an observable event similar to Turbolinks:

document.addEventListener("turbolinks:load", function() {
  // ...
})

Actual behavior

I'm seeing that LiveView often interferes with my custom js, but adding a delay fixes things. In my case using Stimulus:

import { Controller } from "stimulus";

export default class extends Controller {
  static targets = ["element"];

  connect() {
    const element = this.elementTarget;

    // THIS DOESN'T EXECUTE
      if (element.clientHeight < element.scrollHeight) {
        // Do something that modifies the DOM
      }

    // THIS DOES
    setTimeout(() => {
      if (element.clientHeight < element.scrollHeight) {
        // Do same thing
      }
    }, 200);
  }
}
@chrismccord

This comment has been minimized.

Copy link
Member

commented Mar 18, 2019

Since we patch the Dom, your elements can appear/update/disapear at any time, so any event handlings on the DOM element in your JS aren't guaranteed to work. We dispatch a single phx:update event on the document today, but the javascript interop story is still TBD. We plan to tackle that once other areas are in better shape and we have enough usecases to make a generalized JS interop solution. Thanks!

@feliperenan

This comment has been minimized.

Copy link

commented May 15, 2019

OMG, phx:update is the event that I was looking for. In my case I need to re-initialize select2 every time Phoenix Live View changes the page.

Just sharing here, maybe it is going to help others:

const initSelect2 = () => {
  $('.select2').select2({ theme: 'bootstrap' })
}

// Re-initialize the select2 plugin every time Phoenix Live View changes the DOM.
$(document).on('phx:update', event => initSelect2())

@chrismccord, should we document this in somewhere?

@jrissler

This comment has been minimized.

Copy link

commented Jun 2, 2019

I'll post this here - I was in the same situation with selectize, here's what worked here:

const initSelectize = () => {
  $('.selectize_typeahead').each(function() {
    if (this.selectize) {
      var value = $(this).val(); // store the current value of the select/input
      $(this)[0].selectize.destroy(); // destroys selectize()
      $(this).val(value);
      }
    });
  $(".selectize_typeahead").selectize();
}

$(document).on('phx:update', event => initSelectize())
@alvises

This comment has been minimized.

Copy link

commented Jun 12, 2019

Did you have any problem reinitializing elements like select tag with select2?

Once select2 is initialized, it changes the select tag attributes. The problem I see re-initializing select/selectize at each update, is that if we have in the same LiveView template something updating many times per second (like a counter or a market price), each time the select tag is patched back to the original, and then calling the initialization many times per second we make the select dropdown almost impossible to use (and I don't even talk about performance)

I've simulated it sending messages to the LiveView process every 200ms

def mount(_session, socket) do
  socket = assign(socket, counter: 0)
  Process.send_after(self(), :incr, 200)
  {:ok, socket}
end

def render(assigns) do
~L"""
<label><%= @counter %></label>
<select class="select2">...</select>
"""
end

def handle_info(:incr, socket) do
  Process.send_after(self(), :incr, 250)
  {:noreply, assign(socket, counter: socket.assigns.counter + 1) }
end

lv_select2_reinitialize

We can put the select in another LiveView, but this is not enough since (If I understood correctly) phx:update is a generic event triggered for any update handled by any LiveView in the page, so our select2 element will be reinitialized for every LV update event in the page.

The only way I've found to solve this is to put the select element in a separate LiveView and then on each single phx:update event checks if select2 has been already initialized - checking if a class is present.

function initSelect2(selector) {
    if (!isInitialized()) {
        $(selector).select2()
    }
    function isInitialized() {
        return $(selector).hasClass("select2-hidden-accessible")
    }
}

In this way only when we change the select it's patched and re-initialized.

working_lv_select2

It works but still... it's a workaround and maybe it would be better to use channels directly !?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.