DjangoとBulmaで、削除ダイアログを実装する

2019-01-04 / PythonDjangoBulmaJavaScript

概要

DjangoとBulmaのモーダルウィンドウを使い、削除ダイアログで手軽にデータを消去するサンプルです

次のGIFのように動作します。
モーダルウィンドウが出て削除

モーダルウィンドウの表示位置がヘンに見えるかもしれませんが、これは録画範囲の関係です。ちゃんと中央にモーダルウィンドウが表示されます。

一括で削除したい場合は、Djangoで、選択したデータを一括削除も参考にしてください。

views.py

クラスベースビューの、ListViewDeleteViewを定義しておきます。urls.pyで、適当なURLと紐づけておいてください。

from django.urls import reverse_lazy
from django.views import generic
from .models import Post


class PostList(generic.ListView):
    model = Post


class PostDelete(generic.DeleteView):
    model = Post
    success_url = reverse_lazy('app:post_list')

models.py

モデルについては何でもいいです。適当なモデルを用意しておきましょう。

base.html

Bulmaのスターターテンプレートです。後で追加のJavaScriptを書くので、{% block extrajs %}{% endblock %}を用意しておきました。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Hello Bulma!</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
<body>
{% block content %}{% endblock %}
{% block extrajs %}{% endblock %}
</body>
</html>

Bulmaはjsファイルが付属していないので、そういった動作は全て自分で書く必要があります。jQueryだとかも付属しないので、素のJavaScriptの練習には最適です。

post_list.html

私ははPostというモデルを使っているので、ListViewのデフォルトではpost_list.htmlですね。

{% extends 'app/base.html' %}

{% block content %}
    <section class="section">
        <div class="container">
            <h1 class="title">記事一覧</h1>
            {% for post in post_list %}
                <div class="box">
                    <div class="columns">
                        <div class="column is-11">
                            <h2>{{ post.title }}</h2>
                        </div>
                        <div class="column is-1">
                            <button type="button" class="button is-danger delete-modal-button" data-deleteurl="{% url 'app:post_delete' post.pk %}">削除</button>
                        </div>
                    </div>
                </div>

            {% endfor %}
        </div>
    </section>

    <div class="modal" id="modal">
        <div class="modal-background"></div>
        <div class="modal-card">
            <header class="modal-card-head">
                <p class="modal-card-title">確認</p>
            </header>
            <section class="modal-card-body">
                本当に削除してよろしいですか。
            </section>
            <footer class="modal-card-foot">
                <form action="" method="POST" id="delete-form">
                    {% csrf_token %}
                    <button type="button" class="button is-info" id="delete-cancel-button">キャンセル</button>
                    <button type="submit" class="button is-danger">本当に削除</button>
                </form>
            </footer>
        </div>
    </div>

{% endblock %}

{% block extrajs %}
    <script>
        const deleteForm = document.getElementById('delete-form');
        const modal = document.getElementById('modal');
        const deleteCancelButton = document.getElementById('delete-cancel-button');
        const deleteModalButtons = document.getElementsByClassName('delete-modal-button');

        for (const button of deleteModalButtons) {
            button.addEventListener('click', () => {
                modal.classList.add('is-active');
                deleteForm.action = button.dataset.deleteurl;
            });
        }

        deleteCancelButton.addEventListener('click', () => {
            modal.classList.remove('is-active');
        });
    </script>
{% endblock %}

処理の流れ

  1. 「削除」ボタンを押す
  2. モーダルウィンドウ内のformaction属性に削除URLを設定し、モーダルウィンドウを表示
  3. 「本当に削除」ボタンでそのままsubmitし、キャンセルならモーダルウィンドウを閉じる(非表示にする)

記事の一覧表示部分

一覧表示部分は次の部分です。

    <section class="section">
        <div class="container">
            <h1 class="title">記事一覧</h1>
            {% for post in post_list %}
                <div class="box">
                    <div class="columns">
                        <div class="column is-11">
                            <h2>{{ post.title }}</h2>
                        </div>
                        <div class="column is-1">
                            <button type="button" class="button is-danger delete-modal-button" data-deleteurl="{% url 'app:post_delete' post.pk %}">削除</button>
                        </div>
                    </div>
                </div>

            {% endfor %}
        </div>
    </section>

<section class="section"><div class="container">は、単に見栄えをよくするためのものです。

