Custom forms

If a form is created with SQLFORM, SQLFORM.factory or CRUD, there are multiple ways it can be embedded in a view allowing multiple degrees of customization. Consider for example the following model:

  1. db.define_table('image',
  2. Field('name', requires=IS_NOT_EMPTY()),
  3. Field('source', 'upload'))

and upload action

  1. def upload_image():
  2. return dict(form=SQLFORM(db.image).process())

The simplest way to embed the form in the view for upload_image is

  1. {{=form}}

This results in a standard table layout. If you wish to use a different layout, you can break the form into components

  1. {{=form.custom.begin}}
  2. Name: <div>{{=form.custom.widget.name}}</div>
  3. File: <div>{{=form.custom.widget.source}}</div>
  4. {{=form.custom.submit}}
  5. {{=form.custom.end}}

where form.custom.widget[fieldname] gets serialized into the proper widget for the field. If the form is submitted and it contains errors, they are appended below the widgets, as usual.

The above sample form is show in the image below.

image

A similar result could have been obtained without using a custom form:

  1. SQLFORM(..., formstyle='table2cols')

or in case of CRUD forms with the following parameter:

  1. crud.settings.formstyle='table2cols'

See description for formstyle argument of SQLFORM for other possible values.

If you do not wish to use the widgets serialized by web2py, you can replace them with HTML. There are some variables that will be useful for this:

  • form.custom.label[fieldname] contains the label for the field.
  • form.custom.comment[fieldname] contains the comment for the field.
  • form.custom.dspval[fieldname] form-type and field-type dependent display representation of the field.
  • form.custom.inpval[fieldname] form-type and field-type dependent values to be used in field code.

If your form has deletable=True you should also insert

  1. {{=form.custom.delete}}

to display the delete checkbox.

It is important to follow the conventions described below.

CSS conventions

Tags in forms generated by SQLFORM, SQLFORM.factory and CRUD follow a strict CSS naming convention that can be used to further customize the forms.

Given a table “mytable”, and a field “myfield” of type “string”, it is rendered by default by a

  1. SQLFORM.widgets.string.widget

that looks like this:

  1. <input type="text" name="myfield" id="mytable_myfield"
  2. class="string" />

Notice that:

  • the class of the INPUT tag is the same as the type of the field. This is very important for the jQuery code in “web2py_ajax.html” to work. It makes sure that you can only have numbers in “integer” and “double” fields, and that “time”, “date” and “datetime” fields display the popup calendar/datepicker.
  • the id is the name of the class plus the name of the field, joined by one underscore. This allows you to uniquely refer to the field via, for example, jQuery('#mytable_myfield') and manipulate the stylesheet of the field or bind actions associated to the field events (focus, blur, keyup, etc.).
  • the name is, as you would expect, the field name.

Hide errors

Occasionally, you may want to disable the automatic error placement and display form error messages in some place other than the default. That can be done easily.

  • In the case of FORM or SQLFORM, pass hideerror=True to the accepts method.
  • In the case of CRUD, set crud.settings.hideerror=True

You may also want to modify the views to display the error (since they are no longer displayed automatically).

Here is an example where the errors are displayed above the form and not in the form.

  1. {{if form.errors:}}
  2. Your submitted form contains the following errors:
  3. <ul>
  4. {{for fieldname in form.errors:}}
  5. <li>{{=fieldname}} error: {{=form.errors[fieldname]}}</li>
  6. {{pass}}
  7. </ul>
  8. {{form.errors.clear()}}
  9. {{pass}}
  10. {{=form}}

The errors will displayed as in the image shown below.

image

This mechanism also works for custom forms.