Commit 7037aac0 authored by Petrus Janse van Rensburg's avatar Petrus Janse van Rensburg

Merge pull request #919 from pawl/add_edit_modal2

Add modal for edit_view
parents 706dfff3 955271ef
...@@ -99,6 +99,10 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -99,6 +99,10 @@ class BaseModelView(BaseView, ActionsMixin):
create_template = 'admin/model/create.html' create_template = 'admin/model/create.html'
"""Default create template""" """Default create template"""
# Modals
edit_modal = False
"""Setting this to true will display the edit_view as a modal dialog."""
# Customizations # Customizations
column_list = ObsoleteAttr('column_list', 'list_columns', None) column_list = ObsoleteAttr('column_list', 'list_columns', None)
""" """
......
...@@ -69,8 +69,9 @@ table.filters tr td { ...@@ -69,8 +69,9 @@ table.filters tr td {
} }
/* Forms */ /* Forms */
/* adds spacing between navbar and edit/create form (non-modal only) */
/* required because form-horizontal removes top padding */ /* required because form-horizontal removes top padding */
.admin-form { div.container > .admin-form {
margin-top: 35px; margin-top: 35px;
} }
...@@ -79,4 +80,12 @@ table.filters tr td { ...@@ -79,4 +80,12 @@ table.filters tr td {
/* prevents awkward gap after help-block - This is default for bootstrap2 */ /* prevents awkward gap after help-block - This is default for bootstrap2 */
.admin-form .help-block { .admin-form .help-block {
margin-bottom: 0px; margin-bottom: 0px;
}
/* Modals */
/* hack to prevent cut-off left side of select2 inside of modal */
/* may be able to remove this after Bootstrap v3.3.5 */
body.modal-open {
overflow-y: scroll;
padding-right: 0 !important;
} }
\ No newline at end of file
...@@ -101,6 +101,24 @@ ...@@ -101,6 +101,24 @@
</div> </div>
{%- endmacro %} {%- endmacro %}
{# ---------------------- Modal Window -------------------------- #}
{% macro add_modal_window(modal_window_id='fa_modal_window') %}
<div id="{{ modal_window_id }}" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3>Loading...</h3>
</div>
<div class="modal-body">
</div>
</div>
{% endmacro %}
{% macro add_modal_button(url='', title='', content='', modal_window_id='fa_modal_window') %}
<a class="icon" href="#" data-toggle="modal" title="{{ title }}" data-target="#{{ modal_window_id }}" data-remote="{{ url }}">
{{ content|safe }}
</a>
{% endmacro %}
{# ---------------------- Forms -------------------------- #} {# ---------------------- Forms -------------------------- #}
{% macro render_field(form, field, kwargs={}, caller=None) %} {% macro render_field(form, field, kwargs={}, caller=None) %}
{% set direct_error = h.is_field_error(field.errors) %} {% set direct_error = h.is_field_error(field.errors) %}
...@@ -167,15 +185,15 @@ ...@@ -167,15 +185,15 @@
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% macro form_tag(form=None) %} {% macro form_tag(form=None, action=None) %}
<form action="" method="POST" class="admin-form form-horizontal" enctype="multipart/form-data"> <form action="{{ action or '' }}" method="POST" class="admin-form form-horizontal" enctype="multipart/form-data">
<fieldset> <fieldset>
{{ caller() }} {{ caller() }}
</fieldset> </fieldset>
</form> </form>
{% endmacro %} {% endmacro %}
{% macro render_form_buttons(cancel_url, extra=None) %} {% macro render_form_buttons(cancel_url, extra=None, is_modal=False) %}
<div class="control-group"> <div class="control-group">
<div class="controls"> <div class="controls">
<input type="submit" class="btn btn-primary btn-large" value="{{ _gettext('Save') }}" /> <input type="submit" class="btn btn-primary btn-large" value="{{ _gettext('Save') }}" />
...@@ -183,16 +201,16 @@ ...@@ -183,16 +201,16 @@
{{ extra }} {{ extra }}
{% endif %} {% endif %}
{% if cancel_url %} {% if cancel_url %}
<a href="{{ cancel_url }}" class="btn btn-large btn-danger">{{ _gettext('Cancel') }}</a> <a href="{{ cancel_url }}" class="btn btn-large btn-danger" {% if is_modal %}data-dismiss="modal"{% endif %}>{{ _gettext('Cancel') }}</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro render_form(form, cancel_url, extra=None, form_opts=None) -%} {% macro render_form(form, cancel_url, extra=None, form_opts=None, action=None, is_modal=False) -%}
{% call form_tag() %} {% call form_tag(action=action) %}
{{ render_form_fields(form, form_opts=form_opts) }} {{ render_form_fields(form, form_opts=form_opts) }}
{{ render_form_buttons(cancel_url, extra) }} {{ render_form_buttons(cancel_url, extra, is_modal) }}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
......
{% extends 'admin/master.html' %} {%- if not admin_view.edit_modal -%}
{% extends 'admin/master.html' %}
{%- endif -%}
{% import 'admin/lib.html' as lib with context %} {% import 'admin/lib.html' as lib with context %}
{% macro extra() %} {% macro extra() %}
...@@ -6,18 +8,43 @@ ...@@ -6,18 +8,43 @@
{% endmacro %} {% endmacro %}
{% block head %} {% block head %}
{%- if not admin_view.edit_modal -%}
{{ super() }} {{ super() }}
{{ lib.form_css() }} {{ lib.form_css() }}
{%- endif -%}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% call lib.form_tag(form) %} {%- if admin_view.edit_modal -%}
{{ lib.render_form_fields(form, form_opts=form_opts) }} {# remove save and continue button for modal (it won't function properly) #}
{{ lib.render_form_buttons(return_url, extra()) }} {{ lib.render_form(form, return_url, extra=None, form_opts=form_opts,
{% endcall %} action=url_for('.edit_view', id=request.args.get('id'), url=return_url),
is_modal=admin_view.edit_modal) }}
{%- else -%}
{{ lib.render_form(form, return_url, extra(), form_opts,
action=url_for('.edit_view', id=request.args.get('id'), url=return_url),
is_modal=admin_view.edit_modal) }}
{%- endif -%}
{% endblock %} {% endblock %}
{% block tail %} {% block tail %}
{{ super() }} {%- if admin_view.edit_modal -%}
{{ lib.form_js() }} <script>
{% endblock %} // fill the header of modal dynamically
$('.modal-header h3').html('{% block modal_header %}<h3>Edit Record #{{ request.args.get('id') }}</h3>{% endblock %}');
// fixes "remote modal shows same content every time"
$('.modal').on('hidden', function() {
$(this).removeData('modal');
});
$(function() {
// Apply flask-admin global styles after the modal is loaded
window.faForm.applyGlobalStyles(document);
});
</script>
{%- else -%}
{{ super() }}
{{ lib.form_js() }}
{%- endif -%}
{% endblock %}
\ No newline at end of file
...@@ -102,9 +102,13 @@ ...@@ -102,9 +102,13 @@
<td> <td>
{% block list_row_actions scoped %} {% block list_row_actions scoped %}
{%- if admin_view.can_edit -%} {%- if admin_view.can_edit -%}
<a class="icon" href="{{ get_url('.edit_view', id=get_pk_value(row), url=return_url) }}" title="{{ _gettext('Edit record') }}"> {%- if admin_view.edit_modal -%}
<i class="fa fa-pencil icon-pencil"></i> {{ lib.add_modal_button(url=get_url('.edit_view', id=get_pk_value(row), url=return_url), title=_gettext('Edit record'), content='<i class="fa fa-pencil icon-pencil"></i>') }}
</a> {% else %}
<a class="icon" href="{{ get_url('.edit_view', id=get_pk_value(row), url=return_url) }}" title="{{ _gettext('Edit record') }}">
<i class="fa fa-pencil icon-pencil"></i>
</a>
{%- endif -%}
{%- endif -%} {%- endif -%}
{%- if admin_view.can_delete -%} {%- if admin_view.can_delete -%}
<form class="icon" method="POST" action="{{ get_url('.delete_view') }}"> <form class="icon" method="POST" action="{{ get_url('.delete_view') }}">
...@@ -161,6 +165,10 @@ ...@@ -161,6 +165,10 @@
{% endblock %} {% endblock %}
{{ actionlib.form(actions, get_url('.action_view')) }} {{ actionlib.form(actions, get_url('.action_view')) }}
{%- if admin_view.edit_modal -%}
{{ lib.add_modal_window() }}
{%- endif -%}
{% endblock %} {% endblock %}
{% block tail %} {% block tail %}
......
...@@ -97,6 +97,23 @@ ...@@ -97,6 +97,23 @@
</ul> </ul>
{%- endmacro %} {%- endmacro %}
{# ---------------------- Modal Window ------------------- #}
{% macro add_modal_window(modal_window_id='fa_modal_window', modal_label_id='fa_modal_label') %}
<div class="modal fade" id="{{ modal_window_id }}" tabindex="-1" role="dialog" aria-labelledby="{{ modal_label_id }}">
<div class="modal-dialog" role="document">
{# bootstrap version > 3.1.0 required for this to work #}
<div class="modal-content">
</div>
</div>
</div>
{% endmacro %}
{% macro add_modal_button(url='', title='', content='', modal_window_id='fa_modal_window') %}
<a class="icon" data-target="#{{ modal_window_id }}" title="{{ title }}" href="{{ url }}" data-backdrop="false" data-toggle="modal">
{{ content|safe }}
</a>
{% endmacro %}
{# ---------------------- Forms -------------------------- #} {# ---------------------- Forms -------------------------- #}
{% macro render_field(form, field, kwargs={}, caller=None) %} {% macro render_field(form, field, kwargs={}, caller=None) %}
{% set direct_error = h.is_field_error(field.errors) %} {% set direct_error = h.is_field_error(field.errors) %}
...@@ -166,7 +183,7 @@ ...@@ -166,7 +183,7 @@
</form> </form>
{% endmacro %} {% endmacro %}
{% macro render_form_buttons(cancel_url, extra=None) %} {% macro render_form_buttons(cancel_url, extra=None, is_modal=False) %}
<hr> <hr>
<div class="form-group"> <div class="form-group">
<div class="col-md-offset-2 col-md-10 submit-row"> <div class="col-md-offset-2 col-md-10 submit-row">
...@@ -175,16 +192,16 @@ ...@@ -175,16 +192,16 @@
{{ extra }} {{ extra }}
{% endif %} {% endif %}
{% if cancel_url %} {% if cancel_url %}
<a href="{{ cancel_url }}" class="btn btn-danger" role="button">{{ _gettext('Cancel') }}</a> <a href="{{ cancel_url }}" class="btn btn-danger" role="button" {% if is_modal %}data-dismiss="modal"{% endif %}>{{ _gettext('Cancel') }}</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro render_form(form, cancel_url, extra=None, form_opts=None, action=None) -%} {% macro render_form(form, cancel_url, extra=None, form_opts=None, action=None, is_modal=False) -%}
{% call form_tag(action=action) %} {% call form_tag(action=action) %}
{{ render_form_fields(form, form_opts=form_opts) }} {{ render_form_fields(form, form_opts=form_opts) }}
{{ render_form_buttons(cancel_url, extra) }} {{ render_form_buttons(cancel_url, extra, is_modal) }}
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
......
{% extends 'admin/master.html' %} {%- if not admin_view.edit_modal -%}
{% extends 'admin/master.html' %}
{%- endif -%}
{% import 'admin/lib.html' as lib with context %} {% import 'admin/lib.html' as lib with context %}
{% macro extra() %} {% macro extra() %}
...@@ -6,18 +8,47 @@ ...@@ -6,18 +8,47 @@
{% endmacro %} {% endmacro %}
{% block head %} {% block head %}
{%- if not admin_view.edit_modal -%}
{{ super() }} {{ super() }}
{{ lib.form_css() }} {{ lib.form_css() }}
{%- endif -%}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% call lib.form_tag(form) %} {%- if admin_view.edit_modal -%}
{{ lib.render_form_fields(form, form_opts=form_opts) }} {# content added to modal-content #}
{{ lib.render_form_buttons(return_url, extra()) }} <div class="modal-header">
{% endcall %} <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
{% block modal_header %}<h3>Edit Record #{{ request.args.get('id') }}</h3>{% endblock %}
</div>
<div class="modal-body">
{# remove save and continue button for modal (it won't function properly) #}
{{ lib.render_form(form, return_url, extra=None, form_opts=form_opts,
action=url_for('.edit_view', id=request.args.get('id'), url=return_url),
is_modal=admin_view.edit_modal) }}
</div>
{%- else -%}
{{ lib.render_form(form, return_url, extra(), form_opts,
action=url_for('.edit_view', id=request.args.get('id'), url=return_url),
is_modal=admin_view.edit_modal) }}
{%- endif -%}
{% endblock %} {% endblock %}
{% block tail %} {% block tail %}
{{ super() }} {%- if admin_view.edit_modal -%}
{{ lib.form_js() }} <script>
// fixes "remote modal shows same content every time", avoiding the flicker
$('body').on('hidden.bs.modal', '.modal', function () {
$(this).removeData('bs.modal').find(".modal-content").empty();
});
$(function() {
// Apply flask-admin global styles after the modal is loaded
window.faForm.applyGlobalStyles(document);
});
</script>
{%- else -%}
{{ super() }}
{{ lib.form_js() }}
{%- endif -%}
{% endblock %} {% endblock %}
...@@ -102,9 +102,13 @@ ...@@ -102,9 +102,13 @@
<td> <td>
{% block list_row_actions scoped %} {% block list_row_actions scoped %}
{%- if admin_view.can_edit -%} {%- if admin_view.can_edit -%}
<a class="icon" href="{{ get_url('.edit_view', id=get_pk_value(row), url=return_url) }}" title="{{ _gettext('Edit record') }}"> {%- if admin_view.edit_modal -%}
<span class="fa fa-pencil glyphicon glyphicon-pencil"></span> {{ lib.add_modal_button(url=get_url('.edit_view', id=get_pk_value(row), url=return_url), title=_gettext('Edit record'), content='<span class="fa fa-pencil glyphicon glyphicon-pencil"></span>') }}
</a> {% else %}
<a class="icon" href="{{ get_url('.edit_view', id=get_pk_value(row), url=return_url) }}" title="{{ _gettext('Edit record') }}">
<span class="fa fa-pencil glyphicon glyphicon-pencil"></span>
</a>
{%- endif -%}
{%- endif -%} {%- endif -%}
{%- if admin_view.can_delete -%} {%- if admin_view.can_delete -%}
<form class="icon" method="POST" action="{{ get_url('.delete_view') }}"> <form class="icon" method="POST" action="{{ get_url('.delete_view') }}">
...@@ -160,6 +164,10 @@ ...@@ -160,6 +164,10 @@
{% endblock %} {% endblock %}
{{ actionlib.form(actions, get_url('.action_view')) }} {{ actionlib.form(actions, get_url('.action_view')) }}
{%- if admin_view.edit_modal -%}
{{ lib.add_modal_window() }}
{%- endif -%}
{% endblock %} {% endblock %}
{% block tail %} {% block tail %}
......
...@@ -452,6 +452,53 @@ def test_custom_form(): ...@@ -452,6 +452,53 @@ def test_custom_form():
ok_(not hasattr(view._create_form_class, 'col1')) ok_(not hasattr(view._create_form_class, 'col1'))
def test_modal_edit():
# bootstrap 2 - test edit_modal
app_bs2 = Flask(__name__)
admin_bs2 = Admin(app_bs2, template_mode="bootstrap2")
modal_view = MockModelView(Model, edit_modal=True, endpoint="modal_on")
no_modal_view = MockModelView(Model, edit_modal=False, endpoint="modal_off")
admin_bs2.add_view(modal_view)
admin_bs2.add_view(no_modal_view)
client_bs2 = app_bs2.test_client()
# bootstrap 2 - ensure modal window is added when edit_modal is enabled
rv = client_bs2.get('/admin/modal_on/')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('fa_modal_window' in data)
# bootstrap 2 - test modal disabled
rv = client_bs2.get('/admin/modal_off/')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('fa_modal_window' not in data)
# bootstrap 3
app_bs3 = Flask(__name__)
admin_bs3 = Admin(app_bs3, template_mode="bootstrap3")
admin_bs3.add_view(modal_view)
admin_bs3.add_view(no_modal_view)
client_bs3 = app_bs3.test_client()
# bootstrap 3 - ensure modal window is added when edit_modal is enabled
rv = client_bs3.get('/admin/modal_on/')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('fa_modal_window' in data)
# bootstrap 3 - test modal disabled
rv = client_bs3.get('/admin/modal_off/')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('fa_modal_window' not in data)
def check_class_name(): def check_class_name():
class DummyView(MockModelView): class DummyView(MockModelView):
pass pass
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment