History for LetterToAFriend
??changed:
-
This is a letter to a friend that I wrote, trying to describe the CMFFormsTool.
'----snip----'
I cannot yet promise that portal_forms is able to handle your project nor
that it is bug-free .. basically following things are possible:
- multiple forms-collections can be mapped onto each ContentType ..
see it as a mixture of portal_skins and portal_workflow
- a forms-collection holds Formulator-Forms, PageTemplates and PythonScripts
there is another mapping where one can select a template (which has several views)
for a form to display and the Scripts as actions
due to the two mappings it is highly configurable and every forms-collection
offers all forms with certain views and actions.
how it works:
you have a PageTemplate in one of your portal_skin-folders e.g. "custom"
that has the name of your action. e.g. "edit_rack",the template might look like::
<html metal:use-macro="container/main_template/macros/master">
<body>
<div metal:fill-slot="main" tal:define="viewid request/view | string:standard">
<div metal:use-macro="python:here.portal_forms.render_form(here,'masterrack_edit',viewid,REQUEST=request).macros['form']" />
</div>
</body>
</html>
you see that the portalforms tool has a method render_form that takes the
content-object (here), a formid (name
of the formulator-form), a viewid (default is "standard" allthough one can
change this too) and the request.
the method gives you back a freshly wrapped PageTemplate whose macro you can
use then.
and another template in your formscollection e.g. "dtemplate" that is mapped
to your form::
<html>
<body>
<div metal:define-macro="standard">
<span tal:replace="structure form/header" />
<table metal:define-slot="Default" />
<table metal:define-slot="Buttons" />
<span tal:replace="structure form/footer" />
</div>
</body>
</html>
this is the part of your page that is the forms ..
every macro you define here, is a view that can be used to display this form.
this could be used for beginner/advanced, different languages etc.
there are two slots defined by default: "Default" this is the place
where the form-elements are rendered that are in the "Default"-group
of Formulator. "Buttons" is a simple table that has buttons for all
actions that are defined for the form (the scripts btw. the script-title
is used for the buttons value..)
I use the feature from Formulator to group fields to enable
partls of forms that can be inserted in the page at the right place.
for every group-of-formfields you can define a slot in the template ..
or not :) . then you only see parts of the form-elements (beginner-view)
the form-elements in a group are rendered as a simple table with
2 rows (label, widget), the help-text is displayed below the widget
as well as the error-text that can be supplied. when an error occured
there is no exception raised but the form with the original-values from
the request is shown again, and the fields that were wrong are marked
with a different CSS-Class (label, label_with_error)
this is the only part that gets rendered by the engine and is not
customizeable TTW.
the class for this is separated though and only 50 lines of code ..
the following steps happen when a form is rendered:
- first the form is retrieved for the portaltype otherwise an error is raised
- the template for the form is retrieved
- a new pagetemplate is initialized with a generated source that offers the macro "form"
and uses the macro <viewid>. it just fills the slots of "dtemplate" with the form-group tables.
- the template is returned, the macro is included into your page and then
executed by the TAL-engine ( there is another tweaking-possibility that i found out today:
you can specify an "extra"-property in each formfield that is raw-text and is applied to
the input-tag while it's rendered. this enables you to write tal-statements into each formfield.
you can easily retrieve values that may be a result from a call)
basically the formstool tries to get the "alternate_name" or "id" of each
formelement as attribute from the context and then calls it if it is a function otherwise returns
it or the default-value is used from formulator (incl overrides ..). there are at least 2
possiblities before the form is rendered and 1 after to modify the values, styles, etc.
when you included the "Buttons"-slot into your macro you'll get somthing
like::
<table border="0">
<tr>
<td valign="top"><input type="submit" name="just_display:method" value="just display" /></td>
<td valign="top"><input type="submit" name="save_data:method" value="save"/></td>
</tr>
</table>
the method-names are then called on the result object (you need to define an
url in the formulator forms-definition, that handles your request. a single script is
enough, I usually call it "formreply", it is stored in the portal_skins-folder and
looks like::
## Script (Python) "formreply"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=portal_forms_name,portal_forms_view
##title=handle a formreply
##
request = context.REQUEST
pf = context.portal_forms
valid,result =
pf.validate_request(context,portal_forms_name,portal_forms_view,REQUEST=requ
est)
if not valid:
request.set('name',portal_forms_name)
request.set('view',portal_forms_view)
request.set('validation_errors',result)
return context.form()
else:
handler_func = traverse_subpath[0]
return pf.exec_script(context,portal_forms_name,handler_func,result)
this script returns to the original form (portal_forms_name and portal_forms_view are reserved words)
if an validation-error occured and calls the function that is given by the traverse_subpath (your submit-method)
with the validated result. If you define fields in a form outside the scope of formulator you need to change things
here probably too.
You can now have an simple script that is close to the form and its template. there are no blind-passengers
for result-functions in the skins-folder anymore .. the skins-folder has only actions that the user perfoms or
content-relevant skins. every action that needs a form is redirected to the portal_forms tool and the validation
and result are handled just by the routing in "formreply" and your action-script, that fires the workflow-actions
depending on success or not..
'----snip----'