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

Roché Compaan roche at upfrontsystems.co.za
Sat Aug 9 10:38:45 EDT 2003


* Phillip J. Eby <pje at telecommunity.com> [2003-08-08 19:30]:
> 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.)

This is roughly the conceptual model that Formulator uses. During our
discussion I thought that we don't really need "field" and that widgets
can do all that you propose a field should do. I am still not convinced
this is necessary separation, but I'll have to think about it some more.

> 
> 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?)

In my experience its quite common to use the same form for both. The
form does not prescribe how the widgets should be rendered. In the case
of the add/edit form you can call widget.renderInput() or just
widget.render() and for the view you can call widget.renderView(). But I
also think it should be very easy to manipulate the widgets in the form,
precisely because there may be differences between "modes". Sometimes
you just want to remove widgets on the edit form that are visible on the
add form. If the differences are overwhelming then create another form.

I don't think the mode should be set for the form as a whole either. I
often have forms where I call renderInput for some widgets and renderView
for others. The widget render method that must be called should be a
specified by the template as DOMlet parameter.

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

I don't understand why this is tricky in your mind. If I want to return
a specific form for adding I will have an "add" method in a Decorator
that returns a specific template that says "I want to work with form X".
If the template is explicit about what form it wants why is it tricky?

    class Contacts(web.Decorator):

        addForm = MyAddForm()

        def add(self, CONTEXT):
            if CONTEXT.interaction.request.has_key('submit'):
                form.populateFromRequest(CONTEXT.interaction.request)
                if form.isValid():
                    ob = self.addObject()
                    form.updateObject(ob)
                    form.addMessage("Update successful")
                    # do something to render success template using the form
                else:
                    # do something to render the add template using the form
            else:
                # return the add template

Here is the template:

    <table domlet="form:MyContacts" define="addForm">
    <tr domlet="field">
    <th define="title">Field title</td>
    <td define="render">Render widget for this field</td>
    <td define="description">Field description</td>
    </tr>
    </table>

And for editing:

    class Contact(web.Decorator):

        editFormX = MyEditFormX()
        editFormY = MyEditFormY()

        def editX(self, CONTEXT):
            form = self.editFormX(self.subject)
            form.populateFromObject(self.subject)
            if CONTEXT.interaction.request.has_key('submit'):
                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
            else:
                # return the edit template

        def editY(self, CONTEXT):
            # same as above but uses editFormY


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

I don't understand this.

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

Only if you redirect instead of returning to an error page.

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

Hidden fields for lists? No thank you. I decidedly don't like dragging
on hidden fields over multi-page forms - it just contaminates the
namespace of a particular form and you have to turned marshalled data
back into strings until the final page and then marshal them back to
their intended types.

-- 
Roché Compaan
Upfront Systems                 http://www.upfrontsystems.co.za



More information about the PEAK mailing list