perf: optimize member index selection calculations

Calculate selected_count, any_selected? and mailto_bcc once in assigns
instead of recalculating Enum.any? and Enum.count multiple times in template.
This improves render performance and makes the template code more readable.
This commit is contained in:
Moritz 2025-12-16 14:50:52 +01:00
parent 222af635ae
commit fb91f748c2
2 changed files with 43 additions and 14 deletions

View file

@ -145,7 +145,10 @@ defmodule MvWeb.MemberLive.Index do
MapSet.put(socket.assigns.selected_members, id) MapSet.put(socket.assigns.selected_members, id)
end end
{:noreply, assign(socket, :selected_members, selected)} {:noreply,
socket
|> assign(:selected_members, selected)
|> update_selection_assigns()}
end end
@impl true @impl true
@ -159,7 +162,10 @@ defmodule MvWeb.MemberLive.Index do
all_ids all_ids
end end
{:noreply, assign(socket, :selected_members, selected)} {:noreply,
socket
|> assign(:selected_members, selected)
|> update_selection_assigns()}
end end
@impl true @impl true
@ -238,6 +244,7 @@ defmodule MvWeb.MemberLive.Index do
socket socket
|> assign(:query, q) |> assign(:query, q)
|> load_members() |> load_members()
|> update_selection_assigns()
existing_field_query = socket.assigns.sort_field existing_field_query = socket.assigns.sort_field
existing_sort_query = socket.assigns.sort_order existing_sort_query = socket.assigns.sort_order
@ -263,6 +270,7 @@ defmodule MvWeb.MemberLive.Index do
socket socket
|> assign(:paid_filter, filter) |> assign(:paid_filter, filter)
|> load_members() |> load_members()
|> update_selection_assigns()
# Build the URL with all params including new filter # Build the URL with all params including new filter
query_params = query_params =
@ -309,6 +317,7 @@ defmodule MvWeb.MemberLive.Index do
|> assign(:visible_custom_field_ids, extract_custom_field_ids(visible_custom_fields)) |> assign(:visible_custom_field_ids, extract_custom_field_ids(visible_custom_fields))
|> load_members() |> load_members()
|> prepare_dynamic_cols() |> prepare_dynamic_cols()
|> update_selection_assigns()
|> push_field_selection_url() |> push_field_selection_url()
{:noreply, socket} {:noreply, socket}
@ -338,6 +347,7 @@ defmodule MvWeb.MemberLive.Index do
|> assign(:visible_custom_field_ids, extract_custom_field_ids(visible_custom_fields)) |> assign(:visible_custom_field_ids, extract_custom_field_ids(visible_custom_fields))
|> load_members() |> load_members()
|> prepare_dynamic_cols() |> prepare_dynamic_cols()
|> update_selection_assigns()
|> push_field_selection_url() |> push_field_selection_url()
{:noreply, socket} {:noreply, socket}
@ -389,6 +399,7 @@ defmodule MvWeb.MemberLive.Index do
|> assign(:visible_custom_field_ids, extract_custom_field_ids(visible_custom_fields)) |> assign(:visible_custom_field_ids, extract_custom_field_ids(visible_custom_fields))
|> load_members() |> load_members()
|> prepare_dynamic_cols() |> prepare_dynamic_cols()
|> update_selection_assigns()
{:noreply, socket} {:noreply, socket}
end end
@ -1112,4 +1123,30 @@ defmodule MvWeb.MemberLive.Index do
# Public helper function to format dates for use in templates # Public helper function to format dates for use in templates
def format_date(date), do: DateFormatter.format_date(date) def format_date(date), do: DateFormatter.format_date(date)
# Updates selection-related assigns (selected_count, any_selected?, mailto_bcc)
# to avoid recalculating Enum.any? and Enum.count multiple times in templates.
defp update_selection_assigns(socket) do
members = socket.assigns.members
selected_members = socket.assigns.selected_members
selected_count =
Enum.count(members, &MapSet.member?(selected_members, &1.id))
any_selected? =
Enum.any?(members, &MapSet.member?(selected_members, &1.id))
mailto_bcc =
if any_selected? do
format_selected_member_emails(members, selected_members)
|> Enum.join(", ")
else
""
end
socket
|> assign(:selected_count, selected_count)
|> assign(:any_selected?, any_selected?)
|> assign(:mailto_bcc, mailto_bcc)
end
end end

View file

@ -7,25 +7,17 @@
id="copy-emails-btn" id="copy-emails-btn"
phx-hook="CopyToClipboard" phx-hook="CopyToClipboard"
phx-click="copy_emails" phx-click="copy_emails"
disabled={not Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))} disabled={not @any_selected?}
aria-label={gettext("Copy email addresses of selected members")} aria-label={gettext("Copy email addresses of selected members")}
> >
<.icon name="hero-clipboard-document" /> <.icon name="hero-clipboard-document" />
{gettext("Copy email addresses")} ({Enum.count( {gettext("Copy email addresses")} ({@selected_count})
@members,
&MapSet.member?(@selected_members, &1.id)
)})
</.button> </.button>
<.button <.button
class="secondary" class="secondary"
id="open-email-btn" id="open-email-btn"
href={ href={"mailto:?bcc=" <> URI.encode(@mailto_bcc)}
"mailto:?bcc=" <> disabled={not @any_selected?}
(MvWeb.MemberLive.Index.format_selected_member_emails(@members, @selected_members)
|> Enum.join(", ")
|> URI.encode())
}
disabled={not Enum.any?(@members, &MapSet.member?(@selected_members, &1.id))}
aria-label={gettext("Open email program with BCC recipients")} aria-label={gettext("Open email program with BCC recipients")}
> >
<.icon name="hero-envelope" /> <.icon name="hero-envelope" />