HTMX with Flask - Bulma modals

HTMX with Flask - Bulma modals

While I used HTMX alert when deleting post comments, I felt like deleting a whole post was a bigger deal and should have a more fancy warning. Bulma provides a way to show modals, so I wanted to give it a try and have a nice big Bulma modal prompt to ask the user if they are sure they want to delete their whole post.

Making the modal

Starting from the back end, I already had a route for deleting the post that was called when the delete button was clicked. I planned to keep this as is and call it from the modal instead.

@posts.route("/post/<int:post_id>/delete", methods=['POST'])
@login_required
def delete_post(post_id):
    post = Post.query.get_or_404(post_id)
    if post.author != current_user:
        abort(403)
    db.session.delete(post)
    db.session.commit()
    flash('Your post has been deleted!', 'success')
    return redirect(url_for('main.home'))

This is a pretty basic function. It checks if the post exists and then if the user sending the request actually has the right to delete the post. If both conditions are met, it removes the post from the database, returns a success message and redirects the user to their home page since the post page on which they were previously no longer exists.

To use HTMX to show the modal, I needed a new route to call from the HTMX and get the HTML fragment for the modal. This is the route I came up with:

@posts.route("/post/<int:post_id>/delete", methods=['GET'])
@login_required
def delete_post_modal(post_id):
    post = Post.query.get_or_404(post_id)
    if post.author != current_user:
        abort(403)
    return render_template('modal_delete_post.html', post=post)

To keep it simple, it is the same route as the previous one, but instead of POST, it uses GET method to get the contents of the modal. It is again checking the credentials and making sure the post still exists before returning the rendered template for the modal fragment.

modal_delete_post.html

<div class="modal is-active">
    <div class="modal-background cancel"></div>
    <div class="modal-content is-vcentered">
        <div class="box content">
            <h2 class="is-2">Delete Post?</h2>
            <p>Your post will be deleted forever, along with all the comments it attracted. Are you sure you wish to delete it?</p>
            <form action="{{ url_for('posts.delete_post', post_id=post.id) }}" method="POST">
                <nav class="level is-mobile ">
                    <div class="level-left"></div>
                    <div class="level-right">
                      <div class="level-item">
                        <input class="button is-danger" type="submit" value="Delete">
                      </div>
                      <div class="level-item">
                        <a type="button" class="button is-primary cancel">Cancel</a>
                      </div>
                    </div>
                  </nav>
            </form>
        </div>
    </div>
</div>

Of note here is the class "cancel" which is not a Bulma class, but was added to later be used for closing the modal. I added it to both the modal background and to the "Cancel" button, because I wanted users to be able to close the modal either by clicking on the specifically designated button or anywhere on the page outside of the body of the modal.

Calling the modal

Now that I had the modal and route ready to go, I needed to make the call. This was easy with the HTMX call added to the original "Delete" button under the post.

<a type="button" class="button is-danger is-small is-light"
            hx-get="{{ url_for('posts.delete_post_modal', post_id=post.id) }}"
            hx-trigger="click"
            hx-target="#modal"
            hx-swap="innerHTML"
              >Delete</a>

And for the target, I placed a container div under everything else in the template like this:

<div id="modal"></div>

With that done, clicking on the "Delete" button under the post opened the modal, and clicking on the "Delete" button on the modal deleted the post.

Closing the modal

Now all that was left to do was add some way to close the modal. To show the modal with Bulma, it has to have "is-active" class. I included that class in the template for the modal fragment, so it would be visible as soon as it loads. Removing that class is enough to make it disappear. I decided to add some jQuery to make that happen.

As I mentioned above, I added the "cancel" class to elements that I wanted to act as buttons for closing the modal. I included the following script to achieve this.

modal.js

$(document).ready(function () {
    $("div#modal").on('click', '.cancel', function () {
        $(".modal").removeClass("is-active");
    });
});

This was a little tricky to achieve at first because the modal wasn't initially a part of the page but was instead loaded later. JavaScript didn't bind to the dynamically added buttons. To get around that problem, I had to bind JavaScript to the container div with "div#modal" which was then responsible for calling anything that was loaded inside it later on. I made the click on ".cancel" class remove the "is-active" class from the ".modal" elements on the page and that was all it took.

Now my posts have a red "Delete" button which asks for assurance in no uncertain terms that the user is indeed sure they wish to remove the said post from the site forever.

Photo by Ignacio Amenábar on Unsplash