Skip to content

Commit 8e75691

Browse files
authored
Revert "Reapply "Big form widgets refactor to allow user-defined widgets, add…" (#1005)
This reverts commit 1decd41.
1 parent 1decd41 commit 8e75691

File tree

3 files changed

+301
-669
lines changed

3 files changed

+301
-669
lines changed

docs/chapter-12.rst

Lines changed: 42 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ Create a new minimal app called ``form_basic`` :
204204
return dict(form=form, rows=rows)
205205
206206
207-
Note the import of validators at the top. This will be used later
207+
Note the import of two simple validators on top, in order to be used later
208208
with the ``requires`` parameter. We'll fully explain them
209209
on the :ref:`Form validation` paragraph.
210210

@@ -240,7 +240,7 @@ like to experiment, the database content can be fully seen and changed with the
240240
You can turn a create form into a CRUD update form by passing a record or a record id
241241
it second argument:
242242

243-
.. code:: python
243+
.. code:: html
244244

245245
# controllers definition
246246
@action("update_form/<thing_id:int>", method=["GET", "POST"])
@@ -300,64 +300,51 @@ Widgets
300300
Standard widgets
301301
~~~~~~~~~~~~~~~~
302302

303-
Py4web provides many widgets in the py4web.utility.form library. They are used by ``Form`` to generate
304-
the HTML of form fields. All widgets inherit from the ``Widget`` Abstract Base Class, and should be
305-
registered to the ``widgets`` registry object.
303+
Py4web provides many widgets in the py4web.utility.form library. They are simple plugins
304+
that easily allow you to specify the type of the input elements in a form, along with
305+
some of their properties.
306+
307+
Here is the full list:
306308

307-
Here is the full list of the pydal types and their widgets:
309+
- CheckboxWidget
310+
- DateTimeWidget
311+
- FileUploadWidget
312+
- ListWidget
313+
- PasswordWidget
314+
- RadioWidget
315+
- SelectWidget
316+
- TextareaWidget
308317

309-
- ``string``: TextInputWidget
310-
- ``date``: DateInputWidget
311-
- ``time``: TimeInputWidget
312-
- ``integer``: IntegerInputWidget
313-
- ``numeric``: FloatInputWidget
314-
- ``datetime``: DateTimeWidget
315-
- ``text``: TextareaWidget
316-
- ``json``: JsonWidget
317-
- ``boolean``: CheckboxWidget
318-
- ``list``:: ListWidget
319-
- ``password``: PasswordWidget
320-
- ``select``: SelectWidget
321-
- ``radio``: RadioWidget
322-
- ``upload``: FileUploadWidget
323-
- ``blob``: BlobWidget - no-op widget, can be overwritten but does nothing by default
324318

319+
This is an improved 'Basic Form Example' with a radio button widget:
325320

326-
By default Widgets are chosen based on DAL Field type. You can also use choose widgets for individual fields,
327-
like in this improved 'Basic Form Example' with a radio button widget:
328321

329322
.. code:: python
330323
331324
# in controllers.py
332325
from py4web import action, redirect, URL, Field
333326
from py4web.utils.form import Form, FormStyleDefault, RadioWidget
327+
from pydal.validators import *
334328
from .common import db
335329
336330
# controllers definition
337331
@action("create_form", method=["GET", "POST"])
338332
@action.uses("form_widgets.html", db)
339333
def create_form():
340-
MyStyle = FormStyleDefault.clone()
341-
MyStyle.widgets['color'] = RadioWidget
342-
form = Form(db.thing, formstyle=MyStyle)
334+
FormStyleDefault.widgets['color']=RadioWidget()
335+
form = Form(db.thing, formstyle=FormStyleDefault)
343336
rows = db(db.thing).select()
344337
return dict(form=form, rows=rows)
345338
346-
.. note::
347-
The way Widgets work was changed in a recent update. For backwards compatibility, you can still pass a
348-
instance of a older style implicit widget, but for built-in widgets and Widget subclasses,
349-
you need to pass pass the Widget class without instantiating it. ``RadioWidget`` instead of ``RadioWidget()``.
350-
351339
Notice the differences from the 'Basic Form example' we've seen at the
352340
beginning of the chapter:
353341

354342
- you need to import the widget from the py4web.utils.form library
355-
- before the form definition, you set the widgets dictionary entry
356-
corresponding to your field name to the desired Widget
343+
- before the form definition, you define the ``color`` field form style with the line:
357344

358345
.. code:: python
359-
MyStyle = FormStyleDefault.clone()
360-
MyStyle.widgets['color'] = RadioWidget
346+
347+
FormStyleDefault.widgets['color']=RadioWidget()
361348
362349
The result is the same as before, but now we have a radio button widget instead of the
363350
dropdown menu!
@@ -372,118 +359,51 @@ Using widgets in forms is quite easy, and they'll let you have more control on i
372359
Custom widgets
373360
~~~~~~~~~~~~~~
374361

375-
You can also customize the widgets properties by implementing custom widgets.
376-
377-
There are broadly 2 options to make ``Form`` use custom widgets:
378-
379-
- per-Field widgets, as shown above. Gives you more control, but has to be set for each Field/column individually.
380-
- Registered widgets with a matching method. Allows global matching on any characteristic of a Field.
381-
382-
When creating a custom widget, be aware of the methods you can and should overwrite:
383-
384-
- ``make_editable`` is for normal form inputs, this should be an input the user can change
385-
- ``make_readonly`` is for readonly displays of this field, for example when ``field.writable = False``
386-
- ``make`` gets the value and calls the 2 above. Generally, you should prefer overwriting the 2 above
387-
- ``form_html`` calls ``make`` and generates the final HTML to be inserted into the form. It handles the HTML
388-
surrounding the bare form inputs, labels, field comment display, etc.
389-
390-
391-
Custom per-Field Widget
392-
"""""""""""""""""""""""
362+
You can also customize the widgets properties by cloning and modifying and existing style.
363+
Let's have a quick look, improving again our Superhero example:
393364

394365
.. code:: python
395366
396367
# in controllers.py
397368
from py4web import action, redirect, URL, Field
398-
from py4web.utils.form import Form, FormStyleDefault, Widget, RadioWidget, to_id
369+
from py4web.utils.form import Form, FormStyleDefault, RadioWidget
370+
from pydal.validators import *
399371
from .common import db
400372
401373
# custom widget class definition
402-
class MyCustomWidget(Widget):
403-
def make_editable(self, value):
404-
return INPUT(
374+
class MyCustomWidget:
375+
def make(self, field, value, error, title, placeholder, readonly=False):
376+
tablename = field._table if "_table" in dir(field) else "no_table"
377+
control = INPUT(
405378
_type="text",
406-
_id=to_id(self.field),
407-
_name=self.field.name,
379+
_id="%s_%s" % (tablename, field.name),
380+
_name=field.name,
408381
_value=value,
409382
_class="input",
410-
_placeholder=self.placeholder,
411-
_title=self.title,
383+
_placeholder=placeholder if placeholder and placeholder != "" else "..",
384+
_title=title,
412385
_style="font-size: x-large;color: red; background-color: black;",
413386
)
414-
415-
# optionally overwrite the default readonly style
416-
# def make_readonly(self, value):
417-
# return DIV(str(value))
418-
387+
return control
388+
419389
# controllers definition
420390
@action("create_form", method=["GET", "POST"])
421391
@action.uses("form_custom_widgets.html", db)
422392
def create_form():
423393
MyStyle = FormStyleDefault.clone()
424-
425-
MyStyle.widgets['name'] = MyCustomWidget
426-
MyStyle.widgets['color'] = RadioWidget
394+
MyStyle.classes = FormStyleDefault.classes
395+
MyStyle.widgets['name']=MyCustomWidget()
396+
MyStyle.widgets['color']=RadioWidget()
427397
428398
form = Form(db.thing, deletable=False, formstyle=MyStyle)
429399
rows = db(db.thing).select()
430400
return dict(form=form, rows=rows)
431401
432402
433403
The result is similar to the previous ones, but now we have a custom input field,
434-
with foreground color red and background color black.
435-
436-
Registered Widget
437-
"""""""""""""""""
438-
A registered Widget is globally registered to the widget registry at ``py4web.utils.form.widgets``.
439-
This is how default widgets work, and allows you to overwrite default widgets or defines custom ones
440-
which apply to any matching field automatically.
441-
442-
To do this, a ``matches`` classmethod is used, which is checked when generating a form to determine
443-
the correct widget for a Field.
444-
445-
The most basic version just checks against the field type.
446-
447-
Note that matching occurs in reversed order of registration, which means Widgets defined (and imported)
448-
later will get checked first. This is what allows you to overwrite default fields, as those are
449-
always defined first.
450-
451-
In this example we will style all "string" fields which start with "n".
452-
We'll also inherit from the default TextInputWidget and only change its style and ``matches``.
453-
454-
.. code:: python
455-
456-
# in controllers.py
457-
from py4web import action, redirect, URL, Field
458-
from py4web.utils.form import Form, FormStyleDefault, TextInputWidget, widgets
459-
from .common import db
460-
461-
# custom widget class definition
462-
@widgets.register_widget
463-
class MyCustomWidget(TextInputWidget):
464-
465-
@classmethod
466-
def matches(cls, field: Field) -> bool:
467-
return str(field.type) == "string" and field.name.startswith("n")
468-
469-
# since we don't need access to the value or structure
470-
# we can style the element whether its readonly or not
471-
def make(self, readonly: bool = False):
472-
elem = super().make(readonly)
473-
elem._style = "font-size: x-large; color: red; background-color: black;"
474-
return elem
475-
476-
477-
# the controller doesn't need to do anything special
478-
# since the Widget is registered
479-
@action("create_form", method=["GET", "POST"])
480-
@action.uses("form_custom_widgets.html", db)
481-
def create_form():
482-
form = Form(db.thing, deletable=False)
483-
rows = db(db.thing).select()
484-
return dict(form=form, rows=rows)
485-
404+
with foreground color red and background color black,
486405

406+
Even the radio button widget has changed, from red to blue.
487407

488408
Advanced form design
489409
--------------------
@@ -493,19 +413,14 @@ Form structure manipulation
493413

494414
In py4web a form is rendered by YATL helpers. This means the tree structure of a form
495415
can be manipulated before the form is serialized in HTML.
496-
Here is an example of how to manipulate the generated HTML structure:
416+
Here is an example of how to manipulate the generate HTML structure:
497417

498418
.. code:: python
499419
500420
db.define_table('paint', Field('color'))
501421
form = Form(db.paint)
502422
form.structure.find('[name=color]')[0]['_class'] = 'my-class'
503423
504-
.. note::
505-
506-
For demonstration purposes. For changes like this, you should consider
507-
adjusting the FormStyle or using a custom Widget instead.
508-
509424
Notice that a form does not make an HTML tree until form structure is accessed. Once accessed you can use ``.find(...)``
510425
to find matching elements. The argument of ``find`` is a string following the filter syntax of jQuery. In the above case
511426
there is a single match ``[0]`` and we modify the ``_class`` attribute of that element. Attribute names of HTML elements

0 commit comments

Comments
 (0)