Skip to content

Instantly share code, notes, and snippets.

@azazel75
Last active June 14, 2020 23:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azazel75/b67f3b178cb5ae9bd37a18a5d93fd544 to your computer and use it in GitHub Desktop.
Save azazel75/b67f3b178cb5ae9bd37a18a5d93fd544 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
# :Project: journey -- dialog component
# :Created: lun 16 mar 2020, 02:56:55
# :Author: Alberto Berti <alberto@metapensiero.it>
# :License: GNU General Public License version 3 or later
# :Copyright: © 2020 Alberto Berti
#
defmodule Journey.Manage.Components.Dialog do
use Phoenix.LiveComponent
import Journey.Manage.Gettext
import Logger
@moduledoc """
Component that displays a dialog
The parent context has to be a LiveView not a LiveComponent because it
`send` a message when the dialog is closed to the process.
To use it just do the following:
1. Place the dialog markup in you LV, something like:
```html
<%= live_component @socket, Journey.Manage.Components.Dialog,
id: "dialog-id" do %>
<div> contents html here </div>
<% end %>
```
2. When it's time to show it, just call `show/2` from your code, for example:
```
Dialog.show("dialog-id", actions: [yes: gettext("yes"), no: gettext("no")], extra: rec_id)
```
The `:actions` will become buttons at the bottom of the dialog, and
`:extra` contains the additional data, that will be sent to the server when
the an action is clicked, together with the name of the clicked action.
3. handle the message from the dialog that it will send to the LV when it's
closed, like:
```
def handle_info(%__MODULE__{} = state, {:dialog_action,
%{"id" => "dialog-id", "name" => "yes"}, rec_id}, socket) do
Dialog.hide("dialog-id")
# handle the action
end
```
"""
@modal_base_classes "modal modal-animated--zoom-out"
@dismiss_event "dialog_action"
def render(assigns) do
~L"""
<% modal_id = "modal-#{@id}" %>
<div class="<%= @modal_class %>" id="<%= modal_id %>"
phx-hook="Dialog" data-id="<%= @id %>"
data-dismiss="<%= @dismiss_action %>"
data-event="<%= @event %>">
<a href="#" class="modal-overlay close-btn" aria-label="Close"></a>
<div class="modal-content" role="document">
<div class="modal-body">
<%= render_inner_content(@inner_content) %>
</div>
<div class="modal-footer">
<div class="u-flex">
<div class="level-left level-content">
<%= for act <- @actions, act == @primary_action do %>
<button id="<%= modal_id %>-<%= act.id %>"
class="btn-animated btn-primary"
data-name="<%= act.id %>"><%= act.label %></button>
<% end %>
</div>
<div class="level-right level-content">
<%= for act <- @actions, act != @primary_action do %>
<button id="<%= modal_id %>-<%= act.id %>"
class="btn-animated btn<%= act.class %>"
data-name="<%= act.id %>"><%= act.label %></button>
<% end %>
</div>
</div>
</div>
</div>
</div>
"""
end
def handle_event(@dismiss_event, params, socket) do
send self(), {socket.assigns.lv_event, params, socket.assigns.extra}
{:noreply, socket}
end
def hide(dialog_id) do
send_update(__MODULE__, id: dialog_id, show: false)
end
def show(dialog_id, opts \\ []) do
actions = opts[:actions] || [:ok]
lv_event = opts[:event] || :dialog_action
extra = opts[:extra]
actions = Enum.map(actions, fn act -> get_action_spec(act) end)
primary = case Enum.find(actions, fn act -> act[:role] == :primary end) do
nil -> hd actions
val -> val
end
dismiss_action = opts[:dismiss]
send_update(__MODULE__, id: dialog_id, show: true, actions: actions,
event: @dismiss_event, lv_event: lv_event, primary_action: primary,
dismiss_action: dismiss_action, extra: extra)
end
def update(%{show: true} = assigns, socket) do
{:ok, assign(socket, modal_class: "#{@modal_base_classes} shown",
id: assigns.id, actions: assigns.actions, event: assigns.event,
lv_event: assigns.lv_event, primary_action: assigns.primary_action,
dismiss_action: assigns.dismiss_action, extra: assigns.extra)}
end
def update(%{show: false}, socket) do
{:ok, assign(socket, modal_class: "#{@modal_base_classes} hidden")}
end
def update(assigns, socket) do
{:ok, assign(socket, modal_class: "#{@modal_base_classes} hidden",
id: assigns.id, inner_content: Map.get(assigns, :inner_content),
actions: [], event: nil, primary_action: nil,
dismiss_action: nil)}
end
defp get_action_spec(action) when is_atom(action) do
%{id: action, label: Atom.to_string(action), class: nil}
end
defp get_action_spec({id, label}) do
%{id: id, label: label, class: nil}
end
defp get_action_spec(%{id: id, class: nil, role: role} = action) when
role != nil do
%{id: action, label: action[:label] || Atom.to_string(id), class: "-#{role}",
role: role}
end
defp get_action_spec(%{id: id, class: cls, role: role} = action) do
%{id: action, label: action[:label] || Atom.to_string(id), class: cls,
role: role}
end
defp render_inner_content(nil), do: nil
defp render_inner_content(func), do: func.([])
end
// -*- coding: utf-8 -*-
// :Project: journey -- Dialog component js support
// :Created: mar 31 mar 2020, 13:16:37
// :Author: Alberto Berti <alberto@metapensiero.it>
// :License: GNU General Public License version 3 or later
// :Copyright: © 2020 Alberto Berti
//
let DialogHook = {
beforeDestroy() {
let dialog = this.el,
buttons = dialog.querySelectorAll('button');
buttons.forEach(function(el) {
el.onclick = null;
});
if (dialog.__esc_hndl)
document.removeEventListener('keyup', dialog.__esc_hndl);
},
mounted() {
let dialog = this.el,
pushEvent = this.pushEvent.bind(this);
/* Closes the dialog on "Esc" press, and the answer to your
non-asked question is yes, we really need to hook up' the
document instead of the dialog itself on the following
line. */
function handleDialogEsc(e) {
if(e.key === "Escape") {
pushEvent(dialog.dataset.event, {
name: dialog.dataset.dismiss || null, id: dialog.dataset.id});
};
}
dialog.__esc_hndl = handleDialogEsc;
document.addEventListener('keyup', handleDialogEsc);
},
updated() {
let dialog = this.el,
pushEventTo = this.pushEventTo.bind(this),
buttons = dialog.querySelectorAll('button');
function handleClick(el) {
pushEventTo('#' + dialog.id, dialog.dataset.event, {
name: el.target.dataset.name, id: dialog.dataset.id});
};
buttons.forEach(function(el) {
el.onclick = handleClick;
});
}
};
export {DialogHook};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment