Integrating CMFFormController with Formulator
(Note: While still useful and interesting, this howto has been superceded by products in the collective — CMFFormulator, Uniformed and PloneFormMailer, depending on what integration you need. Consider the below as being an interesting exercise, and look into the products if you want to use Formulator in Plone.)
I love the flexibility of the new CMFFormController, but still wanted to be able to use all the pre-existing field types and validations available with Formulator. So I created a lil ZPT macro that lays out Formulator forms (respecting the Plone Forms CSS rules...Thanks Arnia!) and a wrapper around the Formulator validate_all_to_request function. This allows you to create forms in Formulator, but use the CMFFormController to handle the state changes in the form. With these scripts, you can now create you forms normally in Formulator. Don't assign any submit action to them - just define the encoding, fields, etc. Then you create your controlled page template wrapper that calls the form. You can the use the .metadata file (or the ZMI) to control what happens on form submission, etc.
The first step, is obvisously to install CMFFormController.
Next, place the following macro somewhere you can get it via accquistion... (Note: I'm sure you all can find ways to improve the code - please let me know if you do...)
Code:
<div metal:define-macro="showform"
tal:define="form python:path('here/%s' % formname);
title python:test(form.title,form.title,'Unknown');
method python:form.method; enc python:form.enctype;
fname string:${title}_form; tid template/id" >
<form class="group" id="x" action="x" method="x" enctype="x"
tal:define="id form/name; errors options/state/getErrors"
tal:attributes="action tid; method method; id id; enctype enc;">
<span class="legend" i18n:translate="" tal:content="title">Legend</span>
<metal:block tal:define="fields python:form.get_fields"
tal:repeat="field fields">
<div class="row" i18n:domain="plone"
tal:define="id python:field.id; label field/title;
cls python:test(field.is_required(),'label required','label')">
<div class="label" tal:attributes="class cls">
<span i18n:translate="" tal:content="label">Name</span>
</div>
<div class="f" tal:define="err python:path('errors/%s|nothing' % id)"
tal:attributes="class python:test(err,'field error','field')">
<div tal:condition="err">
<tal:block i18n:translate="" content="err">Error</tal:block><br />
</div>
<div tal:define="default field/default;
temp python:request.get('field_'+field.id,None);
value python:test(temp,temp, default)"
tal:replace="structure python:field.render(value=value);">field</div>
</div>
</div>
</metal:block>
<div class="row">
<div class="label"> </div>
<div class="field">
<input value="Save" class="context" i18n:attributes="value"
type="submit" tabindex=""
tal:attributes="tabindex tabindex/next;" />
</div>
</div>
<input type="hidden" name="form.submitted" value="1" />
<input type="hidden" name="itsform" value="1"
tal:attributes="value formname" />
</form>
</div>
This macro builds a form from a formulator definition. It gets the name of the form to render from formname. formname should be a path to the form without the leading here/ (eg /here/forms/form1 would be forms/form1). It also sticks a simple submit button at the bottom of the form. It then uses interpolation to create a number of dependent vars, etc. The various divs are so the resulting form follows the plone styling guidelines and looks like a plone form.
The next piece is the validator - (Note: this should go in a controlled python script)
Code:
## Script (Python) "test_form_validator"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=
##title=
##
from Products.Formulator.Errors import ValidationError, FormValidationError
state = context.portal_form_controller.getState(script,
is_validator = 1)
form = context.restrictedTraverse(context.REQUEST.form.get('itsform'))
try:
form.validate_all_to_request(context.REQUEST)
except FormValidationError, e:
for err in e.errors:
state.setError(err.field_id,err.error_text, new_status='failure')
return state
As you can see, the validator just calls Formulator validate_all_to_request() and adds the errors to the CMFFormController state.
Finally, here is a page to display a form, and validate it -
Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US"
lang="en-US"
metal:use-macro="here/main_template/macros/master"
i18n:domain="plone">
<tal:block metal:fill-slot="base">
<tal:block tal:define="dummy python:request.set('disable_border',1)" />
</tal:block>
<body>
<div metal:fill-slot="main"
tal:define=" tform string:Junk/form;
form python:path('here/%s' % tform);
title python:test(form.title,form.title,'Unknown');
method python:form.method; enc python:form.enctype;
fname string:${title}_form; tid template/id" >
<span tal:define="dummy python:request.response.setHeader('pragma','no-cache')" />
<h1> Feedback</h1>
Please fill out the following form and we will get right back to you<br>
<br>
<metal:block tal:define="formname string:Junk/form" >
<div metal:use-macro="here/itsforms/macros/showform">
FORM STUFF HERE
</div>
</metal:block>
</div>
Notice we use the master macro - for some reason, the forms don't appear in the content area by themselves - so we just wrap the theme around the form. The actual section for calling a form is just:
Code:
<metal:block tal:define="formname string:Junk/form" >
<div metal:use-macro="here/itsforms/macros/showform">
FORM STUFF HERE
</div>
</metal:block>
New Products
From dn:
I just put Uniformed into the collective. It's largely based on the ideas presented here plus some fixes (Plone2 look and feel, fields have descriptions, encodings).
See the Product's README.txt, section HOWTO for a quick explanation.
From jensens:
Based on the ideas above and on some code of Daniels Uniformed I made PloneFormMailer. Daniel also added good piece of code to it. See http://plone.org/newsitems/ploneformailer01a for more info.
