Skip to content

Instantly share code, notes, and snippets.

@ondrej-tucek
Created March 22, 2020 07:55
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 ondrej-tucek/523f40498855e05ad550c9c7d590c93f to your computer and use it in GitHub Desktop.
Save ondrej-tucek/523f40498855e05ad550c9c7d590c93f to your computer and use it in GitHub Desktop.
LiveView Bulma modal window
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