[Twisted-web] Freeform localisation and buttons (Was: Freeformdefault button label)

Simon Hedberg simon at hedberg.net
Wed Jul 20 06:27:15 MDT 2005


To follow up on this thread on button issues I want to share the
following with you. 
Since I want multiple buttons per form in my app I started digging a bit
in webform.py. I've came up with the supplied patch that makes this a
more pleasant experience by making the button renderer pick up template
patterns.
This makes buttons appear at the right place even when doing custom
layouts and also makes it possible to create patterns that use image
buttons. Have a look at the supplied example (run twistd -noy
buttonexample.py). After applying the patch the extra buttons should
appear in the right place and one of them will be in the form of the
twisted logo.

Hope this helps.
And anyone with commit access; feel free to commit the patch to trunk if
you think it's ok.

Final note: To remove the default button just exclude the form-button
slot in your custom template. In the example this is done dynamically
using a render method.

/Simon

-----Original Message-----
From: twisted-web-bounces at twistedmatrix.com
[mailto:twisted-web-bounces at twistedmatrix.com] On Behalf Of Richard Wall
Sent: den 18 juli 2005 23:36
To: Discussion of twisted.web, Nevow, and Woven
Subject: [Twisted-web] Freeform localisation and buttons (Was:
Freeformdefault button label)


On 18/07/05 19:16 Paul Reznicek wrote:
> I see a lot of usecases for more buttons, one I'm writing now: DB 
> record browser/editor, where are at least
>   [previous|next|search] + [reload|new|copy|save|delete]
> are necessary.
> You are right, it is possible to do this with hyperlinks,
> but this allow users to bookmark every of this actions,
> and to use the browser's back button with undesired effects.

Paul, you know your webapp best... but in my experience this could also
be achieved with a single submit button and supplementary hyperlinks.
Assuming that the page above is a form representing the state of a
single record...

Next / Previous - hyperlinks *because* that will allow the user to
bookmark particular pages.

Search - a submit button but associated with a separate search form.

Save - The single submit button to POST the form. Formless does an http
redirect after a successful post so the back button shouldn't be a
problem.

*Cancel - I hyperlink to the previous page or to a datagrid / index of
records. Although you didn't mention it, I think it's a good to provide
users with a way of escaping from the form.

The following would only be shown for saved records...

