Last active
May 6, 2019 13:42
-
-
Save joeesteves/372cf186665ab464413096fd3855f0ca to your computer and use it in GitHub Desktop.
Selection Panel Live View
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<%= selection_panel(@conn, @f, | |
[ %{ | |
key: :number, | |
panels: [ | |
%{ | |
title: "select_category_type", | |
collection: Mu.Misc.Constants.localized_category_types | |
} | |
] | |
} | |
]) %> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<%= selection_panel(@conn, @f, | |
[ %{ | |
key: :number, | |
panels: [ | |
%{ | |
title: "select_event_type", | |
collection: Mu.Misc.Constants.localized_event_types | |
} | |
], | |
child_selectors: [ | |
%{ | |
key: :constraint_src, | |
active_when_parent_in: [2,4], | |
opts: %{exclusive_til: 255}, | |
panels: [ | |
%{ | |
title: "select_categories_src", | |
collection: Mu.Misc.Constants.localized_category_types, | |
opts: %{ | |
action: "inclusive_toggle", | |
color: "rgb(70,100,170)" | |
} | |
}, | |
%{ | |
title: "select_stages_src", | |
collection: Mu.Misc.Constants.localized_stages, | |
opts: %{ | |
action: "inclusive_toggle", | |
color: "rgb(70,100,170)", | |
container_class: "w100 flex wrap"} | |
} | |
] | |
}, | |
%{ | |
key: :constraint_dst, | |
active_when_parent_in: [1,2], | |
opts: %{exclusive_til: 255}, | |
panels: [ | |
%{ | |
title: "select_categories_dst", | |
collection: Mu.Misc.Constants.localized_category_types, | |
opts: %{action: "inclusive_toggle", color: "rgb(170,70,70)"} | |
}, | |
%{ | |
title: "select_stages_dst", | |
collection: Mu.Misc.Constants.localized_stages, | |
opts: %{action: "inclusive_toggle", color: "rgb(170,70,70)", container_class: "w100 flex wrap"} | |
} | |
] | |
} | |
] | |
} | |
] | |
)%> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<%= for selector <- @selectors do %> | |
<div class="<%= show_container(get_value(@form, selector[:parent_key]), selector[:active_when_parent_in]) %>"> | |
<%= text_input @form, selector[:key], value: get_value(@form, selector[:key]), readonly: true, hidden: false %> | |
<%= for %{title: t, collection: c, opts: o} <- selector[:panels] do %> | |
<h3 class="w100"> <%= t %></h3> | |
<div class="<%= o[:container_class] %>"> | |
<%= for {name, value} <- c do %> | |
<div | |
class="<%= o[:member_class] %> selection-group__member <%= get_value(@form, selector[:key]) |> active(value, ~s[selection-group__member-active]) %>" | |
style="background-color: <%= o[:color] %>" | |
phx-click="<%= o[:action] %>" | |
phx-value="<%= [selector[:key], value] |> Enum.join(~s[,]) %>" | |
> | |
<%= name %> | |
</div> | |
<% end %> | |
</div> | |
<%= error_tag @form, selector[:key] %> | |
<% end %> | |
</div> | |
<% end %> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule MuWeb.SelectPanelView do | |
use MuWeb, :view | |
use Bitwise | |
@moduledoc """ | |
This module provides a view helper to easily plug a Selection Panel (visuale keypad) for handling bitwise flags options. | |
It support excluxive, inclusive and mixed selections. | |
## Selector | |
Each selector contains: | |
* key targeting the hidden input field on the form. | |
* Panels (one ore more): the visual key pads that feed the input | |
* Child_Selectors: Selectors that are toggle based on parent value | |
Example of a simple exclusive (default) selection_panel. It shows buttons for the categories in collection (collecionts must be bit flats.. {1, category_1}, {2, cat_2},{4, cat_4}) | |
<div> | |
<%= selection_panel(@conn, @f, | |
[ %{ | |
key: :number, | |
panels: [ | |
%{ | |
title: "select_category_type", | |
collection: Mu.Misc.Constants.localized_category_types | |
} | |
] | |
} | |
]) %> | |
</div> | |
""" | |
# exclusive_til defautls to 4 bytes (32 bits) | |
# This option is for mixed selectors, where one part is exlusive and other is incluive | |
# Excluive panels must be on top of inclusive ones and pass tht boundaries as exlusive_til. | |
@default_selector_opts %{ | |
exclusive_til: (1 <<< 32) - 1 | |
} | |
@default_panel_opts %{ | |
container_class: "w100 flex", | |
member_class: "w33", | |
color: "rgb(150, 170, 70)", | |
action: "exclusive_toggle" | |
} | |
# Selectors is a collection of button panels that affect input | |
def selection_panel(conn, form, selectors) do | |
selectors = parse_selectors(conn, selectors) | |
live_render( | |
conn, | |
MuWeb.SelectPanelLiveView, | |
session: [ | |
form: form, | |
selectors: selectors, | |
selectors_opts: parse_selectors_opts(selectors) | |
] | |
) | |
end | |
defp active(target_value, member_value, class) when is_number(target_value) do | |
((target_value &&& member_value) != 0 && class) || "" | |
end | |
defp active(_, _, _), do: false | |
defp add_parent(child_selectors, parent_key) do | |
Enum.map(child_selectors, fn child_selector -> | |
Map.put(child_selector, :parent_key, parent_key) | |
end) | |
end | |
defp flatten_selectors_with_childs(selectors) do | |
Enum.flat_map(selectors, fn selector -> | |
List.flatten([ | |
Map.delete(selector, :child_selectors), | |
(selector[:child_selectors] || []) |> add_parent(selector[:key]) | |
]) | |
end) | |
end | |
defp get_value(form, key) do | |
Ecto.Changeset.get_field(form.source, key) | |
end | |
defp merge_defaults(default_opts, opts) do | |
Map.merge(default_opts, opts) | |
end | |
defp parse_panels(panels) do | |
Enum.map(panels, fn panel -> | |
panel | |
|> Map.update!(:title, &translate/1) | |
|> Map.update(:opts, @default_panel_opts, &merge_defaults(@default_panel_opts, &1)) | |
end) | |
end | |
defp parse_selectors(conn, selectors) do | |
selectors | |
|> flatten_selectors_with_childs | |
|> Enum.map(fn selector -> | |
selector | |
|> Map.update(:opts, @default_selector_opts, &merge_defaults(@default_selector_opts, &1)) | |
|> Map.update!(:panels, &parse_panels/1) | |
end) | |
end | |
defp parse_selectors_opts(selectors) do | |
for selector <- selectors, into: %{}, do: {selector[:key], selector[:opts]} | |
end | |
defp show_container(_, nil), do: "" | |
defp show_container(value, active_when) do | |
(Enum.member?(active_when, value) && "") || "hide" | |
end | |
defp translate(string) do | |
Gettext.dgettext(MuWeb.Gettext, "labels", string) | |
end | |
defmacro __using__(opts) do | |
quote do | |
import MuWeb.SelectPanelView, only: [selection_panel: 3] | |
end | |
end | |
end | |
defmodule MuWeb.SelectPanelLiveView do | |
use Phoenix.LiveView | |
use Bitwise, only: :operators | |
def render(assigns) do | |
MuWeb.SelectPanelView.render("select_panel.html", assigns) | |
end | |
def mount(session, socket) do | |
{:ok, | |
assign(socket, | |
form: session[:form], | |
selectors: session[:selectors], | |
selectors_opts: session[:selectors_opts] | |
)} | |
end | |
defp decode_member_msg(member_msg) do | |
[key, value] = | |
member_msg | |
|> String.split(",") | |
{String.to_atom(key), String.to_integer(value)} | |
end | |
defp update_exclusibly(form, key, member_value, opts) do | |
exclusive_til = opts[:exclusive_til] | |
Map.update!(form, :source, fn prev_source -> | |
prev_value = Ecto.Changeset.get_field(prev_source, key) | |
new_value = | |
(is_nil(prev_value) && member_value) || | |
(prev_value &&& ~~~exclusive_til) ||| member_value | |
Ecto.Changeset.change(prev_source, %{key => new_value}) | |
end) | |
end | |
defp update_inclusibly(form, key, member_value) do | |
Map.update!(form, :source, fn prev_source -> | |
prev_value = Ecto.Changeset.get_field(prev_source, key) | |
new_value = | |
(is_nil(prev_value) && member_value) || | |
prev_value ^^^ member_value | |
Ecto.Changeset.change(prev_source, %{key => new_value}) | |
end) | |
end | |
def handle_event("exclusive_toggle", member_msg, socket) do | |
{key, member_value} = decode_member_msg(member_msg) | |
opts = socket.assigns.selectors_opts[key] | |
{ | |
:noreply, | |
update(socket, :form, &update_exclusibly(&1, key, member_value, opts)) | |
} | |
end | |
def handle_event("inclusive_toggle", member_msg, socket) do | |
{key, member_value} = decode_member_msg(member_msg) | |
{ | |
:noreply, | |
update(socket, :form, &update_inclusibly(&1, key, member_value)) | |
} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment