Skip to content

Commit

Permalink
stream_insert update_only aka stream_update
Browse files Browse the repository at this point in the history
Fixes #2690.
Relates to: https://elixirforum.com/t/add-stream-update-to-liveview-streams/68107

Work in progress, no tests yet.
  • Loading branch information
SteffenDE committed Dec 15, 2024
1 parent 51db662 commit b3f7e83
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 11 deletions.
9 changes: 7 additions & 2 deletions assets/js/phoenix_live_view/dom_patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ export default class DOMPatch {
}
},
onBeforeNodeAdded: (el) => {
// don't add update_only nodes if they did not already exist
if(this.getStreamInsert(el)?.updateOnly && !this.streamComponentRestore[el.id]){
return false
}

DOM.maintainPrivateHooks(el, el, phxViewportTop, phxViewportBottom)
this.trackBefore("added", el)

Expand Down Expand Up @@ -296,8 +301,8 @@ export default class DOMPatch {

liveSocket.time("morphdom", () => {
this.streams.forEach(([ref, inserts, deleteIds, reset]) => {
inserts.forEach(([key, streamAt, limit]) => {
this.streamInserts[key] = {ref, streamAt, limit, reset}
inserts.forEach(([key, streamAt, limit, updateOnly]) => {
this.streamInserts[key] = {ref, streamAt, limit, reset, updateOnly}
})
if(reset !== undefined){
DOM.all(container, `[${PHX_STREAM_REF}="${ref}"]`, child => {
Expand Down
6 changes: 5 additions & 1 deletion lib/phoenix_live_view.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1834,6 +1834,9 @@ defmodule Phoenix.LiveView do
here as well in order to be enforced. See `stream/4` for more information on
limiting streams.
* `:update_only` - A boolean to only update the item in the stream. If the item does not
exist on the client, it will not be inserted. Defaults to `false`.
## Examples
Imagine you define a stream on mount with a single item:
Expand Down Expand Up @@ -1878,8 +1881,9 @@ defmodule Phoenix.LiveView do
def stream_insert(%Socket{} = socket, name, item, opts \\ []) do
at = Keyword.get(opts, :at, -1)
limit = Keyword.get(opts, :limit)
update_only = Keyword.get(opts, :update_only, false)

update_stream(socket, name, &LiveStream.insert_item(&1, item, at, limit))
update_stream(socket, name, &LiveStream.insert_item(&1, item, at, limit, update_only))
end

@doc """
Expand Down
4 changes: 3 additions & 1 deletion lib/phoenix_live_view/engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ defmodule Phoenix.LiveView.Comprehension do

@doc false
def __annotate__(comprehension, %Phoenix.LiveView.LiveStream{} = stream) do
inserts = for {id, at, _item, limit} <- stream.inserts, do: [id, at, limit]
inserts =
for {id, at, _item, limit, update_only} <- stream.inserts, do: [id, at, limit, update_only]

data = [stream.ref, inserts, stream.deletes]

if stream.reset? do
Expand Down
9 changes: 5 additions & 4 deletions lib/phoenix_live_view/live_stream.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ defmodule Phoenix.LiveView.LiveStream do
"stream :dom_id must return a function which accepts each item, got: #{inspect(dom_id)}"
end

items_list = for item <- items, do: {dom_id.(item), -1, item, opts[:limit]}
items_list =
for item <- items, do: {dom_id.(item), -1, item, opts[:limit], opts[:update_only]}

%LiveStream{
ref: ref,
Expand Down Expand Up @@ -58,10 +59,10 @@ defmodule Phoenix.LiveView.LiveStream do
%{stream | deletes: [dom_id | stream.deletes]}
end

def insert_item(%LiveStream{} = stream, item, at, limit) do
def insert_item(%LiveStream{} = stream, item, at, limit, update_only) do
item_id = stream.dom_id.(item)

%{stream | inserts: stream.inserts ++ [{item_id, at, item, limit}]}
%{stream | inserts: stream.inserts ++ [{item_id, at, item, limit, update_only}]}
end

defimpl Enumerable, for: LiveStream do
Expand All @@ -86,7 +87,7 @@ defmodule Phoenix.LiveView.LiveStream do
defp do_reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &do_reduce(list, &1, fun)}
defp do_reduce([], {:cont, acc}, _fun), do: {:done, acc}

defp do_reduce([{dom_id, _at, item, _limit} | tail], {:cont, acc}, fun) do
defp do_reduce([{dom_id, _at, item, _limit, _update_only} | tail], {:cont, acc}, fun) do
do_reduce(tail, fun.({dom_id, item}, acc), fun)
end

Expand Down
3 changes: 2 additions & 1 deletion lib/phoenix_live_view/test/dom.ex
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,8 @@ defmodule Phoenix.LiveViewTest.DOM do

streamInserts =
Enum.reduce(streams, %{}, fn %{ref: ref, inserts: inserts}, acc ->
Enum.reduce(inserts, acc, fn [id, stream_at, limit], acc ->
# TODO: support update_only in LiveViewTest
Enum.reduce(inserts, acc, fn [id, stream_at, limit, _update_only], acc ->
Map.put(acc, id, %{ref: ref, stream_at: stream_at, limit: limit})
end)
end)
Expand Down
12 changes: 10 additions & 2 deletions test/phoenix_live_view/live_stream_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,20 @@ defmodule Phoenix.LiveView.LiveStreamTest do

test "default dom_id" do
stream = LiveStream.new(:users, 0, [%{id: 1}, %{id: 2}], [])
assert stream.inserts == [{"users-1", -1, %{id: 1}, nil}, {"users-2", -1, %{id: 2}, nil}]

assert stream.inserts == [
{"users-1", -1, %{id: 1}, nil, nil},
{"users-2", -1, %{id: 2}, nil, nil}
]
end

test "custom dom_id" do
stream = LiveStream.new(:users, 0, [%{name: "u1"}, %{name: "u2"}], dom_id: &"u-#{&1.name}")
assert stream.inserts == [{"u-u1", -1, %{name: "u1"}, nil}, {"u-u2", -1, %{name: "u2"}, nil}]

assert stream.inserts == [
{"u-u1", -1, %{name: "u1"}, nil, nil},
{"u-u2", -1, %{name: "u2"}, nil, nil}
]
end

test "default dom_id without struct or map with :id" do
Expand Down

0 comments on commit b3f7e83

Please sign in to comment.