formrenderers.py 11.1 KB
Newer Older
1
# copyright 2013-2022 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
Nicola Spanti's avatar
Nicola Spanti committed
2
# contact https://www.logilab.fr/ -- mailto:contact@logilab.fr
Sylvain Thénault's avatar
Sylvain Thénault committed
3
4
5
6
7
8
9
10
11
12
13
14
#
# This program is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
Nicola Spanti's avatar
Nicola Spanti committed
15
# with this program. If not, see <https://www.gnu.org/licenses/>.
Sylvain Thénault's avatar
Sylvain Thénault committed
16
"""bootstrap implementation of formrenderers"""
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

__docformat__ = "restructuredtext en"

from warnings import warn

from logilab.common.decorators import monkeypatch

from cubicweb import tags
from cubicweb.utils import support_args
from cubicweb.web.views import formrenderers


@monkeypatch(formrenderers.EntityCompositeFormRenderer)
def render_fields(self, w, form, values):
    if form.parent_form is None:
        # We should probably take those CSS classes to uiprops.py
        w(u'<table class="table table-striped table-bordered table-condensed">')
        # get fields from the first subform with something to display (we
        # may have subforms with nothing editable that will simply be
        # skipped later)
        for subform in form.forms:
            subfields = [field for field in subform.fields
                         if field.is_visible()]
            if subfields:
                break
        if subfields:
            # main form, display table headers HTML5
            w(u'<thead>')
            w(u'<tr>')
            w(u'<th>%s</th>' %
              tags.input(type='checkbox',
                         title=self._cw._('toggle check boxes'),
                         onclick="setCheckboxesState('eid', null, this.checked)"))
            for field in subfields:
                w(u'<th>%s</th>' % formrenderers.field_label(form, field))
            w(u'</tr>')
            w(u'</thead>')
    super(formrenderers.EntityCompositeFormRenderer, self).render_fields(w, form, values)
    if form.parent_form is None:
        w(u'</table>')
        if self._main_display_fields:
            super(formrenderers.EntityCompositeFormRenderer, self)._render_fields(
                self._main_display_fields, w, form)


62
formrenderers.FormRenderer.button_bar_class = u'form-group'
63

Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
64

65
66
67
68
69
class ModalFormRenderer(formrenderers.FormRenderer):
    __regid__ = 'modal-form-renderer'
    button_bar_class = 'modal-footer'

    def open_form(self, form, values, **attrs):
70
        showmessage = values.get('showmessage')
71
72
        showonload = values.get('showonload', False)
        if showonload:
73
74
75
            # show the `modal` dialog : initialized in
            # basecomponents.CookieLoginComponent by showonload=True
            self._cw.add_onload("$('#%s').modal('show')" % values['modal_id'])
76
77
78
79
80
81
            data_backdrop, data_keyboard = u'static', u'false'
        else:
            data_backdrop, data_keyboard = u'true', u'true'

        html = [u'<div class="%(class)s" id="%(id)s" tabindex="-1" role="dialog" '
                u'data-backdrop="%(backdrop)s" data-keyboard="%(keyboard)s">\n'
82
                u'<div class="modal-dialog">'
Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
83
                u'<div class="modal-content">' % {
84
                    'id': values['modal_id'],
85
86
                    'class': u'modal fade in' if showmessage else u'modal',
                    'backdrop': data_backdrop, 'keyboard': data_keyboard}]
87
        html.append(super(ModalFormRenderer, self).open_form(form, values, **attrs))
88
89
90
        html.append(u'<div class="modal-header">')
        if not showonload:
            # add cancel button when modal triggered by CookieLoginComponent
Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
91
92
93
94
            html.append(u'<button type="button" class="close" data-dismiss="modal" '
                        'aria-hidden="true">&#215;</button>')
        html.append(
            u'<div class="modal-title">%s</div>\n</div>\n' % values.get('title', u''))
