[TransWarp] peak.web and forms

Roché Compaan roche at upfrontsystems.co.za
Tue Aug 5 08:41:54 EDT 2003


* Phillip J. Eby <pje at telecommunity.com> [2003-08-04 23:21]:
> At 09:49 PM 8/4/03 +0200, Roché Compaan wrote:
> 
> >Before I get to form generation it should be possible to build a
> >BrowserView in pure python. A BrowserView is a collection of widgets
> >with a binding to a model.Element. I don't want to call it a
> >BrowserForm because it does not only render HTML forms - it can also
> >render non-editable views
> >
> >A BrowserView introspects the element to supply its widgets with data.
> >No layout or look and feel is set on a BrowserView - that is the
> >responsibility of the template rendering the view. Templates will have
> >access to a set of domlets that knows how to render a BrowserView, maybe
> >something like:
> >
> >    <table domlet="browserview:foo">
> >    <tr define="widget">
> >    <th domlet="text:title">widget title</th>
> >    <td domlet="render">rendered widget</td>
> >    </tr>
> >    </table>
> 
> Looks good so far.  I presume that the BrowserView DOMlet will adapt its 
> subject to an IBrowserView interface, which will then supply an iterator 
> over a set of IWidgets?  And each IWidget offers a title attribute, maybe a 
> help link, and some other attributes (e.g. validation messages)?

Yes, most widget attributes should be available.

> Of course, I can potentially see the need for alternate views of the same 
> record.  It sounds like in such applications, one would want the UI 
> decorator to offer such views as attributes, e.g.:

I initially thought that they could be separate decorators but I like
this idea - it's more compact.

> 
> class ContactView(web.Decorator):
> 
>     protocols.advise(
>         instancesProvide=[IWebTraversable],
>         asAdapterForTypes=[MyContactClass],
>         factoryMethod='asTraversableFor'
>     )
> 
>     asLineItem = WidgetSet(...blah...)
>     asEditForm = WidgetSet(...blah...)
>     security.allow(asLineItem = [security.Anybody], 
> asEditForm=[contacts.Manage])
> 
> Then a template could refer to a particular widget set, e.g.:
> 
> <table domlet="recordView:asEditForm">
> ...etc.
> 
> (Btw, I think BrowserView is too generic, and that RecordView might be a 
> more function-oriented description.)
> 
> If WidgetSets are implemented/implementable as Resources, the class above 
> could use 'bindResource()' instead of hardcoding the widget sets.  This 
> would also make the widget sets overrideable in different skins.

I haven't looked at Resources yet, how do you make a WidgetSet a
Resource? I just scanned your latest checkin messages and thought
Resources are limited to files and templates - I must still study your
resource design notes and the code.

> >If a HTML form is submitted your app can call validate on the
> >BrowserView with the request as parameter to validate all fields. The
> >validated result can then be applied to the element the BrowserView is
> >bounded to.
> 
> Hm.  I'd rather that the view simply assigned the fields to the underlying 
> object, one after the other, tracking any errors.  After all fields were 
> set, calling an overall validate() method could be done on the object to 
> get any high-level issues.

And validate can set an error message on the appropriate widget for
rendering by the template.

> >If there are validation errors your app can use them to
> >re-render a template with appropriate error messages. At this point
> >rendering a widget should show the input last provided by the user - ie.
> >the form should be sticky.
> >
> >How does this sound?
> 
> Mostly good, but there are some pieces I'm having trouble wrapping my head 
> around, like where does the POST go to?  What if there's more than one form 
> on the page?  (These two questions overlap a bit, since if there's more 
> than one form, they can't both POST to the same place.)  If you POST to the 
> widget set (a logical alternative), how do you get back to the template 
> that rendered the submitting page?

I think this is really a pattern that each app should work out for
itself but what follows is what I would like to do. I am not sure yet
how this would work in PEAK but the POST goes to any method that keeps
the form stuff in play eg. (I'll try to explain this in PEAK terms): I
want to add, edit and view contacts TTW. I create an IWebTraversable
that has add, edit and view methods - lets call them actions. 'add',
'edit' and 'view' returns the templates 'add_template', 'edit_template'
and 'view_template' when called for the first time or when we come from
a given location. Each of the contact templates are coded to post back
to their corresponding action on the contact traversable.

If we post to the 'add' action, for example, it calls upon the
RecordView to interpret the request, apply changes to its underlying
object and return any validation errors. If there are errors the add
action returns the add_template again. The template uses DOMlets that
already know how to interrogate the widgets for errors so it displays
them. If there are no errors it redirects to another location or returns
another template showing that the operation was successful, or whatever
the app wants.

You don't have to post back to the same action, but I have found making
these actions into mini state machines works really well and keeps urls
clean.

I hope it's clear from above that it really doesn't matter how many
forms there are on a page, as long as the action you are posting to
knows what is coming and what it should return.

We actually take the state machine idea even further in our existing
apps. The 'add' action delegates to a form wizard that knows which form
to display given the current state. So after I am done with RecordViews
I'll take on the form wizard.

> 
> There are also some further-out-there issues stewing in my head, like if 
> you want to use JavaScript to pop-up help windows or perform validation, 
> and how to get that into the overall page layout...  :)
> 

At the moment widgets can do simple javascript:

w = TextWidget()
w.extra = "onclick='popup_window();'"
print w.render('some_key', 'some value')
<input type="text" name="some_key" value="some value" onclick='popup_window();'" />


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



More information about the PEAK mailing list