DEV Community

Imam Ali Mustofa
Imam Ali Mustofa

Posted on • Edited on • Originally published at darkterminal.prose.sh

Create a Dynamic Modal using PHP and HTMX #0

Hello Punk! I am again... sorry for that.

The follow up question that come up in my invisible head about this journal:

Jd Headless Jd GIF - https://tenor.com/view/jd-headless-jd-john-dorian-bye-funny-gif-14134807

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?!

High Level Plan
Screenshot from htmx modal custom - example

The Meme

20 Year Old

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>
Enter fullscreen mode Exit fullscreen mode

The Create 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>

Enter fullscreen mode Exit fullscreen mode

The Modal Itself!

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>
Enter fullscreen mode Exit fullscreen mode

and this

<button class="btn btn-sm btn-ghost btn-circle" _="on click trigger closeModal"><?= Icons::use('XMark') ?></button>
Enter fullscreen mode Exit fullscreen mode

Oh sorry, I am not mention it before. If you don't know what is that, that's the _hyperscript.

_hyperscript little introduction

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">
Enter fullscreen mode Exit fullscreen mode

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.

Tommy Boy Comedy GIF - https://tenor.com/view/tommy-boy-comedy-chris-farley-stressed-upset-gif-11704610607797404331

Huuuuuh... how good I am explaining!? Dang bro... that sound weird for me!

First Milestone

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);}
}
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it! Here I am...

First Milestone

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...

Top comments (0)