95
96
        return u'\n'.join(html)

97
    def render_content(self, w, form, values):
98
        self._cw.add_onload("$('.close').click(function () {$(this).parent().removeClass('in');});")
99
        w(u'<div class="modal-body">')
100
101
102
103
104
        if values.get('showmessage'):
            message = self._cw.message
            if message:
                w(u'<div class="alert alert-danger in">%s'
                  u'<button class="close" data-dismiss="alert">x</button></div>' % message)
105
106
107
108
109
110
111
112
113
114
        if self.display_progress_div:
            w(u'<div id="progress">%s</div>' % self._cw._('validating...'))
        w(u'\n<fieldset>\n')
        self.render_fields(w, form, values)
        w(u'\n</fieldset>\n')
        w(u'</div>')
        self.render_buttons(w, form)

    def close_form(self, form, values):
        html = [super(ModalFormRenderer, self).close_form(form, values)]
Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
115
        html.append('</div></div></div>')  # close modal-content, modal-dialog, ...
116
        return u'\n'.join(html)
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132


@monkeypatch(formrenderers.FormRenderer)
def render_help(self, form, field):
    """display help in the form
    """
    help = []
    descr = field.help
    if callable(descr):
        if support_args(descr, 'form', 'field'):
            descr = descr(form, field)
        else:
            warn("[3.10] field's help callback must now take form "
                 "and field as argument (%s)" % field, DeprecationWarning)
            descr = descr(form)
    if descr:
133
        help.append('<p class="help-block">%s</p>' % self._cw._(descr))
134
135
    example = field.example_format(self._cw)
    if example:
136
        help.append('<p class="form-control-static">(%s: %s)</p>'
137
138
139
                    % (self._cw._('sample format'), example))
    return u'&#160;'.join(help)

Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
140

141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
@monkeypatch(formrenderers.FormRenderer)
def error_message(self, form):
    """return formatted error message

    This method should be called once inlined field errors has been consumed
    """
    req = self._cw
    errex = form.form_valerror
    # get extra errors
    if errex is not None:
        errormsg = req._('please correct the following errors:')
        errors = form.remaining_errors()
        if errors:
            if len(errors) > 1:
                templstr = u'<li>%s</li>'
            else:
                templstr = u'&#160;%s'
            for field, err in errors:
                if field is None:
                    errormsg += templstr % err
                else:
                    errormsg += templstr % '%s: %s' % (req._(field), err)
            if len(errors) > 1:
                errormsg = '<ul>%s</ul>' % errormsg
165
        return u'<div class="alert alert-danger">%s</div>' % errormsg
166
167
168
169
170
171
172
173
174
175
176
177
178
    return u''


@monkeypatch(formrenderers.FormRenderer)
def _render_fields(self, fields, w, form):
    """render form fields
    """
    byfieldset = {}
    for field in fields:
        byfieldset.setdefault(field.fieldset, []).append(field)
    if form.fieldsets_in_order:
        fieldsets = form.fieldsets_in_order
    else:
179
180
        fieldsets = byfieldset
    for fieldset in list(fieldsets):
181
182
183
184
185
        try:
            fields = byfieldset.pop(fieldset)
        except KeyError:
            self.warning('no such fieldset: %s (%s)', fieldset, form)
            continue
186
        w(u'<fieldset>\n')
187
        if fieldset:
188
            w(u'<legend>%s</legend>' % self._cw.__(fieldset))
189
190
191
        for field in fields:
            error = form.field_error(field)
            control = not hasattr(field, 'control_field') or field.control_field
Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
192
193
            w(u'<div id="%s-%s_row" class="form-group %s">' % (
                field.name, field.role, 'error' if error else ''))
194
195
            if self.display_label and field.label is not None:
                w(u'%s' % self.render_label(form, field))
