@moduledoc """ Example Phoenix controller demonstrating StarView with Datastar. Features: - Active search with debounced input - Element patching with smart change detection - Optimistic client-side filtering with data-show """ use <%= inspect web_module %>, :starview @items [ "Elixir", "Phoenix", "LiveView", "Datastar", "SSE", "Plug", "Ecto", "Ash", "HEEx", "Tailwind" ] #======================================================================================= # Mount @impl StarView def mount(conn, _params) do conn |> signal(:query, "") |> signal(:results, @items) end #======================================================================================= # Initial Render @impl StarView def render(assigns) do ~H"""

Active Search

<.search_form /> <.item_list results={@results} /> <.no_results query={@query} />
""" end #======================================================================================= # Event handlers @impl StarView def handle_event("search", %{"query" => query} = signals, conn) do conn |> signal(:results, get_items(query)) |> maybe_patch_list(signals) end def handle_event("reset", signals, conn) do conn |> signal(:query, "") |> signal(:results, @items) |> maybe_patch_list(signals) end #======================================================================================= # Helpers defp get_items(""), do: @items defp get_items(query) do search_query = String.trim(String.downcase(query)) Enum.filter(@items, &String.contains?(&1 |> String.downcase(), search_query)) end # We use this to make the response as light as possible # Compared to LiveView which does this automatically with change tracking defp maybe_patch_list(%{assigns: %{results: x}} = conn, %{"results" => x}), do: conn defp maybe_patch_list(conn, _signals), do: patch_element(conn, &item_list/1) #======================================================================================= # JS helpers - for optimistic updates defp starts_with?(item) do "#{item}.trim().toLowerCase().startsWith($query.trim().toLowerCase())" end defp query_results(condition) do "$results.filter(x => #{starts_with?("x")}).length #{condition}" end #======================================================================================= # Function Components def search_form(assigns) do ~H"""
""" end attr :query, :string, default: nil def no_results(assigns) do ~H"""

No results found for "{@query}"

""" end attr :results, :list, default: [] def item_list(assigns) do ~H""" """ end attr :item, :string, required: true def item(assigns) do ~H"""
  • {@item}
  • """ end