Formetto rationale
Nowadays ToscaWidgets seems to be the most important form declaration framework, but it implements more or less the same interface as TurboGears Widgets. It has these perceived disadvantages: * In general, they lead to too much repetition and too much code. * Forms are programmed as classes that contain widgets. But widgets should not be in the controller layer. I prefer that my widgets stay in the Genshi template. * If custom positioning of widgets is needed, you end up writing the whole HTML template anyway. * To populate the widgets with their values you need to pass dictionaries around. Unnecessary work: since mostly my forms represent SQLAlchemy ORM objects, the values can be obtained automatically. * Validation in the controller is overrated. FormEncode is a framework for validation in the controller. But clearly, in the interest of code reuse, the first and foremost place for validation code is the model. The controller should be used for validation only when it is impossible to express it in the model (e.g. if the validation logic depends on information that is not present in the model). So, whenever I can, I prefer to use properties in the model rather than validation in the controller. Is not this a better MVC?
Formetto is my attempt at a new interface. It tries to take all this code out of the controller and provide client-side validation (javascript) which occurs before the form is submitted to the server.
Formetto removes repetition. We are used to describing different aspects of a field in 4 places and this is way too much: 1. the model, 1. the controller, 1. the HTML and 1. the Javascript
Formetto removes form declaration from the controller and unifies it in the template (a single declaration is enough to generate HTML and Javascript). We end up with: 1. the model and 1. the Genshi template.
Formetto advantages
- (much) shorter controllers
- easy positioning of widgets in the Genshi template
- extremely easy generation of table forms
- optional introspection of SQLAlchemy ORM objects to populate the widgets:
- automatic discovery of the value
- automatic discovery of the default value
- automatic discovery of the maxlength attribute
- automatic generation of Javascript at the end of the page
- helps with client-side validation (need to import formetto.js into the page)
- enforces required fields
- offers behaviour for email fields, number fields etc.
- fast implementation using StringIO
Known Formetto disadvantages (temporary?)
- does not help with validation at the controller level
- does not help with showing the form again for the user to correct his mistake. Currently we present an error message and tell the user to press the Back button.
- no fancy widgets yet (e.g. no calendar)
Example
See how easy the controller is:
@expose(template=".templates.BlogEdit")
def edit(self, blogId):
"Presents the form to add or edit a Blog."
if blogId == "0": # If we are creating a new Blog...
o = Blog("new blog", slug="") # ...instantiate it...
o.expunge() # ...then make it transient, so it won't be persisted.
else: # If editing an existing Blog...
o = self.getBlog(blogId) # ...just get it from the database.
if o == None: return error(u"That blog does not exist: " + blogId)
return dict(
action = url_for("Blog", action="save", blogId=blogId),
f = Formetto(obj=o) # Pass to the Formetto constructor the Blog object.
)
The Genshi template is not so different from straight HTML:
```
${ f.Text(id="name", label="Title", size=58, required=True) } ${ f.Text(id="slug", label="Slug" , size=30, required=True, title="short name, for the URL") } ${ f.TextArea(id="summary", label="Summary", rows=2, cols=59) } ${ f.Text(id="templateFile", label="Template", size=40, title="name of the template file for this blog", help=".html") } ${ f.Select(id="authors", label="Blog authors", required=True, multiple=True, options = [(t.id, t.display_name, t in o.authors) for t in editors]) } ${f.Button(id="btnSend", value="Send", action=action)} ${f.getJavascript()}```
Notice the getJavascript() call at the end of the page. It outputs generated javascript for all widgets that need it. It must be below all widgets.
In the code above, whenever a label parameter is provided, instead of returning just the widget, Formetto encloses it in a table row. The output looks like this:
<tr>
<th><label for="name">Title:</label></th>
<td><input type="text" id="name" name="name" value="Nando's blog" maxlength="80" class="textfield" size="58" /> *</td>
</tr>
The asterisk at the end informs the user that this is a required field.
What does this example accomplish?
- When the page is displayed, all widgets are populated with their values.
- maxlength is set for all text widgets by looking up the SQLAlchemy object.
- Textareas have an optional benefit: the user can change their vertical size by clicking a couple of links. Javascript.
- If the user tries to submit the form without filling out all required fields, a message is displayed and the form is not sent.
- The controller knows nothing about the fields! You only describe them:
- in the model, so they can be persisted, and
- in the template, so they can be shown.
- Once and only once. Each widget is described in only place in the template.
Read the code
Formetto can be used separately from Webpyte. It is made of 3 files: * formetto.py is the Python module * formetto.js provides client-side features * strDic.js is a string dictionary for i18n.
To extend Formetto, just create subclasses. Send us your code!
Roadmap
- Investigate whether Formetto could become just an adapter, encapsulating ToscaWidgets to reuse all that code.
- Integer fields in the model could be automatically detected to generate the client-side behaviour.
- Idem for DateTime fields.
- An Email class should be derived from the Text class.
- Formetto currently knows about SQLAlchemy and Genshi. If you would like to decouple it from these, or add integration with other components, OK, let us do it.