Create a Dynamic Modal using PHP and HTMX #0
Imam Ali Mustofa
Posted on March 26, 2024
Hello Punk! I am again... sorry for that.
The follow up question that come up in my invisible head about this journal:
is... How can I create a modal that display form to create a new supplier in my app and load the content from backend (Yes! PHP) that generate the content, also doing validation and stuff, and removing the modal after the create operation is done!.
Hmmm... my question is super interesting. I would not compare with other approach (I mean language or framework), I just want to share what I am doing before (Yes! my fck PHP Framework)
Technically, I am so proud of myself and I need to prepare for something interesting and wasting my time! 😏 Oh yeah beibeh
What's The Plan?!
Screenshot from htmx modal custom - example
The Meme
The Hypertext Markup Language of The Modal
This not fully HTML (I see it bro!). Also have a PHP tag inside (Seriously!?) that's part of my fck PHP Framework (Fck'it bro!).
The Create Button
<button class="btn btn-sm btn-neutral" hx-get="<?= base_url('suppliers/create') ?>" hx-target="body" hx-swap="beforeend" id="exclude-indicator"><?= Icons::use('Plus', 'h-4 w-4') ?>Create new</button>
The Modal Itself!
<?php
// Filename: /home/darkterminal/workspaces/fck-htmx/views/components/suppliers/modals/modal-create.php
use App\config\helpers\Icons;
use App\config\helpers\Utils;
?>
<div id="modal" _="on closeModal wait 250ms add .closing then wait for animationend then remove me">
<div class="modal-underlay" _="on click trigger closeModal"></div>
<div class="w-7/12 min-h-80 max-h-[40em] modal-content">
<div class="flex justify-between p-2 mb-3 border-b-2">
<h2 class="text-lg font-bold">Create New Supplier</h2>
<button class="btn btn-sm btn-ghost btn-circle" _="on click trigger closeModal"><?= Icons::use('XMark') ?></button>
</div>
<form class="grid grid-cols-2 gap-3 p-3 w-full" method="post" hx-post="<?= base_url('suppliers/create') ?>" hx-target="#modal" hx-swap="outerHTML" autocomplete="off">
<label class="w-full form-control">
<div class="label">
<span class="label-text">Supplier Name</span>
</div>
<input type="text" name="supplierName" placeholder="Supplier name" class="w-full input input-sm input-bordered <?= !empty(Utils::getValidationError($errors, 'supplierName')) ? 'input-error' : '' ?>" />
<div class="label">
<?= Utils::getValidationError($errors, 'supplierName') ?>
</div>
</label>
<label class="w-full form-control">
<div class="label">
<span class="label-text">Supplier Company Name</span>
</div>
<input type="text" name="supplierCompanyName" placeholder="Supplier company name" class="w-full input input-sm input-bordered <?= !empty(Utils::getValidationError($errors, 'supplierCompanyName')) ? 'input-error' : '' ?>" />
<div class="label">
<?= Utils::getValidationError($errors, 'supplierCompanyName') ?>
</div>
</label>
<label class="w-full form-control">
<div class="label">
<span class="label-text">Supplier Phone Number</span>
</div>
<input type="text" name="supplierPhoneNumber" placeholder="Supplier phone number" class="w-full input input-sm input-bordered <?= !empty(Utils::getValidationError($errors, 'supplierPhoneNumber')) ? 'input-error' : '' ?>" />
<div class="label">
<?= Utils::getValidationError($errors, 'supplierPhoneNumber') ?>
</div>
</label>
<label class="w-full form-control">
<div class="label">
<span class="label-text">Supplier Email</span>
</div>
<input type="text" name="supplierEmail" placeholder="Supplier email address" class="w-full input input-sm input-bordered <?= !empty(Utils::getValidationError($errors, 'supplierEmail')) ? 'input-error' : '' ?>" />
<div class="label">
<?= Utils::getValidationError($errors, 'supplierEmail') ?>
</div>
</label>
<label class="w-full form-control">
<div class="label">
<span class="label-text">Latitude Coordinate</span>
</div>
<input type="text" name="latitude" placeholder="Latitude" class="w-full input input-sm input-bordered" />
</label>
<label class="w-full form-control">
<div class="label">
<span class="label-text">Longitude Coordinate</span>
</div>
<input type="text" name="longitude" placeholder="Longitude" class="w-full input input-sm input-bordered" />
</label>
<label class="col-span-2 w-full form-control">
<div class="label">
<span class="label-text">Address</span>
</div>
<textarea class="h-24 textarea textarea-bordered <?= !empty(Utils::getValidationError($errors, 'supplierAddress')) ? 'input-error' : '' ?>" name="supplierAddress" placeholder="Supplier Address"></textarea>
<div class="label">
<?= Utils::getValidationError($errors, 'supplierAddress') ?>
</div>
</label>
<div class="col-span-2">
<button class="btn btn-sm btn-block btn-neutral" type="submit">Save</button>
</div>
</form>
</div>
</div>
Wait... what is this?!
<div id="modal" _="on closeModal wait 250ms add .closing then wait for animationend then remove me">
<div class="modal-underlay" _="on click trigger closeModal"></div>
and this
<button class="btn btn-sm btn-ghost btn-circle" _="on click trigger closeModal"><?= Icons::use('XMark') ?></button>
Oh sorry, I am not mention it before. If you don't know what is that, that's the _hyperscript.
When the XMark
button is clicked
then the button will be send event called closeModal
, where the closeModal
event is received at:
<div id="modal" _="on closeModal wait 250ms add .closing then wait for animationend then remove me">
So whenever this event is (on
) triggered they will wait 250ms
and add
-ing a .closing
class in that element then wait for animationend then remove me
me mean that element itself.
Huuuuuh... how good I am explaining!? Dang bro... that sound weird for me!
First Milestone
The first milestone is to create this and display it. In this moment is not doing anything. But after we create a routing
for that modal request and adding some CSS (that I copy and paste in the htmx docs example)
The CSS (copy and paste)
Source: https://htmx.org/examples/modal-custom/
/***** MODAL DIALOG ****/
#modal {
/* Underlay covers entire screen. */
position: fixed;
top:0px;
bottom: 0px;
left:0px;
right:0px;
background-color:rgba(0,0,0,0.5);
z-index:1000;
/* Flexbox centers the .modal-content vertically and horizontally */
display:flex;
flex-direction:column;
align-items:center;
/* Animate when opening */
animation-name: fadeIn;
animation-duration:150ms;
animation-timing-function: ease;
}
#modal > .modal-underlay {
/* underlay takes up the entire viewport. This is only
required if you want to click to dismiss the popup */
position: absolute;
z-index: -1;
top:0px;
bottom:0px;
left: 0px;
right: 0px;
}
#modal > .modal-content {
/* Position visible dialog near the top of the window */
margin-top:10vh;
/* Sizing for visible dialog */
width:80%;
max-width:600px;
/* Display properties for visible dialog*/
border:solid 1px #999;
border-radius:8px;
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.3);
background-color:white;
padding:20px;
/* Animate when opening */
animation-name:zoomIn;
animation-duration:150ms;
animation-timing-function: ease;
}
#modal.closing {
/* Animate when closing */
animation-name: fadeOut;
animation-duration:150ms;
animation-timing-function: ease;
}
#modal.closing > .modal-content {
/* Animate when closing */
animation-name: zoomOut;
animation-duration:150ms;
animation-timing-function: ease;
}
@keyframes fadeIn {
0% {opacity: 0;}
100% {opacity: 1;}
}
@keyframes fadeOut {
0% {opacity: 1;}
100% {opacity: 0;}
}
@keyframes zoomIn {
0% {transform: scale(0.9);}
100% {transform: scale(1);}
}
@keyframes zoomOut {
0% {transform: scale(1);}
100% {transform: scale(0.9);}
}
The Routing #1 GET
<?php
// Filename: /home/darkterminal/workspaces/fck-htmx/routes/web.php
use Fckin\core\Application;
/** @var Application $app */
$app->router->get('suppliers/create', 'Suppliers@create');
This route is corresponding to get the modal content from the backend (Yes! PHP). then display in the body of the page as mentioned in this button and swap
it beforeend
:
<button class="btn btn-sm btn-neutral" hx-get="<?= base_url('suppliers/create') ?>" hx-target="body" hx-swap="beforeend" id="exclude-indicator"><?= Icons::use('Plus', 'h-4 w-4') ?>Create new</button>
The Controller #1 GET
<?php
// Filename: /home/darkterminal/workspaces/fck-htmx/controllers/Suppliers.php
namespace App\controllers;
use App\config\helpers\Utils;
use App\models\Suppliers as ModelsSuppliers;
use Fckin\core\Controller;
use Fckin\core\Request;
use Fckin\core\Response;
class Suppliers extends Controller
{
protected $suppliers;
public function __construct()
{
$response = new Response();
if (!isAuthenticate()) {
$response->setStatusCode(401);
exit();
}
$this->suppliers = new ModelsSuppliers();
}
public function create(Request $request)
{
$params = []; // <-- I will do it something here when make POST request
return Utils::addComponent('suppliers/modals/modal-create', $params);
}
}
That's it! Here I am...
If anything goes wrong or not working! It's my fault! not me. (Are you confuse? Yes, me too...)
See ya in the next stone! 👋 I am so tired!!! Hope you mad of me... thank you very much, I appreciate that...
Posted on March 26, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.