Why LiveView resets all the document created element (DOM)

Hi friends, I am creating a drag and drop template creator with Phoenix LiveView and sortable js.
When my user drops an element, I create divs with custom unique IDs and append them as a Dom.

All the js Hook and phx events work except when my state is updated, for example:

def handle_event("delete_access", %{"id" => id, "type" => "section-drag"}, socket) do
    {:noreply, assign(socket, :section_id, id)}
end

after updating the assign socket parameters, it resets and deletes all the created drag/drop elements which are created by sortable js


I put phx-update based on my experience, the first one phx-update="ignore" it allows me to put my JS code, and it has some limitation for example the changes are implemented in the same parent div is not shown

If I use append or prepend it shows me an error and say go delete the illegal node which has no ID, If all my elements have an ID

Please see this video:

JS example Code:

Sortable.create(getBlocks, {
  group: {
    name: 'LayoutGroup',
    pull: 'clone',
    put: false,
  },
  animation: sortableSpeed,
  sort: false,
  onStart: function (evt) {
    previewHelper.classList.add('hidden');
  },
  onEnd: function (evt) {
    if (putBlock.children.length === 1) {
      previewHelper.classList.remove('hidden');
    } else {
     ...
    }
  },
});

Thank you in advance

I don’t think I fully understood your implementation.

I’ve been using Sortable to order some survey answers for a specific question.

It works just fine, and what I do is to use a hook on the parent element (in my case, a ul), when things are dragged within the ul to reorder the elements, the hook sends an event to the LV which in turn applies the new order on its state and refreshes the page content.

In this scenario, the HTML order is defined by how my LV state is ordered. This is why I don’t set phx-update on the hook element.

You’re going to have weird issues if you have both LV and JS changing the same element, you need to stick with one. If you set phx-update=ignore, you need to leverage push_event in your LV when you update its state and send events back to Sortable.

This is the hook:

import Sortable from "sortablejs";

export default {
  mounted() {
    const hook = this;
    const selector = "#" + this.el.id;
    const event = this.el.dataset.dropEvent || "dropped";

    this.sortable = new Sortable(this.el, {
      animation: 0,
      delay: 50,
      delayOnTouchOnly: true,
      group: "shared",
      draggable: ".js-draggable",
      handle: ".js-handle",
      ghostClass: "bg-blue-50",
      onEnd: function (evt) {
        hook.pushEventTo(selector, event, {
          draggedId: evt.item.id,
          dropzoneId: evt.to.id,
          draggableIndex: evt.newDraggableIndex,
        });
      },
    });
  },
  destroyed() {
    this.sortable.destroy();
  },
};

This is the simplified element:

        <ul
          id="draggable-items"
          phx-target={@myself}
          phx-hook="Draggable"
          data-drop-event="choice-dropped"
        >
          <%= for choice <- @choices do %>
            <li id={choice.id} class="js-draggable">
              <span class="js-handle">handle element</span>
              <%= choice.text %>
            </li>
          <% end %>
        </ul>

And then the LV:

  def handle_event("choice-dropped", %{"draggedId" => question_id, "draggableIndex" => desired_position}, socket) do
    choices = # reorder choices using the question_id and desired_position
    socket = assign(socket, :choices, choices) # update the choices with new ordering so the HTML changes
    {:noreply, socket}
  end

Note that since I’m only using reordering I configured Sortable to do so and have added a specific class to elements within the ul that should be draggable as well as set a specific element that I need to click to drag.

3 Likes

Thank you for your answer, I think you are using basic of Sortable JS, when you use drag & drop an element which is clone, it deletes all the ID of the original elements, in another hand I need to re-create my HTML to let user put some stuff as dragged item dynamically.

For now as my gist you can see I use push_event and another stuff like listeners and custom one

But I think I need to use ignore my content, every time you create nested div the live view shows more errors

To me it still looks like you could do it only using Sortable to manage the UI interaction and have LV manage each element.

What you’re trying to accomplish is something like the Sortable clone example, isn’t it?

Instead of using clone to duplicate the element to the other container, I think you can just use the onEnd to send a message to LV that will: 1) restore the first list state so the element does not move away/vanish from it and is still available for future use, and 2) add the dropped element to the desired container.

If you’re interested, can you make a sample app with one LV and Sortable to try to do this? Perhaps I can hop in and try to help you out.

1 Like

Sure, your help clears my way, I just explain about the project and each element I have and why I did like this.

Before it, I am sorry because my English skill