原文: 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
关注下方公众号获取更多文章