原文: https://www.abidibo.net/blog/2014/05/26/how-implement-modal-popup-django-forms-bootstrap/
作者: Stefano Contini
前言
有時候我們希望在不離開主頁或者索引頁的情況下顯示所有需要的用戶信息,換句話說,就是讓form表單顯示在一個新層layer上,在一個彈出的模態窗口modal window里。
要在一個模態窗口渲染表單非常容易,只需要使用ajax請求並且將返回結果顯示在你的模態窗口上即可。但是有的時候如果我們想繼續操作這個表單就比較棘手了,尤其是出錯的時候。通常情況下,django會重定向用戶到一個顯示出錯的表單,但是如果表單本身並沒有自己的頁(自己的完整模板),只是存在於一個模態窗口中,我們怎么來處理這種情況呢?
這兒,我們將介紹一種方法來解決這個問題,它會用到django, bootstrap和modal,jquery和jquery form plugin
場景
我們有一些列表條目帶有編輯按鈕,當點擊這些編輯按鈕時,表單將會渲染到一個模態窗口中,讓你在這個窗口中完成內容更新。
視圖
我們使用django類的ListView和UpdateView,第一個用來管理條目列表,第二個用於條目更新。
from django.views.generic import UpdateView, ListView from django.http import HttpResponse from django.template.loader import render_to_string from myapp.models import Item from myapp.forms import ItemForm """ items list """ class ItemListView(ListView): model = Item template_name = 'myapp/item_list.html' def get_queryset(self): return Item.objects.all() """ Edit item """ class ItemUpdateView(UpdateView): model = Item form_class = ItemForm template_name = 'myapp/item_edit_form.html' def dispatch(self, *args, **kwargs): self.item_id = kwargs['pk'] return super(ItemUpdateView, self).dispatch(*args, **kwargs) def form_valid(self, form): form.save() item = Item.objects.get(id=self.item_id) return HttpResponse(render_to_string('myapp/item_edit_form_success.html', {'item': item}))
ItemListView類很簡單,不做特別解釋。
在ItemUpdateView 類里,我重寫了dispatch 方法,它會從url里讀取item id,這樣我就能在form_valid函數里將這個item對象傳遞給模板item_edit_form_success template。模板item_edit_form僅僅包含modal的內容,沒有其他內容。
在urls.py里,你必須調用ItemUpdateView並在url里獲取 pk參數,它是即將編輯條目的id。
ItemForm也沒什么特別的,就不在這兒貼出它的內容了。
模板
接下來開始處理列表模板item_list.html。
{% extends 'base_site.html' %} {% block extra_js%} <script src="http://malsup.github.com/jquery.form.js"></script> {% endblock %} {% block content %} <section> <h1>Items list</h1> <!-- Modal --> <div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> </div><!-- /.modal --> <table class="table table-bordered table-hovered "cellspacing='0'> <tr> <th>Item</th> <th>Actions</th> </tr> {% for loan in object_list %} <tr> <td>{{ item.name }}</td> <td> <a class="fa fa-pencil" data-toggle="modal" href="{% url 'item_edit' item.id %}" data-target="#modal" title="edit item" data-tooltip></a> | </td> </tr> {% endfor %} </table> </section> {% endblock %}
有三件事情需要注意一下:
- 在頁面包含js plugin: jquery.form.js
- 添加modal container 的markup標記 (參考bootstrap modals)
- 使用鏈接去加載href屬性里url的響應內容,加載的內容顯示在modal內(ajax請求)。
學習bootstrap modals來理解這三點,重點關注的是:
如果remote url已經提供,內容會通過jquery load 方法一次獲取並注入到.modal-content div中。如果使用的是data-api,你需要使用href 屬性去指定遠端數據源。
上面這些響應內容從哪兒來的?這里會用到模板item_edit_form.html。
<div class="modal-dialog modal-lg"> <div class="modal-content"> <form id="item_update_form" method='post' class="form" role="form" action='{% url 'item_edit' item.id %}'> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel">Item {{ item.id }}</h4> </div> <div class="modal-body"> {% csrf_token %} {{ form.non_field_errors }} <div class="form-group"> {% for field in form %} <div class="form-group"> {% if field.errors %} <ul class="form-errors"> {% for error in field.errors %} <li><span class="fa fa-exclamation-triangle"></span> <strong>{{ error|escape }}</strong></li> {% endfor %} </ul> {% endif %} {{ field.label_tag }} {{ field }} {% if field.help_text %}<div class="form-helptext">{{ field.help_text }}</div>{% endif %} </div> {% endfor %} </div> <div class="modal-footer"> <input type="button" class="btn btn-default" data-dismiss="modal" value="annulla" /> <input type="submit" class="btn btn-primary" value="save" style="margin-bottom: 5px;" /> </div> </form> <script> jQuery('.modal-content .calendar').datepicker({ dateFormat: "yy-mm-dd" }); var form_options = { target: '#modal', success: function() { } } $('#item_update_form').ajaxForm(form_options); </script> </div><!-- /.modal-content --> </div><!-- /.modal-dialog -->
其他要考慮的一些點:
我們給form定義了一個id屬性。
並顯式了定義了form的action。為什么?因為form會在列表頁內加載,一個空的action屬性表示action就是當前頁(list url),這顯然不符合我們的期望。
我們通過ajax方式來提交表單。
第三點尤其重要,用django實現modal form遇到的最要的最大的問題之一是,如果遇到任何錯誤,它應該返回在form頁內,但是如果form沒有自己的頁,我們該怎么做呢?
你可能想到的方法是將結果返回到列表頁內,傳遞一些GET參數,讀這些參數並且重新打開modal,但是這樣的話form error變量會丟失。所以,你需要在list view實現form action,並且傳遞form處理結果到ajax url,由它渲染form。
一個簡單且優美的解決方案是通過ajax提交form並且捕捉響應。如果響應是相同的form並且包含錯誤的話,我們更新對應的modal內容,如果返回成功,我只需要關閉modal,非常簡單!
如果你還記得的話,modal container的id屬性是#modal,並且它也是form_option js對象的目標屬性,對modal的操作是通過這個id來完成的。
到此,方案基本已完成,如果發生錯誤,form在modal內顯示錯誤,不需要重新加載頁面。
我們還有最后一件事要做:實現表單提交沒有錯誤時的模板,該表單渲染返回結果response,名字item_edit_form_success.html,如下:
<div class="modal-dialog modal-lg"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title" id="myModalLabel">Pratica {{ loan.id }}</h4> </div> <div class="modal-body"> <p>Fuck yeah!</p> <script> setTimeout(function() { jQuery('#modal').modal('hide'); }, 1000); $('body').on('hidden.bs.modal', '.modal', function () { $(this).removeData('bs.modal'); }); </script> </div> <div class="modal-footer"> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog -->
我們首先顯示一些成功信息,一秒之后銷毀這個modal。請注意,這兒是銷毀不是隱藏,因為如果我們只是隱藏的話,將來點擊編輯按鈕,相同的內容會在modal里顯示(bootstrap工作原理)。因為這個原因,我們的做法是銷毀這個modal,下次調用時重新創建。
總結
在django內用bootstrap modal和 jquery form plugin來實現modal form是可行的。目標是通過ajax提交form,然后在相同的modal內加載ajax響應。如果出錯了,form會被重寫並且包含錯誤消息,否則提供一個成功返回會並通過js 代碼在一秒內銷毀這個modal。
參考資料
- https://www.abidibo.net/blog/2014/05/26/how-implement-modal-popup-django-forms-bootstrap/
- https://www.abidibo.net/blog/2015/11/18/modal-django-forms-bootstrap-4/
- https://getbootstrap.com/docs/3.3/javascript/#modals
關注下方公眾號獲取更多文章