196
197
198
199
200
201
202
203
204
            # Use full width for inlined forms, 9/12 for normal fields
            base_css = 'col-md-12' if hasattr(field, 'view') else 'col-md-9'
            if not control:
                # katia : 'control' class no longer exists in bootstrap 3.0.0
                # but is used here because of
                # 'form-horizontal' bootstrap 2.0.4 css
                # backport
                base_css += ' nomargin'
            w(u'<div class="%s">' % base_css)
205
206
207
208
209
210
211
212
213
214
215
            w(field.render(form, self))
            if error:
                self.render_error(w, error)
            if self.display_help:
                w(self.render_help(form, field))
            w(u'</div>')
            w(u'</div>')
        w(u'</fieldset>')
    if byfieldset:
        self.warning('unused fieldsets: %s', ', '.join(byfieldset))

Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
216

217
218
219
220
221
222
@monkeypatch(formrenderers.FormRenderer)
def render_label(self, form, field):
    if field.label is None:
        return u''
    label = formrenderers.field_label(form, field)
    attrs = {'for': field.dom_id(form)}
223
    attrs['class'] = 'col-md-3 control-label'
224
225
226
227
    if field.required:
        attrs['class'] += ' required'
    return tags.label(label, **attrs)

228
229
230
231
232
233
234

@monkeypatch(formrenderers.FormRenderer)
def render_buttons(self, w, form):
    """render form's buttons
    """
    if not form.form_buttons:
        return
235
    w(u'<div class="%s">' % self.button_bar_class)
236
    for button in form.form_buttons:
237
238
        w(button.render(form))
    w(u'</div>')
239
240
241
242


@monkeypatch(formrenderers.EntityFormRenderer)
def open_form(self, form, values):
243
    attrs_fs_label = u''
244
    if self.main_form_title:
245
246
247
        attrs_fs_label = u'<h3>%s</h3>' % self._cw._(self.main_form_title)
    open_form = attrs_fs_label + super(formrenderers.EntityFormRenderer,
                                       self).open_form(form, values)
248
249
250
251
252
    return open_form


@monkeypatch(formrenderers.EntityFormRenderer)
def close_form(self, form, values):
253
254
    # needed to remove the '</div>' from the original method
    return super(formrenderers.EntityFormRenderer, self).close_form(form, values)
255
256


257
@monkeypatch(formrenderers.EntityFormRenderer)  # noqa: F811
258
def render_buttons(self, w, form):
259
260
261
262
    # needed to use our own monkeypatched FormRenderer.render_buttons() which
    # doesn't need to add a special case when there are 3 buttons to render
    super(formrenderers.EntityFormRenderer, self).render_buttons(w, form)

263

264
@monkeypatch(formrenderers.EntityInlinedFormRenderer)  # noqa: F811
265
266
267
268
269
270
def open_form(self, w, form, values):
    try:
        w(u'<div id="div-%(divid)s" onclick="%(divonclick)s">' % values)
    except KeyError:
        w(u'<div id="div-%(divid)s">' % values)
    else:
271
272
        w(u'<div id="notice-%s" class="notice text-info">'
          u'<span class="glyphicon glyphicon-info-sign"></span> %s</div>' % (
Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
273
              values['divid'], self._cw._('click on the box to cancel the deletion')))
274
275
    w(u'<div class="iformBody">')

Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
276

277
@monkeypatch(formrenderers.EntityInlinedFormRenderer)  # noqa: F811
278
279
280
def close_form(self, w, form, values):
    w(u'</div></div>')

Sylvain Thénault's avatar
pep8    
Sylvain Thénault committed
281

282
@monkeypatch(formrenderers.EntityInlinedFormRenderer)  # noqa: F811
283
284
285
286
287
288
289
def render_fields(self, w, form, values):
    w(u'<fieldset id="fs-%(divid)s">' % values)
    fields = self._render_hidden_fields(w, form)
    w(u'</fieldset>')
    if fields:
        self._render_fields(fields, w, form)
    self.render_child_forms(w, form, values)