Reload - a hyperlink to the current resource. To discard changes made to
the data. (Although a simple <input type="reset"> might do that

New - Hyperlink to a blank form.

Copy - Hyperlink to a form prefilled with the data of the source record,
but unsaved.

Delete - a hyperlink to a deletion confirmation page displaying the
record that is to be deleted. With a single submit button to confirm
deletion, and which http POSTs so that the deletion url cannot be
bookmarked.

> To extend the discussion about freeform:
> - please also pay attention to the fact, that all labels, errors 
> messages, tips, etc. should be easy localizable.

I've never done a foreign language site, but I can see that this would
be useful. I just tried hacking the i18n example a bit and have found
that you can use an i18n.Translator as the label, requiredFailMessage
parameters to the eg annotate.String. Have attached tgz archive of the
app. It seems really cool, but I haven't had time to figure out how to
generate the .mo files from the .po files. It would be good if that
could be done automatically when the .po files change. Maybe you'll
better understand that side of things.

> - error messages can vary depending on field contents: integer error 
> is from user's point of view not a numeric error, (i.e. "please enter 
> your age as whole number")

You might get away with annotate.String(requiredFailMessage="please
enter your age as whole number")

More likely, you'll want to create your own library of formless types,
processors and renderers. For ideas see second attachement.

-- 
Richard Wall
-------------- next part --------------
A non-text attachment was scrubbed...
Name: webform.diff
Type: application/octet-stream
Size: 934 bytes
Desc: not available
Url : http://twistedmatrix.com/pipermail/twisted-web/attachments/20050720/bfd8eafc/webform.obj
-------------- next part --------------
#################################################################################
# Example of having more than one submit button
# Made to show the benefits of the supplied patch
# /STemplar


from zope.interface import implements

from nevow import rend
from nevow import url
from nevow import loaders
from nevow import tags as T
from nevow import inevow

from formless import annotate
from formless import webform


FORM_LAYOUT = loaders.xmlstr("""<?xml version="1.0"?>
<form xmlns:n="http://nevow.com/ns/nevow/0.1" n:pattern="freeform-form" enctype="multipart/form-data">
  <n:attr name="action"><n:slot name="form-action"/></n:attr>
  <n:attr name="id"><n:slot name="form-id"/></n:attr>
  <n:attr name="name"><n:slot name="form-name"/></n:attr>
  <n:attr name="method">post</n:attr>
  <p><strong><n:slot name="form-label"/></strong></p>
  <p><em><n:slot name="form-description"/></em></p>
  <p><strong><em><n:slot name="form-error"/></em></strong></p>
    <table id="form-table">
        <n:slot name="form-arguments"/>
        <n:invisible n:render="formbutton">
            <n:slot name="form-button"/>
        </n:invisible>
    </table>
    <n:invisible n:pattern="argument">
       <tr>
           <td>
           <n:slot name="label"/>
           </td>
           <td>
           <n:slot name="input"/><span class="freeform-error"><n:slot name="error"/></span>
           <n:slot name="description"/>
           </td>
       </tr>
    </n:invisible>
    <n:invisible n:pattern="argument!!action2">
       <tr>
           <td>
           <n:slot name="label"/>
           </td>
           <td>
           <input type="image" widht="155" height="24" src="http://twistedmatrix.com/images/header-logo.png">
               <n:attr name="name"><n:slot name="name"/></n:attr>
               <n:attr name="value"><n:slot name="value"/></n:attr>
           </input>
           <n:slot name="description"/>
           </td>
       </tr>
    </n:invisible>
    <n:invisible n:pattern="form-button">
        <td></td><td><n:slot name="input"/></td>
    </n:invisible>
</form>
"""      ).load()
css = """
.freeform-radio-option {margin-left:0px;}
"""
class Root(rend.Page):
    """Render a custom and normal form for an ISomething.
    """       
    addSlash = True
    _showbutton = True
    
    child_webform_css = webform.defaultCSS
    
    def render_normalForm(self, ctx, data):
        return webform.renderForms()
    
    def render_customForm(self, ctx, data):
        return webform.renderForms()[FORM_LAYOUT]
    
    def doSomething(self, ctx, **kwargs):
        if kwargs.get('defaultbutton'): #Switch the value of self._showbutton
            self._showbutton = [1,0][self._showbutton==1]
        btn = "default"
        if kwargs.get('action1'):
           btn = "action1" 
        elif kwargs.get('action2'):
           btn = "action2" 
        return [T.p['You clicked the %s button.'%btn],T.p['doSomething called with: %s' %kwargs]]
    
    def bind_doSomething(self, ctx):
        return [
            ( 'ctx', annotate.Context() ),
            ( 'fee', annotate.String(description="Wee!") ),
            ( 'fi', annotate.Integer(description="Tra-la-la") ),
            ( 'fo', annotate.Text() ),
            ( 'fum', annotate.String()),
            ( 'defaultbutton', annotate.Radio(choices=lambda x,y:[('Show',),('Hide',)][self._showbutton])),
            ( 'action1', annotate.Button()),
            ( 'action2', annotate.Button())]
    
    def render_formbutton(self,ctx,data):
        if self._showbutton:
            return ctx.tag
        else:
            return ''
            
    def render_hand(self, ctx, data):
        hand = inevow.IHand(ctx, default=None)
        if hand is not None:
            return ctx.tag[hand]
        return ''
        
    docFactory = loaders.stan(
        T.html[
            T.head[
                T.title['Example :: Custom Form Layout'],
                T.link(rel='stylesheet', type='text/css', href=url.here.child("webform_css")),
                T.style[css],
                ],
            T.body[T.p(render=T.directive("hand")),
                T.h1['Custom'],
                render_customForm,
                T.h1['Default'],
                render_normalForm,
                ]
            ]
        )

from twisted.application import internet, service
from nevow import appserver
application = service.Application('buttontest')
webServer = internet.TCPServer(8080, appserver.NevowSite(Root()))
webServer.setServiceParent(application)


More information about the Twisted-web mailing list