[TransWarp] State of the Onion (updates to current release plans)

Phillip J. Eby pje at telecommunity.com
Fri Aug 8 13:30:26 EDT 2003


At 01:54 PM 8/8/03 +0200, Roché Compaan wrote:
>* Phillip J. Eby <pje at telecommunity.com> [2003-08-08 01:29]:
> > Very Important:
> >
> > * Forms - we need a form widgets framework  (much easier said than done,
> > since we haven't defined precisely what that means, yet)
>
>Let me try.
>
>The problem as I see it is that it is tedious to code HTML forms, make
>them sticky and validate their input and they are generally more
>difficult to reuse than what is possible in python. There are also
>enough similarities between the add form, edit form, list view and
>record view of an object that makes coding a template for each feel even
>more tedious and often leads to a lot of inconsistency in the ui.
>
>To start of with we need a set of widgets. A widget is a python class
>that knows how to render itself as HTML. A widget can render a default
>if supplied or render a value if passed one.

Whether it occurs at this level or the widget set level, it needs to be 
able to deal with multiple possible values, e.g. the submitted form 
contents, and the subject object's current value, as well as a default.

This also seems to imply that there may be "modes" that a form is in, e.g. 
add/edit/view modes.  But, we can possibly break this down a 
little...  more below.


>Next we need to assemble widgets into a collection that can be offered
>by a Decorator to a template for rendering. We call this a WidgetSet.
>Widgets in the WidgetSet must have order.
>
>We need DOMLets that can iterate over a WidgetSet and render its
>widgets. These DOMLets are also responsible for supplying the widgets
>with values. First they should check if there is a value on the request
>and then on the subject, so the values on the request overrides.

I think that perhaps this would actually be cleaner if we use a slightly 
different conceptual model, to divide up the work better.  Let's change 
what we've been calling a "widget set" into a "form definition", containing 
"widgets", and then also have "forms".  A form is an object that contains a 
set of "fields", where a "field" is a data value and optional current 
validation messages.  (Actually, a field will have more than that, it will 
also reference the underlying widget, and offer other convenient attributes 
for use by the rendering machinery.  But its value and messages will be 
changeable, and the other stuff probably will be constant over the life of 
the form instance.)

Now, we can request that a "form" populate its fields from the object it's 
editing or viewing, to override the defaults that come from the form 
definition (which in turn probably defaults its defaults from the object 
type definition).  We can then request it override *those* defaults with 
anything that's filled in on the request.  Or, we can just not override 
anything, for "add" mode.  I think that this covers nearly all the "modes" 
that we want forms to be able to have, but without hardwiring any mode 
handling into the form object or definition.

(By the way, I'm not entirely convinced that it's desirable to use the same 
form definition to represent both viewing and editing, unless perhaps the 
form definition can optionally allow you to define different widgets for 
viewing and editing.  The key would be "optionally", since if you are 
required to always define two widgets for each field, you might as well 
create two form definitions, no?)

So, form objects would have methods like populateFromObject(), and 
populateFromRequest().  They could validate their contents as they are 
populated, or wait until you perform some action that requires validation 
(e.g. updateObject()).  The form object (and/or its contained "widget 
content" objects) would hold any "messages" that result from 
validation.  In theory, an update method in a decorator might look 
something like:

    def update(self, CONTEXT):
        form = self.editFormDefinition(self.subject)
        form.populateFromObject(self.subject)
        form.populateFromRequest(CONTEXT.interaction.request)
        if form.isValid():
            form.updateObject(self.subject)
            form.addMessage("Update successful")
            # do something to render success template using the form
        else:
            # do something to render the editing template using the form

Obviously, this is a lot of stuff to type under normal circumstances, so 
we'll probably collapse a lot of it into higher-level "do everything" 
methods to cover the common case usages.  Also obviously, I need to figure 
out how the hand-wavy bits ("do something to render X") will actually work....

Specifically, in a template we need to be able to say, "I want to work with 
form X in this block", and if the template was called with an explicit form 
X, we get that form object, otherwise we should get a new form object, 
populated with data from the object that is the subject of our 
template.  (Add forms are trickier - we need to be able to get a form from 
the specialist, since we don't have an instance of the object to get it from.)

Actually, it's tricky even for instances.  It's possible that the page 
we're rendering has edit forms for more than one object.  So we can't say 
"from the subject of our template" -- there might be more than one.  We 
need to get a form for some specific object, designated by the data path in 
the DOMlet attribute.

This seems to imply that form definitions found as attributes on a 
decorator, need to play a dual role.  Not only do they need to be the 
constructor for a form instance, they also need to be able to look "up the 
execution context" to see if an existing instance of that form definition, 
*for a particular object*, already exists.  Note that this means that 
really such an attribute isn't going to be a "form definition", but more 
like a "form binding" that knows the form definition, and the object the 
form is for.  At this point, such an object is also a natural for being the 
one to perform most of the actions outlined in the update() method above: 
create the instance, populate it, etc.

This still leaves the question of how to register (and find) form instances 
in the execution context, and also some open questions about how to do 
multi-object edits.


>For forms, the values should be validated when a POST occurs. I think
>this can be much simpler than I initially thought. A Decorator calls
>applyChanges on the WidgetSet that first tries to apply changes to the
>underlying object and then calls validate to do any extra validation.
>applyChanges gets context and object as arguments and uses the keys of
>the widgets in the WidgetSet to lookup values in the request and apply
>it to the object. If there are any failures exceptions are raised and
>stored in an errors data structure.
>
>If there were errors they should be available to templates for
>rendering. I'm unclear on how this will work.

I think they should be attached to the form and its fields, which the 
template will have access to during rendering.


>Can the errors just be set
>on the request object? This will only work if we return the template in
>the same request, otherwise errors will have to be saved on the session.
>DOMLets rendering form errors should then look on both the request and
>session for errors.

I'm confused by this, in that I don't understand how/when you would *not* 
display the errors as part of the same operation.

I *can* see the possibility that one might have such a thing as a 
"multi-page form", but I think it would work like this...  Imagine that you 
have one large form definition, but the widgets on it have a "page" 
property (or maybe the definition is divided into pages; whatever 
works).  Whenever you render the form, you put in hidden fields for any 
page that's already passed, regular widgets for the page you're on, and 
ignore any widgets for a page you've not yet reached.  If there are errors, 
you simply render the first "page" with errors in it.




More information about the PEAK mailing list