Last active
October 23, 2020 22:56
-
-
Save mazz/ff4af076e51e28d41a31d48ff47e86ca to your computer and use it in GitHub Desktop.
liveview alpine modal live component
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
// We need to import the CSS so that webpack will load it. | |
// The MiniCssExtractPlugin is used to separate it out into | |
// its own CSS file. | |
import "../css/app.scss" | |
// webpack automatically bundles all modules in your | |
// entry points. Those entry points can be configured | |
// in "webpack.config.js". | |
// | |
// Import deps with the dep name or local files with a relative path, for example: | |
// | |
// import {Socket} from "phoenix" | |
// import socket from "./socket" | |
// | |
import "phoenix_html" | |
import {Socket} from "phoenix" | |
import NProgress from "nprogress" | |
import {LiveSocket} from "phoenix_live_view" | |
import {InitToast} from "./init_toast.js" | |
let Hooks = {} | |
Hooks.InitToast = InitToast | |
Hooks.initModal = { | |
mounted() { | |
const handleOpenCloseEvent = event => { | |
if (event.detail.open === false) { | |
this.el.removeEventListener("modal-change", handleOpenCloseEvent) | |
this.pushEvent("close-modal", {id: this.el.id}) | |
} | |
} | |
this.el.addEventListener("modal-change", handleOpenCloseEvent) | |
} | |
} | |
Hooks.closeModal = { | |
mounted() { | |
const modalId = this.el.dataset.modalId | |
const el = document.getElementById(modalId) | |
const event = new CustomEvent('close-modal'); | |
el.dispatchEvent(event) | |
} | |
} | |
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") | |
let liveSocket = new LiveSocket("/live", Socket, { | |
hooks: Hooks, | |
params: {_csrf_token: csrfToken}, | |
dom: { | |
onBeforeElUpdated(from, to){ | |
if(from.__x){ window.Alpine.clone(from.__x, to) } | |
} | |
} | |
}) | |
// Show progress bar on live navigation and form submits | |
window.addEventListener("phx:page-loading-start", info => NProgress.start()) | |
window.addEventListener("phx:page-loading-stop", info => NProgress.done()) | |
// connect if there are any LiveViews on the page | |
liveSocket.connect() | |
// expose liveSocket on window for web console debug logs and latency simulation: | |
// >> liveSocket.enableDebug() | |
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session | |
// >> liveSocket.disableLatencySim() | |
window.liveSocket = liveSocket | |
import "alpinejs" |
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 ElijahWeb.InvitationRequestLive.Index do | |
use ElijahWeb, :live_view | |
alias Elijah.Accounts.Invitations | |
alias Elijah.Schema.InvitationRequest | |
@impl true | |
def mount(_params, %{"csrf_token" => csrf_token} = _session, socket) do | |
if connected?(socket), do: Invitations.subscribe() | |
socket = assign(socket, | |
conn: socket, | |
csrf_token: csrf_token) | |
{:ok, assign(socket, :invitation_requests, fetch_invitation_requests()), temporary_assigns: [invitation_requests: []]} | |
end | |
@impl true | |
def handle_params(params, _url, socket) do | |
{:noreply, apply_action(socket, socket.assigns.live_action, params)} | |
end | |
# defp apply_action(socket, :edit, %{"id" => id}) do | |
# socket | |
# |> assign(:page_title, "Edit Post") | |
# |> assign(:invitation_request, Timeline.get_invitation_request!(id)) | |
# end | |
# defp apply_action(socket, :new, _params) do | |
# socket | |
# |> assign(:page_title, "New Post") | |
# |> assign(:invitation_request, %Post{}) | |
# end | |
defp apply_action(socket, :index, _params) do | |
socket | |
|> assign(:page_title, "Listing Invitation Requests") | |
|> assign(:invitation_request, nil) | |
end | |
@impl true | |
def handle_event("delete", %{"id" => id}, socket) do | |
invitation_request = Invitations.get_invitation!(id) | |
|> IO.inspect() | |
{:ok, _} = Invitations.delete_invitation(invitation_request) | |
{:noreply, assign(socket, :invitation_requests, fetch_invitation_requests())} | |
end | |
@impl true | |
def handle_info({:invitation_request_created, invitation_request}, socket) do | |
{:noreply, update(socket, :invitation_requests, fn invitation_requests -> [invitation_request | invitation_requests] end)} | |
end | |
def handle_info({:invitation_request_updated, invitation_request}, socket) do | |
{:noreply, update(socket, :invitation_requests, fn invitation_requests -> [invitation_request | invitation_requests] end)} | |
end | |
@impl true | |
def handle_info({:invitation_request_deleted, _invitation_request}, socket) do | |
{:noreply, assign(socket, :invitation_requests, fetch_invitation_requests())} | |
end | |
# MODAL | |
def handle_event("submit", %{"id" => id}, socket) do | |
send_update ElijahWeb.ModalComponent, id: id, action: "CLOSE" | |
{:noreply, socket} | |
end | |
def handle_event("open-modal", %{"id" => id}, socket) do | |
IO.inspect(id) | |
IO.inspect(socket) | |
send_update ElijahWeb.ModalComponent, id: id, state: "OPEN" | |
{:noreply, socket} | |
end | |
def handle_event("close-modal", %{"id" => id}, socket) do | |
:timer.sleep(300) # SO THE CSS ANIMATIONS HAVE TIME TO RUN | |
send_update ElijahWeb.ModalComponent, id: id, state: "CLOSED", action: nil | |
{:noreply, socket} | |
end | |
defp fetch_invitation_requests do | |
Invitations.list_invitations() | |
end | |
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
<div class="w-full card mb-20"> | |
<div class="card-header flex"> | |
<h5 class="mb-0 flex-1">Invitation Requests</h5> | |
</div> | |
<table class="card-body table"> | |
<thead> | |
<tr> | |
<th>Email</th> | |
<th>Token</th> | |
<th>Invitation Sent</th> | |
<th></th> | |
</tr> | |
</thead> | |
<tbody> | |
<%= for invitation_request <- @invitation_requests do %> | |
<tr> | |
<td><%= invitation_request.email %></td> | |
<td><%= invitation_request.token %></td> | |
<td><%= invitation_request.invitation_sent %></td> | |
<td class="w-1/12 whitespace-no-wrap"> | |
<span class="rounded-md shadow-sm"> | |
<button phx-click="open-modal" phx-value-id="modal-one" type="button" class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150">Open modal one</button> | |
</span> | |
<%= link "Show", to: Routes.invitation_request_path(@conn, :show, invitation_request), class: "btn btn-sm btn-link" %> | |
<%= link "Edit", to: Routes.invitation_request_path(@conn, :edit, invitation_request), class: "btn btn-sm btn-link" %> | |
<%= link "Delete", to: "#", phx_click: "delete", phx_value_id: invitation_request.id, class: "btn btn-sm btn-link", data: [confirm: "This will stop the user from redeeming the invite request. Are you sure?"] %> | |
</td> | |
</tr> | |
<% end %> | |
</tbody> | |
</table> | |
</div> | |
<%= live_component @socket, ElijahWeb.ModalComponent, id: "modal-one" do %> | |
<form> | |
<div class="mb-4"> | |
<label class="block text-gray-700 text-sm font-bold mb-2" for="username"> | |
Username | |
</label> | |
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="Username"> | |
</div> | |
<div class="mb-6"> | |
<label class="block text-gray-700 text-sm font-bold mb-2" for="password"> | |
Password | |
</label> | |
<input class="shadow appearance-none border border-red-500 rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" id="password" type="password" placeholder="******************"> | |
<p class="text-red-500 text-xs italic">Please choose a password.</p> | |
</div> | |
<div class="flex items-center justify-between"> | |
<button phx-click="submit" phx-value-id="modal-one" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="button"> | |
Sign In | |
</button> | |
<a class="inline-block align-baseline text-sm text-indigo-600 hover:text-indigo-800" href="#"> | |
Forgot Password? | |
</a> | |
</div> | |
</form> | |
<% 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
defmodule ElijahWeb.ModalComponent do | |
use Phoenix.LiveComponent | |
require Logger | |
def mount(socket) do | |
Logger.info("mount ElijahWeb.ModalComponent") | |
IO.inspect(socket) | |
{:ok, assign(socket, state: "CLOSED", action: nil)} | |
end | |
def render(assigns) do | |
IO.inspect(assigns) | |
if assigns.state == "OPEN" do | |
~L""" | |
<div | |
id="<%= @id %>" | |
phx-hook="initModal" | |
x-data="{ open: false }" | |
x-init="() => { | |
setTimeout(() => open = true, 100); | |
$watch('open', isOpen => $dispatch('modal-change', { open: isOpen })) | |
}" | |
x-show="open" | |
@close-modal="setTimeout(() => open = false, 100)" | |
class="z-50 fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center" | |
> | |
<%= if @action == "CLOSE" do %> | |
<div id="close-modal-<%= @id %>" data-modal-id="<%= @id %>" phx-hook="closeModal"></div> | |
<% end %> | |
<!-- BACKDROP --> | |
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity"> | |
<div class="absolute inset-0 bg-gray-800 opacity-75"></div> | |
</div> | |
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" class="bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full"> | |
<div @click.away="open = false" class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | |
<div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4"> | |
<button type="button" @click="open = false" class="text-gray-400 hover:text-gray-500 focus:outline-none focus:text-gray-500 transition ease-in-out duration-150"> | |
<svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24"> | |
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> | |
</svg> | |
</button> | |
</div> | |
<!-- CONTENT --> | |
<%= @inner_content.([]) %> | |
</div> | |
</div> | |
</div> | |
""" | |
else | |
~L""" | |
<div id="closed-modal-<%= @id %>"></div> | |
""" | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment