Skip to content

Commit

Permalink
DataTable update (#425)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonatan Kłosko <[email protected]>
  • Loading branch information
cristineguadelupe and jonatanklosko authored May 9, 2024
1 parent 0f4278a commit 5eb60c2
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 9 deletions.
67 changes: 60 additions & 7 deletions lib/kino/data_table.ex
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,48 @@ defmodule Kino.DataTable do
"""
@spec new(Table.Reader.t(), keyword()) :: t()
def new(tabular, opts \\ []) do
tabular = normalize_tabular(tabular)

name = Keyword.get(opts, :name, "Data")
sorting_enabled = Keyword.get(opts, :sorting_enabled, true)
{data_rows, data_columns, count, inspected} = prepare_data(tabular, opts)

Kino.Table.new(__MODULE__, {data_rows, data_columns, count, name, sorting_enabled, inspected},
export: fn state -> {"text", state.inspected} end
)
end

@doc """
Updates the table to display a new tabular data.
## Options
* `:keys` - a list of keys to include in the table for each record.
The order is reflected in the rendered table. Optional
## Examples
data = [
%{id: 1, name: "Elixir", website: "https://elixir-lang.org"},
%{id: 2, name: "Erlang", website: "https://www.erlang.org"}
]
kino = Kino.DataTable.new(data)
Once created, you can update the table to display new data:
new_data = [
%{id: 1, name: "Elixir Lang", website: "https://elixir-lang.org"},
%{id: 2, name: "Erlang Lang", website: "https://www.erlang.org"}
]
Kino.DataTable.update(kino, new_data)
"""
def update(kino, tabular, opts \\ []) do
{data_rows, data_columns, count, inspected} = prepare_data(tabular, opts)
Kino.Table.update(kino, {data_rows, data_columns, count, inspected})
end

defp prepare_data(tabular, opts) do
tabular = normalize_tabular(tabular)
keys = opts[:keys]

{_, meta, _} = reader = init_reader!(tabular)
Expand All @@ -68,9 +106,7 @@ defmodule Kino.DataTable do

inspected = inspect(tabular)

Kino.Table.new(__MODULE__, {data_rows, data_columns, count, name, sorting_enabled},
export: fn _ -> {"text", inspected} end
)
{data_rows, data_columns, count, inspected}
end

defp normalize_tabular([%struct{} | _] = tabular) do
Expand Down Expand Up @@ -126,7 +162,7 @@ defmodule Kino.DataTable do
end

@impl true
def init({data_rows, data_columns, count, name, sorting_enabled}) do
def init({data_rows, data_columns, count, name, sorting_enabled, inspected}) do
features = Kino.Utils.truthy_keys(pagination: true, sorting: sorting_enabled)
info = %{name: name, features: features}

Expand All @@ -138,7 +174,8 @@ defmodule Kino.DataTable do
total_rows: count,
slicing_fun: slicing_fun,
slicing_cache: slicing_cache,
columns: Enum.map(data_columns, fn key -> %{key: key, label: value_to_string(key)} end)
columns: Enum.map(data_columns, fn key -> %{key: key, label: value_to_string(key)} end),
inspected: inspected
}}
end

Expand Down Expand Up @@ -269,4 +306,20 @@ defmodule Kino.DataTable do
inspect(value)
end
end

@impl true
def on_update({data_rows, data_columns, count, inspected}, state) do
{count, slicing_fun, slicing_cache} = init_slicing(data_rows, count)

{:ok,
%{
state
| data_rows: data_rows,
total_rows: count,
slicing_fun: slicing_fun,
slicing_cache: slicing_cache,
columns: Enum.map(data_columns, fn key -> %{key: key, label: value_to_string(key)} end),
inspected: inspected
}}
end
end
34 changes: 32 additions & 2 deletions lib/kino/table.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,14 @@ defmodule Kino.Table do
@callback export_data(rows_spec(), state(), String.t()) ::
{:ok, %{data: binary(), extension: String.t(), type: String.t()}}

@optional_callbacks export_data: 3
@doc """
Invoked to update state with new data.
This callback is called in response to `update/2`.
"""
@callback on_update(update_arg :: term(), state :: state()) :: {:ok, state()}

@optional_callbacks export_data: 3, on_update: 2

use Kino.JS, assets_path: "lib/assets/data_table/build"
use Kino.JS.Live
Expand Down Expand Up @@ -82,6 +89,17 @@ defmodule Kino.Table do
Kino.JS.Live.new(__MODULE__, {module, init_arg}, export: export)
end

@doc """
Updates the table with new data.
An arbitrary update event can be used and it is then handled by
the `c:on_update/2` callback.
"""
@spec update(t(), term()) :: :ok
def update(kino, update_arg) do
Kino.JS.Live.cast(kino, {:update, update_arg})
end

@impl true
def init({module, init_arg}, ctx) do
{:ok, info, state} = module.init(init_arg)
Expand Down Expand Up @@ -146,7 +164,7 @@ defmodule Kino.Table do
end

def handle_event("order_by", %{"key" => nil}, ctx) do
{:noreply, ctx |> assign(order: nil, page: 1) |> broadcast_update()}
{:noreply, ctx |> reset() |> broadcast_update()}
end

def handle_event("order_by", %{"key" => key_string, "direction" => direction}, ctx) do
Expand All @@ -161,6 +179,18 @@ defmodule Kino.Table do
{:noreply, ctx |> assign(relocates: relocates) |> broadcast_update()}
end

@impl true
def handle_cast({:update, update_arg}, ctx) do
unless Kino.Utils.has_function?(ctx.assigns.module, :on_update, 2) do
raise ArgumentError, "module #{inspect(ctx.assigns.module)} does not define on_update/2"
end

{:ok, state} = ctx.assigns.module.on_update(update_arg, ctx.assigns.state)
{:noreply, assign(ctx, state: state) |> reset() |> broadcast_update()}
end

defp reset(ctx), do: assign(ctx, order: nil, page: 1)

defp broadcast_update(ctx) do
{content, ctx} = get_content(ctx)
broadcast_event(ctx, "update_content", content)
Expand Down
33 changes: 33 additions & 0 deletions test/kino/data_table_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,37 @@ defmodule Kino.DataTableTest do
data: [["1", "1"], ["2", "2"] | _]
})
end

test "supports data update" do
entries = [
%User{id: 1, name: "Sherlock Holmes"},
%User{id: 2, name: "John Watson"}
]

kino = Kino.DataTable.new(entries)
data = connect(kino)

assert %{
content: %{
columns: [
%{key: "0", label: ":id"},
%{key: "1", label: ":name"}
],
total_rows: 2
}
} = data

new_entries = [
%User{id: 1, name: "Sherlock Holmes"},
%User{id: 2, name: "John Watson"},
%User{id: 3, name: "Tuka Tuka"}
]

Kino.DataTable.update(kino, new_entries)

assert_broadcast_event(kino, "update_content", %{
data: [["1", "Sherlock Holmes"], ["2", "John Watson"], ["3", "Tuka Tuka"]],
total_rows: 3
})
end
end

0 comments on commit 5eb60c2

Please sign in to comment.