Last active
June 14, 2020 23:50
-
-
Save azazel75/b67f3b178cb5ae9bd37a18a5d93fd544 to your computer and use it in GitHub Desktop.
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
# -*- 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 |
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
// -*- 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