{% for post in post_list %}として記事の一覧を取り出して、<div class="box">とします。boxを使うと線で囲まれ、良い感じに表示されますね。

11列を記事のタイトル部分にして、右端の1列に削除ボタンを配置しています。そして、ボタンには次のような指定をします。

<button type="button" class="button is-danger delete-modal-button" data-deleteurl="{% url 'app:post_delete' post.pk %}">削除</button>

classのbutton is-dangerは、赤っぽいBulmaのボタンを作っています。delete-modal-buttonは削除ボタンを判別するための印です。

data-deleteurl="{% url 'app:post_delete' post.pk %}"は、その記事を削除するのに必要なURLを埋め込んでいます。次のようなHTMLになります。

<button type="button" class="button is-danger delete-modal-button" data-deleteurl="/delete/1/">削除</button>

モーダルウィンドウ部分

削除ダイアログとなるモーダルウィンドウは次の部分です。

    <div class="modal" id="modal">
        <div class="modal-background"></div>
        <div class="modal-card">
            <header class="modal-card-head">
                <p class="modal-card-title">確認</p>
            </header>
            <section class="modal-card-body">
                本当に削除してよろしいですか。
            </section>
            <footer class="modal-card-foot">
                <form action="" method="POST" id="delete-form">
                    {% csrf_token %}
                    <button type="button" class="button is-info" id="delete-cancel-button">キャンセル</button>
                    <button type="submit" class="button is-danger">本当に削除</button>
                </form>
            </footer>
        </div>
    </div>

Bulmaの一般的なModalですが、form要素と、削除するためのsubmitなボタンと、キャンセルするための通常のボタンを用意しておきます。このフォームのactionに、削除ボタンのdata-deleteurl...削除するためのURLが設定されます。

「本当に削除」ボタンを押したらそのままsubmitしてデータが削除されますし、「キャンセル」の場合はモーダルウィンドウを非表示にします。

JavaScript部分

次の部分は、記事欄にある、各削除ボタンに対してイベントを設定していきます。

        for (const button of deleteModalButtons) {
            button.addEventListener('click', () => {
                modal.classList.add('is-active');
                deleteForm.action = button.dataset.deleteurl;
            });
        }

modal.classList.add('is-active');は、<div class="modal" id="modal"><div class="modal is-active" id="modal">にします。classの追加です。Bulmaではis-activeが追加されると、普段は見えないものが見えるようになります。モーダルもそうですし、ドロップダウンやナビバー等でもよく使います。

そして、「キャンセル」ボタンを押されたい際の処理が次です。is-activeを単純に削除して、非表示の状態に戻すだけですね。

        deleteCancelButton.addEventListener('click', () => {
            modal.classList.remove('is-active');
        });

この記事の関連記事

Djangoで、選択したデータを一括削除

2018-11-25 / PythonDjango

- データを一覧等で表示し、選択したデータを一括で削除したいというケースはよくあります。Djangoでのよく使うアプローチを二つ紹介します。

Bulmaでよく使うJavaScriptコード

2019-02-26 / BulmaJavaScript

- CSSフレームワークBulmaにはjsファイルは付属しません。なので、幾つかの処理は自分でJavaScriptを書く必要があります。良く使うものを、今回紹介していきます。

コメント欄

記事にコメントする

meteor

ほぼ毎日拝見させていただいています! こちらも大変勉強になったので、実装してみたいと考えているのですがbootstrap4でも同じ記述で動くのでしょうか?

コメントに返信する

なりと

流れは殆ど同じです。Bootstrap4ならばモーダルの表示や、キャンセルでの閉じる機能が用意されているので、少し簡単に書けます。

JavaScript部分は次のコードだけで良くなると思います。

        const deleteForm = document.getElementById('delete-form');
        const deleteModalButtons = document.getElementsByClassName('delete-modal-button');

        for (const button of deleteModalButtons) {
            button.addEventListener('click', () => {
                deleteForm.action = button.dataset.deleteurl;
            });
        }
meteor

ご丁寧にありがとうございます

udemyも受講させていただき基本を学ばせていただきました。 応用で少し複雑な機能の実装でブログを拝見させていただいています。

これは提案になるんですが、udemyで受講されて私みたいに基本は理解できたけど、 その後の拡張ができない人向けに月額で技術的なメンターになっていただけると助かります。

私なら10000円以上は全然出すのでご検討いただきたいです

なりと

ありがとうございます。前向きに検討させていただきます。