defmodule LivebookWeb.Output.TableDynamicLive do use LivebookWeb, :live_view @limit 10 @loading_delay_ms 100 @impl true def mount(_params, %{"pid" => pid, "id" => id}, socket) do send(pid, {:connect, self()}) {:ok, assign(socket, id: id, pid: pid, loading: true, show_loading_timer: nil, # Data specification page: 1, limit: @limit, order_by: nil, order: :asc, # Fetched data name: "Table", features: [], columns: [], rows: [], total_rows: 0 )} end @impl true def render(%{loading: true} = assigns) do ~L"""
""" end def render(assigns) do ~L"""

<%= @name %>

<%= if :refetch in @features do %> <%= tag :button, class: "icon-button", phx_click: "refetch" %> <%= remix_icon("refresh-line", class: "text-xl") %> <% end %>
<%= if :pagination in @features and @total_rows > 0 do %>
<%= tag :button, class: "flex items-center font-medium text-sm text-gray-400 hover:text-gray-800 disabled:pointer-events-none disabled:text-gray-300", disabled: @page == 1, phx_click: "prev" %> <%= remix_icon("arrow-left-s-line", class: "text-xl") %> Prev
<%= @page %> of <%= max_page(@total_rows, @limit) %>
<%= tag :button, class: "flex items-center font-medium text-sm text-gray-400 hover:text-gray-800 disabled:pointer-events-none disabled:text-gray-300", disabled: @page == max_page(@total_rows, @limit), phx_click: "next" %> Next <%= remix_icon("arrow-right-s-line", class: "text-xl") %>
<% end %>
<%= if @columns == [] do %>

No data

<% else %>
<%= for {column, idx} <- Enum.with_index(@columns) do %> <% end %> <%= for row <- @rows do %> <%= for column <- @columns do %> <% end %> <% end %>
" phx-click="column_click" phx-value-column_idx="<%= idx %>">
<%= column.label %> <%= tag :span, class: unless(@order_by == column.key, do: "invisible") %> <%= remix_icon(order_icon(@order), class: "text-xl align-middle leading-none") %>
<%= to_string(row.fields[column.key]) %>
<% end %> """ end defp order_icon(:asc), do: "arrow-up-s-line" defp order_icon(:desc), do: "arrow-down-s-line" defp max_page(total_rows, limit) do ceil(total_rows / limit) end @impl true def handle_event("refetch", %{}, socket) do {:noreply, request_rows(socket)} end def handle_event("prev", %{}, socket) do {:noreply, assign(socket, :page, socket.assigns.page - 1) |> request_rows()} end def handle_event("next", %{}, socket) do {:noreply, assign(socket, :page, socket.assigns.page + 1) |> request_rows()} end def handle_event("column_click", %{"column_idx" => idx}, socket) do idx = String.to_integer(idx) %{key: key} = Enum.at(socket.assigns.columns, idx) {order_by, order} = case {socket.assigns.order_by, socket.assigns.order} do {^key, :asc} -> {key, :desc} {^key, :desc} -> {nil, :asc} _ -> {key, :asc} end {:noreply, assign(socket, order_by: order_by, order: order) |> request_rows()} end @impl true def handle_info({:connect_reply, %{name: name, columns: columns, features: features}}, socket) do {:noreply, assign(socket, name: name, columns: columns, features: features) |> request_rows()} end def handle_info({:rows, %{rows: rows, total_rows: total_rows, columns: columns}}, socket) do columns = case columns do :initial -> socket.assigns.columns columns when is_list(columns) -> columns end if socket.assigns.show_loading_timer do Process.cancel_timer(socket.assigns.show_loading_timer) end {:noreply, assign(socket, loading: false, show_loading_timer: nil, columns: columns, rows: rows, total_rows: total_rows )} end def handle_info(:show_loading, socket) do {:noreply, assign(socket, loading: true, show_loading_timer: nil)} end defp request_rows(socket) do rows_spec = %{ offset: (socket.assigns.page - 1) * socket.assigns.limit, limit: socket.assigns.limit, order_by: socket.assigns.order_by, order: socket.assigns.order } send(socket.assigns.pid, {:get_rows, self(), rows_spec}) show_loading_timer = Process.send_after(self(), :show_loading, @loading_delay_ms) assign(socket, show_loading_timer: show_loading_timer) end end