Ability to change modal form backdrop element behavior

By default modal form is closed after clicking outside of it on a dark
backdrop (data-backdrop="true"). For the forms with a lot of fields
this could lead to a loss of data the user had entered before he has
unintentionally clicked outside of the form. So more preferable option
for such forms (and workflows/client-side modals) would be
data-backdrop="static".

DocImpact
Change-Id: I56fca9131a4e4d3991d53184ee1332e84138cdc2
Partially-Implements: blueprint form-template-to-view
Related-Bug: #1062065
This commit is contained in:
Timur Sufiev 2014-08-11 13:54:20 +04:00
parent 277fc76fb2
commit a6c364448a
9 changed files with 61 additions and 8 deletions

View File

@ -168,6 +168,20 @@ A dictionary containing classes of exceptions which Horizon's centralized
exception handling should be aware of. Based on these exception categories,
Horizon will handle the exception and display a message to the user.
``modal_backdrop``
------------------
.. versionadded:: 2014.2(Kilo)
Default: ``"static"``
Controls how bootstrap backdrop element outside of modals looks and feels.
Valid values are ``"true"`` (show backdrop element outside the modal, close
the modal after clicking on backdrop), ``"false"`` (do not show backdrop
element, do not close the modal after clicking outside of it) and ``"static"``
(show backdrop element outside the modal, do not close the modal after
clicking on backdrop).
``password_validator``
----------------------

View File

@ -15,6 +15,7 @@
import json
import os
from django.conf import settings
from django import http
from django.utils.translation import ugettext_lazy as _
from django.views import generic
@ -25,6 +26,35 @@ from horizon import exceptions
ADD_TO_FIELD_HEADER = "HTTP_X_HORIZON_ADD_TO_FIELD"
class ModalBackdropMixin(object):
"""This mixin class is to be used for together with ModalFormView and
WorkflowView classes to augment them with modal_backdrop context data.
.. attribute: modal_backdrop (optional)
The appearance and behavior of backdrop under the modal element.
Possible options are:
* 'true' - show backdrop element outside the modal, close the modal
after clicking on backdrop (the default one);
* 'false' - do not show backdrop element, do not close the modal after
clicking outside of it;
* 'static' - show backdrop element outside the modal, do not close
the modal after clicking on backdrop.
"""
modal_backdrop = 'static'
def __init__(self):
super(ModalBackdropMixin, self).__init__()
config = getattr(settings, 'HORIZON_CONFIG', {})
if 'modal_backdrop' in config:
self.modal_backdrop = config['modal_backdrop']
def get_context_data(self, **kwargs):
context = super(ModalBackdropMixin, self).get_context_data(**kwargs)
context['modal_backdrop'] = self.modal_backdrop
return context
class ModalFormMixin(object):
def get_template_names(self):
if self.request.is_ajax():
@ -47,7 +77,7 @@ class ModalFormMixin(object):
return context
class ModalFormView(ModalFormMixin, generic.FormView):
class ModalFormView(ModalBackdropMixin, ModalFormMixin, generic.FormView):
"""The main view class from which all views which handle forms in Horizon
should inherit. It takes care of all details with processing
:class:`~horizon.forms.base.SelfHandlingForm` classes, and modal concerns

View File

@ -33,9 +33,11 @@ horizon.modals.create = function (title, body, confirm, cancel) {
cancel = gettext("Cancel");
}
var template = horizon.templates.compiled_templates["#modal_template"],
params = {title: title, body: body, confirm: confirm, cancel: cancel},
modal = $(template.render(params)).appendTo("#modal_wrapper");
return modal;
params = {
title: title, body: body, confirm: confirm, cancel: cancel,
modal_backdrop: horizon.modals.MODAL_BACKDROP
};
return $(template.render(params)).appendTo("#modal_wrapper");
};
horizon.modals.success = function (data, textStatus, jqXHR) {

View File

@ -6,10 +6,10 @@
{% comment %} Django's JavaScript i18n Implementation {% endcomment %}
<script type="text/javascript" src="{% url 'horizon:jsi18n' 'horizon' %}"></script>
<script type="text/javascript">var STATIC_URL = "{{ STATIC_URL }}";</script>
{% comment %} Compress jQuery, Angular, Plugins, Bootstrap, Hogan.js and Horizon-specific JS. {% endcomment %}
{% compress js %}
<script type="text/javascript">var STATIC_URL = "{{ STATIC_URL }}";</script>
{% for file in HORIZON_CONFIG.js_files %}
<script src='{{ STATIC_URL }}{{ file }}' type='text/javascript' charset='utf-8'></script>
{% endfor %}
@ -42,6 +42,9 @@
<script src='{{ STATIC_URL }}horizon/js/horizon.instances.js' type='text/javascript' charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.messages.js' type='text/javascript' charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.modals.js' type='text/javascript' charset='utf-8'></script>
<script type="text/javascript">
horizon.modals.MODAL_BACKDROP = "{% firstof HORIZON_CONFIG.modal_backdrop 'static' %}";
</script>
<script src='{{ STATIC_URL }}horizon/js/horizon.quota.js' type='text/javascript' charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.tables.js' type='text/javascript' charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.tables_inline_edit.js' type='text/javascript' charset='utf-8'></script>

View File

@ -4,7 +4,7 @@
{% block id %}modal_template{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="modal">
<div class="modal" data-backdrop="[[modal_backdrop]]">
<div class="modal-dialog">
<div class="modal-content">
<div class='modal-header'>

View File

@ -1,4 +1,5 @@
<div id="{% block modal_id %}{{ modal_id }}{% endblock %}"
data-backdrop="{{ modal_backdrop }}"
class="{% block modal_class %}{% if hide %}modal{% else %}static_page{% endif %}{% endblock %}">
<div class="{% if hide %}modal-dialog{% endif %}">
<div class="{% if hide %}modal-content{% endif %}">

View File

@ -1,6 +1,6 @@
{% load i18n %}
{% with workflow.get_entry_point as entry_point %}
<div class="workflow {{ layout|join:' ' }}">
<div class="workflow {{ layout|join:' ' }}" data-backdrop="{{ modal_backdrop }}">
<form {{ workflow.attr_string|safe }} action="{{ workflow.get_absolute_url }}" {% if add_to_field %}data-add-to-field="{{ add_to_field }}"{% endif %} method="POST"{% if workflow.multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
{% if REDIRECT_URL %}<input type="hidden" name="{{ workflow.redirect_param_name }}" value="{{ REDIRECT_URL }}"/>{% endif %}
<div class="modal-dialog">

View File

@ -23,11 +23,12 @@ from django.views import generic
import six
from horizon import exceptions
from horizon.forms import views as hz_views
from horizon.forms.views import ADD_TO_FIELD_HEADER # noqa
from horizon import messages
class WorkflowView(generic.TemplateView):
class WorkflowView(hz_views.ModalBackdropMixin, generic.TemplateView):
"""A generic class-based view which handles the intricacies of workflow
processing with minimal user configuration.
@ -60,6 +61,7 @@ class WorkflowView(generic.TemplateView):
step_errors = {}
def __init__(self):
super(WorkflowView, self).__init__()
if not self.workflow_class:
raise AttributeError("You must set the workflow_class attribute "
"on %s." % self.__class__.__name__)

View File

@ -63,6 +63,7 @@ HORIZON_CONFIG = {
'exceptions': {'recoverable': exceptions.RECOVERABLE,
'not_found': exceptions.NOT_FOUND,
'unauthorized': exceptions.UNAUTHORIZED},
'modal_backdrop': 'static',
'angular_modules': [],
'js_files': [],
}