-
-
Save ondrej-tucek/523f40498855e05ad550c9c7d590c93f to your computer and use it in GitHub Desktop.
LiveView Bulma modal window
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 ServerWeb.Component.ModalLive do | |
@moduledoc """ | |
This is a general modal live component defined as blocks. | |
You can set the title of modal window, his body content and text along with | |
css styling for two action buttons (confirm and close). | |
Hence first thing what to do is, set these settings in mount section: | |
def mount(_params, _session, socket) do | |
socket = | |
socket | |
|> ... | |
|> ModalLive.init( | |
title: "Smazání pacienta", | |
primary_btn_label: "Ano, smazat", | |
primary_btn_class: "is-danger", | |
secondary_btn_label: "Zrušit", | |
on_confirm: &delete_patient/1, | |
on_cancel: fn _ -> nil end | |
) | |
{:ok, socket} | |
end | |
Then you have to set up which kind of data want to send to Modal body content: | |
def handle_event( | |
"open_modal", | |
%{ | |
"id" => id, | |
"name" => name, | |
"surname" => surname, | |
"phone" => phone | |
}, | |
socket | |
) do | |
patient = %{id: String.to_integer(id), name: name, surname: surname, phone: phone} | |
{:noreply, ModalLive.put(socket, patient)} | |
end | |
Last thing is handle message from Modal component: | |
def handle_info( | |
{ServerWeb.Component.ModalLive, :close_modal}, | |
socket | |
) do | |
socket = | |
socket | |
|> ModalLive.clear() | |
|> ... | |
{:noreply, socket} | |
end | |
Now the component can be called (anywhere you want) like: | |
<%= live_component @socket, ModalLive, id: "delete-modal", modal: @modal do %> | |
<div>Přejete si smazat pacienta</div> | |
<div class="media"> | |
<div class="media-content has-padding-20"> | |
<p class="title is-4"><%= @data.name %> <%= @data.surname %></p> | |
<p class="subtitle is-6"><%= @data.phone %></p> | |
</div> | |
</div> | |
<div>z databáze?</div> | |
<div class="has-padding-top-20"> | |
Údaje pacienta budou odebrány z databáze a nebudou již nadále k dispozici. | |
</div> | |
<% end %> | |
""" | |
use Phoenix.LiveComponent | |
alias Phoenix.LiveView.Socket | |
defmodule Settings do | |
@moduledoc """ | |
Default settings for Modal component. | |
""" | |
@type data() :: ExMaybe.t(any()) | |
@type t() :: %__MODULE__{ | |
title: ExMaybe.t(String.t()), | |
primary_btn_label: ExMaybe.t(String.t()), | |
primary_btn_class: ExMaybe.t(String.t()), | |
secondary_btn_label: ExMaybe.t(String.t()), | |
secondary_btn_class: ExMaybe.t(String.t()), | |
on_confirm: (data() -> any()), | |
on_cancel: (data() -> any()), | |
data: data() | |
} | |
defstruct title: "Description label", | |
primary_btn_label: "Confirm", | |
primary_btn_class: "is-success", | |
secondary_btn_label: "Cancel", | |
secondary_btn_class: "is-secondary", | |
on_confirm: nil, | |
on_cancel: nil, | |
data: nil | |
def create(opts) when is_list(opts) do | |
%__MODULE__{ | |
title: Keyword.get(opts, :title, "Description label"), | |
primary_btn_label: Keyword.get(opts, :primary_btn_label, "Confirm"), | |
primary_btn_class: Keyword.get(opts, :primary_btn_class, "is-success"), | |
secondary_btn_label: Keyword.get(opts, :secondary_btn_label, "Cancel"), | |
secondary_btn_class: Keyword.get(opts, :secondary_btn_class, "is-secondary"), | |
on_confirm: | |
Keyword.get(opts, :on_confirm) || raise("on_confirm/1 callback must be provided!!!"), | |
on_cancel: | |
Keyword.get(opts, :on_cancel) || raise("on_cancel/1 callback must be provided!!!"), | |
data: nil | |
} | |
end | |
end | |
@spec init(Socket.t(), keyword()) :: Socket.t() | |
def init(%Socket{} = socket, opts) when is_list(opts) do | |
assign(socket, modal: Settings.create(opts)) | |
end | |
@spec put(Socket.t(), any()) :: Socket.t() | |
def put(%Socket{assigns: %{modal: %Settings{} = settings}} = socket, data) do | |
assign(socket, :modal, %Settings{settings | data: data}) | |
end | |
@spec clear(Socket.t()) :: Socket.t() | |
def clear(%Socket{assigns: %{modal: %Settings{} = settings}} = socket) do | |
assign(socket, :modal, %Settings{settings | data: nil}) | |
end | |
@spec render(map()) :: Phoenix.LiveView.Rendered.t() | |
def render(assigns) do | |
~L""" | |
<%= if not is_nil(@modal.data) do %> | |
<div id="<%= @id %>" class="modal is-active"> | |
<div class="modal-background"></div> | |
<div class="modal-card"> | |
<header class="modal-card-head"> | |
<p class="modal-card-title"> | |
<%= @modal.title %> | |
</p> | |
</header> | |
<section class="modal-card-body"> | |
<div class="card-content has-padding-5"> | |
<%= @inner_content.(data: @modal.data) %> | |
</div> | |
</section> | |
<footer class="modal-card-foot"> | |
<button | |
class="button <%= @modal.primary_btn_class %>" | |
phx-click="modal_confirm" | |
phx-target="#<%= @id %>" | |
> | |
<%= @modal.primary_btn_label %> | |
</button> | |
<button | |
class="button <%= @modal.secondary_btn_class %>" | |
phx-click="modal_close" | |
phx-target="#<%= @id %>" | |
> | |
<%= @modal.secondary_btn_label %> | |
</button> | |
</footer> | |
</div> | |
</div> | |
<% end %> | |
""" | |
end | |
def mount(_params, _session, socket) do | |
{:ok, socket} | |
end | |
def handle_event( | |
"modal_confirm", | |
_params, | |
%Socket{ | |
assigns: %{ | |
modal: %Settings{on_confirm: on_confirm, data: data} | |
} | |
} = socket | |
) do | |
on_confirm.(data) | |
send(self(), {__MODULE__, :close_modal}) | |
{:noreply, socket} | |
end | |
def handle_event( | |
"modal_close", | |
_params, | |
%Socket{ | |
assigns: %{ | |
modal: %Settings{on_cancel: on_cancel, data: data} | |
} | |
} = socket | |
) do | |
on_cancel.(data) | |
send(self(), {__MODULE__, :close_modal}) | |
{:noreply, socket} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment