• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / z3c.form / 16248976312

02 Jul 2025 05:53AM UTC coverage: 95.275%. Remained the same
16248976312

push

github

icemac
Back to development: 6.1

717 of 800 branches covered (89.63%)

Branch coverage included in aggregate %.

3699 of 3835 relevant lines covered (96.45%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.11
/src/z3c/form/browser/widget.py
1
##############################################################################
2
#
3
# Copyright (c) 2007 Zope Foundation and Contributors.
4
# All Rights Reserved.
5
#
6
# This software is subject to the provisions of the Zope Public License,
7
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE.
12
#
13
##############################################################################
14
"""Widget Framework Implementation."""
15
__docformat__ = "reStructuredText"
1✔
16
import zope.interface
1✔
17
from zope.schema.fieldproperty import FieldProperty
1✔
18

19
from z3c.form.browser import interfaces
1✔
20
from z3c.form.interfaces import INPUT_MODE
1✔
21
from z3c.form.interfaces import IFieldWidget
1✔
22

23

24
class WidgetLayoutSupport:
1✔
25
    """Widget layout support"""
26

27
    def wrapCSSClass(self, klass, pattern='%(class)s'):
1✔
28
        """Return a list of css class names wrapped with given pattern"""
29
        if klass is not None and pattern is not None:
1!
30
            return [pattern % {'class': k} for k in klass.split()]
1✔
31
        else:
32
            return []
×
33

34
    def getCSSClass(self, klass=None, error=None, required=None,
1✔
35
                    classPattern='%(class)s', errorPattern='%(class)s-error',
36
                    requiredPattern='%(class)s-required'):
37
        """Setup given css class (klass) with error and required postfix
38

39
        If no klass name is given the widget.wrapper class name/names get used.
40
        It is also possible if more then one (empty space separated) names
41
        are given as klass argument.
42

43
        This method can get used from your form or widget template or widget
44
        layout template without to re-implement the widget itself just because
45
        you a different CSS class concept.
46

47
        The following sample:
48

49
        <div tal:attributes="class python:widget.getCSSClass('foo bar')">
50
          label widget and error
51
        </div>
52

53
        will render a div tag if the widget field defines required=True:
54

55
        <div class="foo-error bar-error foo-required bar-required foo bar">
56
          label widget and error
57
        </div>
58

59
        And the following sample:
60

61
        <div tal:attributes="class python:widget.getCSSClass('row')">
62
          label widget and error
63
        </div>
64

65
        will render a div tag if the widget field defines required=True
66
        and an error occurs:
67

68
        <div class="row-error row-required row">
69
          label widget and error
70
        </div>
71

72
        Note; you need to define a globale widget property if you use
73
        python:widget (in your form template). And you need to use the
74
        view scope in your widget or layout templates.
75

76
        Note, you can set the pattern to None for skip error or required
77
        rendering. Or you can use a pattern like 'error' or 'required' if
78
        you like to skip postfixing your default css klass name for error or
79
        required rendering.
80

81
        """
82
        classes = []
1✔
83
        # setup class names
84
        if klass is not None:
1!
85
            kls = klass
1✔
86
        else:
87
            kls = self.css
×
88

89
        # setup error class names
90
        if error is not None:
1!
91
            error = error
×
92
        else:
93
            error = kls
1✔
94

95
        # setup required class names
96
        if required is not None:
1!
97
            required = required
×
98
        else:
99
            required = kls
1✔
100

101
        # append error class names
102
        if self.error is not None:
1✔
103
            classes += self.wrapCSSClass(error, errorPattern)
1✔
104
        # append required class names
105
        if self.required:
1✔
106
            classes += self.wrapCSSClass(required, requiredPattern)
1✔
107
        # append given class names
108
        classes += self.wrapCSSClass(kls, classPattern)
1✔
109
        # remove duplicated class names but keep order
110
        unique = []
1✔
111
        [unique.append(kls) for kls in classes if kls not in unique]
1✔
112
        return ' '.join(unique)
1✔
113

114

115
@zope.interface.implementer(interfaces.IHTMLFormElement)
1✔
116
class HTMLFormElement(WidgetLayoutSupport):
1✔
117

118
    id = FieldProperty(interfaces.IHTMLFormElement['id'])
1✔
119
    klass = FieldProperty(interfaces.IHTMLFormElement['klass'])
1✔
120
    style = FieldProperty(interfaces.IHTMLFormElement['style'])
1✔
121
    title = FieldProperty(interfaces.IHTMLFormElement['title'])
1✔
122

123
    lang = FieldProperty(interfaces.IHTMLFormElement['lang'])
1✔
124

125
    onclick = FieldProperty(interfaces.IHTMLFormElement['onclick'])
1✔
126
    ondblclick = FieldProperty(interfaces.IHTMLFormElement['ondblclick'])
1✔
127
    onmousedown = FieldProperty(interfaces.IHTMLFormElement['onmousedown'])
1✔
128
    onmouseup = FieldProperty(interfaces.IHTMLFormElement['onmouseup'])
1✔
129
    onmouseover = FieldProperty(interfaces.IHTMLFormElement['onmouseover'])
1✔
130
    onmousemove = FieldProperty(interfaces.IHTMLFormElement['onmousemove'])
1✔
131
    onmouseout = FieldProperty(interfaces.IHTMLFormElement['onmouseout'])
1✔
132
    onkeypress = FieldProperty(interfaces.IHTMLFormElement['onkeypress'])
1✔
133
    onkeydown = FieldProperty(interfaces.IHTMLFormElement['onkeydown'])
1✔
134
    onkeyup = FieldProperty(interfaces.IHTMLFormElement['onkeyup'])
1✔
135

136
    disabled = FieldProperty(interfaces.IHTMLFormElement['disabled'])
1✔
137
    tabindex = FieldProperty(interfaces.IHTMLFormElement['tabindex'])
1✔
138
    onfocus = FieldProperty(interfaces.IHTMLFormElement['onfocus'])
1✔
139
    onblur = FieldProperty(interfaces.IHTMLFormElement['onblur'])
1✔
140
    onchange = FieldProperty(interfaces.IHTMLFormElement['onchange'])
1✔
141

142
    # layout support
143
    css = FieldProperty(interfaces.IHTMLFormElement['css'])
1✔
144

145
    def addClass(self, klass: str):
1✔
146
        """Add a class to the HTML element.
147

148
        See interfaces.IHTMLFormElement.
149
        """
150
        if not self.klass:
1✔
151
            self.klass = str(klass)
1✔
152
        else:
153
            # make sure items are not repeated
154
            parts = self.klass.split() + klass.split()
1✔
155
            # Remove duplicates and keep order.
156
            parts = list(dict.fromkeys(parts))
1✔
157
            self.klass = " ".join(parts)
1✔
158

159
    def update(self):
1✔
160
        """See z3c.form.interfaces.IWidget"""
161
        super().update()
1✔
162
        if self.mode == INPUT_MODE and self.required:
1✔
163
            self.addClass('required')
1✔
164

165
    @property
1✔
166
    def _html_attributes(self) -> list:
1✔
167
        """Return a list of HTML attributes managed by this class."""
168
        # This is basically a list of all the FieldProperty names except for
169
        # the `css` property, which is not an HTML attribute.
170
        return [
1✔
171
            "id",
172
            "klass",  # will be changed to `class`
173
            "style",
174
            "title",
175
            "lang",
176
            "onclick",
177
            "ondblclick",
178
            "onmousedown",
179
            "onmouseup",
180
            "onmouseover",
181
            "onmousemove",
182
            "onmouseout",
183
            "onkeypress",
184
            "onkeydown",
185
            "onkeyup",
186
            "disabled",
187
            "tabindex",
188
            "onfocus",
189
            "onblur",
190
            "onchange",
191
        ]
192

193
    _attributes = None
1✔
194

195
    @property
1✔
196
    def attributes(self) -> dict:
1✔
197
        # If `attributes` were explicitly set, return them.
198
        if isinstance(self._attributes, dict):
1✔
199
            return self._attributes
1✔
200

201
        # Otherwise return the default set of non-empty HTML attributes.
202
        attributes_items = [
1✔
203
            ("class" if attr == "klass" else attr, getattr(self, attr, None))
204
            for attr in self._html_attributes
205
        ]
206
        self._attributes = {key: val for key, val in attributes_items if val}
1✔
207
        return self._attributes
1✔
208

209
    @attributes.setter
1✔
210
    def attributes(self, value: dict):
1✔
211
        # Store the explicitly set attributes.
212
        self._attributes = value
1✔
213

214

215
@zope.interface.implementer(interfaces.IHTMLInputWidget)
1✔
216
class HTMLInputWidget(HTMLFormElement):
1✔
217

218
    readonly = FieldProperty(interfaces.IHTMLInputWidget['readonly'])
1✔
219
    alt = FieldProperty(interfaces.IHTMLInputWidget['alt'])
1✔
220
    accesskey = FieldProperty(interfaces.IHTMLInputWidget['accesskey'])
1✔
221
    onselect = FieldProperty(interfaces.IHTMLInputWidget['onselect'])
1✔
222

223
    @property
1✔
224
    def _html_attributes(self) -> list:
1✔
225
        attributes = super()._html_attributes
×
226
        attributes.extend([
×
227
            "readonly",
228
            "alt",
229
            "accesskey",
230
            "onselect",
231
        ])
232
        return attributes
×
233

234

235
@zope.interface.implementer(interfaces.IHTMLTextInputWidget)
1✔
236
class HTMLTextInputWidget(HTMLInputWidget):
1✔
237

238
    size = FieldProperty(interfaces.IHTMLTextInputWidget['size'])
1✔
239
    maxlength = FieldProperty(interfaces.IHTMLTextInputWidget['maxlength'])
1✔
240
    placeholder = FieldProperty(interfaces.IHTMLTextInputWidget['placeholder'])
1✔
241
    autocapitalize = FieldProperty(
1✔
242
        interfaces.IHTMLTextInputWidget['autocapitalize'])
243

244
    @property
1✔
245
    def _html_attributes(self) -> list:
1✔
246
        attributes = super()._html_attributes
×
247
        attributes.extend([
×
248
            "size",
249
            "maxlength",
250
            "placeholder",
251
            "autocapitalize",
252
        ])
253
        return attributes
×
254

255

256
@zope.interface.implementer(interfaces.IHTMLTextAreaWidget)
1✔
257
class HTMLTextAreaWidget(HTMLFormElement):
1✔
258

259
    rows = FieldProperty(interfaces.IHTMLTextAreaWidget['rows'])
1✔
260
    cols = FieldProperty(interfaces.IHTMLTextAreaWidget['cols'])
1✔
261
    readonly = FieldProperty(interfaces.IHTMLTextAreaWidget['readonly'])
1✔
262
    accesskey = FieldProperty(interfaces.IHTMLTextAreaWidget['accesskey'])
1✔
263
    onselect = FieldProperty(interfaces.IHTMLTextAreaWidget['onselect'])
1✔
264

265
    @property
1✔
266
    def _html_attributes(self) -> list:
1✔
267
        attributes = super()._html_attributes
×
268
        attributes.extend([
×
269
            "rows",
270
            "cols",
271
            "readonly",
272
            "accesskey",
273
            "onselect",
274
        ])
275
        return attributes
×
276

277

278
@zope.interface.implementer(interfaces.IHTMLSelectWidget)
1✔
279
class HTMLSelectWidget(HTMLFormElement):
1✔
280

281
    multiple = FieldProperty(interfaces.IHTMLSelectWidget['multiple'])
1✔
282
    size = FieldProperty(interfaces.IHTMLSelectWidget['size'])
1✔
283

284
    @property
1✔
285
    def _html_attributes(self) -> list:
1✔
286
        attributes = super()._html_attributes
×
287
        attributes.extend([
×
288
            "multiple",
289
            "size",
290
        ])
291
        return attributes
×
292

293

294
def addFieldClass(widget):
1✔
295
    """Add a class to the widget that is based on the field type name.
296

297
    If the widget does not have field, then nothing is done.
298
    """
299
    if IFieldWidget.providedBy(widget):
1✔
300
        klass = str(widget.field.__class__.__name__.lower() + '-field')
1✔
301
        widget.addClass(klass)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc