@@ -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
208208with the ``requires `` parameter. We'll fully explain them
209209on the :ref: `Form validation ` paragraph.
210210
@@ -240,7 +240,7 @@ like to experiment, the database content can be fully seen and changed with the
240240You can turn a create form into a CRUD update form by passing a record or a record id
241241it 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
300300Standard 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
352340beginning 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
363350dropdown menu!
@@ -372,118 +359,51 @@ Using widgets in forms is quite easy, and they'll let you have more control on i
372359Custom 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
488408Advanced form design
489409--------------------
@@ -493,19 +413,14 @@ Form structure manipulation
493413
494414In py4web a form is rendered by YATL helpers. This means the tree structure of a form
495415can 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(...) ``
510425to find matching elements. The argument of ``find `` is a string following the filter syntax of jQuery. In the above case
511426there is a single match ``[0] `` and we modify the ``_class `` attribute of that element. Attribute names of HTML elements
0 commit comments