LetterToAFriend
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
. 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----