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

init after destroy(false) doesn't work #1204

Closed
ghost opened this issue Mar 10, 2020 · 5 comments
Closed

init after destroy(false) doesn't work #1204

ghost opened this issue Mar 10, 2020 · 5 comments

Comments

@ghost
Copy link

ghost commented Mar 10, 2020

Subject of the issue

A grid initialised on the DOM left by grid.destroy(false) doesn't seem to work: widgets are not draggable.

Your environment

  • version of gridstack.js 1.1.0
  • which browser and its version Chrome Version 80.0.3987.132 (Official Build) (64-bit)

Steps to reproduce

grid = GridStack.init() // works well
grid.destroy(false)
grid = GridStack.init() // widgets are no longer draggable

https://jsfiddle.net/n4f2dpv5/

Expected behaviour

Initialising a new grid (GridStack.init()) on the DOM of the destroyed one (grid.destroy(false)) should work the same way as initialising a "fresh" grid.

@adumesny
Copy link
Member

Hey thanks,
did this ever worked before ?
I assume this is a valid user case - what are you trying to do ?
FYI you can disable grid drag/resize by making the grid fixed as well instead of 'destroying it' and re-creating, in case you didn't know.

@ghost
Copy link
Author

ghost commented Mar 11, 2020

@adumesny Hi!

did this ever worked before ?

I haven't tried it before.

I assume this is a valid user case - what are you trying to do ?

I'm trying to use gridstack.js with phoenix liveview through so called hooks. The only way I have found for it to work so far is to destroy gridstack before update, and then reinit it after liveview patches the widgets in the DOM:

// WidgetGridHook.js

function positions(items) {
  return items.reduce((acc, item) => {
    let widgetId = item.el.id;
    acc[widgetId] = {
      x: item.x,
      y: item.y,
      width: item.width,
      height: item.height
    };
    return acc;
  }, {});
}

const WidgetGridHook = {
  mounted() {
    this.grid = GridStack.init();

    this.grid.on("change", (_event, items) => {
      if (items) {
        this.pushEvent("widget:move", { positions: positions(items) });
      }
    });
  },

  beforeUpdate() {
    this.grid.destroy(false);
  },

  updated() {
    this.grid = GridStack.init();

    this.grid.on("change", (_event, items) => {
      if (items) {
        this.pushEvent("widget:move", { positions: positions(items) });
      }
    });
  },

  beforeDestroy() {
    this.grid.destroy(false);
  }
};

export default WidgetGridHook;

And the liveview:

defmodule PlaybookWeb.PlaybookLive do
  use Phoenix.LiveView
  alias App.{Dashboards, Accounts}
  alias Dashboards.Dashboard

  def render(assigns) do
    positions = Dashboard.widget_positions(assigns.dashboard)

    ~L"""
    <div
      class="grid-stack"
      phx-hook="WidgetGridHook"
      style="height: 540px;">
      <%= for widget <- @dashboard.widgets do %>
        <div
          id="<%= widget.id %>"
          class="grid-stack-item"
          data-gs-x="<%= positions[widget.id]["x"] %>"
          data-gs-y="<%= positions[widget.id]["y"] %>"
          data-gs-width="<%= positions[widget.id]["width"] %>"
          data-gs-height="<%= positions[widget.id]["height"] %>"
          data-gs-min-width="1"
          data-gs-min-height="2">
          <div class="grid-stack-item-content bg-white h-100">
            <%= WidgetView.render("widget.html", widget: widget) %>
          </div>
        </div>
      <% end %>
    </div>
    """
  end

  def mount(%{"dashboard_id" => dashboard_id}, %{"user_id" => user_id}, socket) do
    if connected?(socket) do
      :ok = Dashboards.subscribe(dashboard_id)
    end

    current_user = Accounts.get_user!(user_id)
    dashboard = Dashboards.get_dashboard!(dashboard_id, current_user)
    {:ok, assign(socket, current_user: current_user, dashboard: dashboard)}
  end

  def handle_event("widget:move", %{"positions" => new_positions}, socket) do
    %{dashboard: dashboard, current_user: current_user} = socket.assigns
    old_positions = Dashboard.widget_positions(dashboard)

    positions =
      Enum.reduce(new_positions, old_positions, fn {widget_id, new_position}, acc ->
        Map.put(acc, widget_id, new_position)
      end)

    {:ok, dashboard} = Dashboards.persist_positions(dashboard, positions, current_user)
    {:noreply, assign(socket, dashboard: dashboard)}
  end
end

So the way it works right now is

  1. A browser makes a request for dashboards and gets the rendered HTML from PlaybookWeb.PlaybookLive.render/1 above.
  2. After that the browser makes a websocket connection to the server and initialises the liveview on the client together with WidgetGridHook which creates the grid in mounted().
  3. Now when the widget is moved, the browser sends "widget:move" event to the server which makes it persist the new positions and send the patched HTML back to the client.
  4. This is the step which is problematic. Phoenix LiveView uses morphdom to patch the DOM, which at least in my setup, results in the grid breaking down on any patch to .grid-stack children arriving from the server (the widgets disappear from the screen), so my attempt to make it work was to destroy the gridstack instance right before the update (beforeUpdate hook) and then reinitialise it with the new, updated DOM.

@adumesny
Copy link
Member

adumesny commented Mar 11, 2020

simple possible workaround for now (there might be more issues as I discovered stuff we didn't do during destroy(false) is to call grid.enable(); after you init the second time.

still a bug that it doesn't work out of the box.

adumesny added a commit to adumesny/gridstack.js that referenced this issue Mar 11, 2020
* calling `grid.destroy(false)` will clean things and let you init() again.
* removing an item will now nuke  jquery drag&drop using `draggable('destroy')` instead of just disabling.
This also removes all JQ styles added if you keep DOM element around.
* also removes extra grid style left behind
* fix for gridstack#1204
adumesny added a commit to adumesny/gridstack.js that referenced this issue Mar 11, 2020
* calling `grid.destroy(false)` will clean things and let you init() again.
* removing an item will now nuke  jquery drag&drop using `draggable('destroy')` instead of just disabling.
This also removes all JQ styles added if you keep DOM element around.
* also removes extra grid style left behind
* fix for gridstack#1204
@adumesny
Copy link
Member

fixed in next release.

@ghost
Copy link
Author

ghost commented Mar 12, 2020

@adumesny thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant