Changeset 231

Show
Ignore:
Timestamp:
07/04/06 09:53:31 (3 years ago)
Author:
matt
Message:

Merge -r204:228 fieldgroups to trunk.

The fieldgroups branch introduces groups of fields. A Group is really just a
(dotted) namespace in the Form's data dictionary and a <fieldset> in the HTML.

See the ChangeLog? and examples for more information.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/ChangeLog

    r175 r231  
     12006-07-04  Matt Goodall <matt@pollenation.net> 
     2 
     3        Added groups of fields. A Group of Fields can now be added to a Form using 
     4        the Form.add() method. Fields are added to the Group using the Group.add() 
     5        method. A Group can also be added to another Group using Group.add() 
     6         
     7        A Group is reresented by a <fieldset> element and it completely contains 
     8        its Fields. A Group currently performs to validation of its own. 
     9 
     10        Each Group is named, just like a Field, and the name must be unique within 
     11        the context of the object it is added to (i.e. a Group of Form). The 
     12        effect of the Group's name is that an extra "segment" is added to the Form 
     13        data's dict keys to provide the namespace for Fields in a Group. 
     14         
     15        For instance, if a Field, 'bar', is added to a Group, 'foo', and that 
     16        Group is then added to a Form the field's data key is 'foo.bar'. 
     17 
     18        This change introduces a new Form.add() method. It is intended to replace 
     19        Form.addField() although that method still exists. All Form.addField(...) 
     20        now does is forward the call to Form.add(formal.Field(...)). 
     21        Form.addField() will probably be deprecated sometime. 
     22 
     23        I may have slighty broken the default (and your) stylesheet with this 
     24        change because I had been using a <fieldset> immediately inside the <form> 
     25        element. That turns out to be unnecessary (any block element will do) so I 
     26        decided to use a <fieldset> only for Groups to keep the <fieldset> nesting 
     27        as shallow as possible. 
     28 
    1292005-12-21  Damian Staniforth <damian@pollenation.net> 
    230 
  • trunk/formal/__init__.py

    r225 r231  
    44 
    55 
    6 version_info = (0, 8, 2
     6version_info = (0, 9, 0
    77version = '.'.join([str(i) for i in version_info]) 
    88 
     
    1414from formal.widgets.restwidget import * 
    1515from formal.widgets.multiselect import * 
    16 from formal.form import Form, ResourceMixin, renderForm 
     16from formal.form import Form, Field, Group, ResourceMixin, renderForm 
    1717from formal import iformal 
    1818 
  • trunk/formal/examples/group.py

    r213 r231  
    2424 
    2525        form = formal.Form() 
    26         form.addField('before', formal.String()) 
     26        form.add(formal.Field('before', formal.String())) 
    2727        form.add(makePersonGroup('me')) 
    2828        form.add(makePersonGroup('you')) 
    29         form.addField('after', formal.String()) 
     29        form.add(formal.Field('after', formal.String())) 
    3030        form.addAction(self.submitted) 
    3131 
  • trunk/formal/examples/main.py

    r197 r231  
    1414    'formal.examples.missing.MissingFormPage', 
    1515    'formal.examples.prepopulate.PrepopulateFormPage', 
     16    'formal.examples.group.GroupFormPage', 
    1617    'formal.examples.fileupload.FileUploadFormPage', 
    1718    'formal.examples.smartupload.SmartUploadFormPage', 
     
    2223    'formal.examples.restwidget.ReSTWidgetFormPage', 
    2324    'formal.examples.nofields.NoFieldsFormPage', 
     25    'formal.examples.hidden.HiddenFieldsFormPage', 
    2426    ] 
    2527 
  • trunk/formal/form.py

    r223 r231  
    66from twisted.internet import defer 
    77from twisted.python.components import registerAdapter 
    8 from nevow import appserver, context, loaders, inevow, tags as T, url 
     8from nevow import appserver, context, loaders, inevow, rend, tags as T, url 
    99from nevow.util import getPOSTCharset 
    1010from formal import iformal, util, validation 
     
    7171 
    7272 
     73 
     74class Field(object): 
     75 
     76 
     77    itemParent = None 
     78 
     79 
     80    def __init__(self, name, type, widgetFactory=None, label=None, 
     81            description=None, cssClass=None): 
     82        if not util.validIdentifier(name): 
     83            raise ValueError('%r is an invalid field name'%name) 
     84        if label is None: 
     85            label = util.titleFromName(name) 
     86        if widgetFactory is None: 
     87            widgetFactory = iformal.IWidget 
     88        self.name = name 
     89        self.type = type 
     90        self.widgetFactory = widgetFactory 
     91        self.label = label 
     92        self.description = description 
     93        self.cssClass = cssClass 
     94 
     95 
     96    def setItemParent(self, itemParent): 
     97        self.itemParent = itemParent 
     98 
     99 
     100    def _getKey(self): 
     101        parts = [self.name] 
     102        parent = self.itemParent 
     103        while parent is not None: 
     104            parts.append(parent.name) 
     105            parent = parent.itemParent 
     106        parts.reverse() 
     107        return '.'.join(parts) 
     108 
     109 
     110    key = property(_getKey) 
     111 
     112 
     113    def makeWidget(self): 
     114        return self.widgetFactory(self.type) 
     115 
     116 
     117    def process(self, ctx, form, args, errors): 
     118 
     119        # If the type is immutable then copy the original value to args in case 
     120        # another validation error causes this field to be re-rendered. 
     121        if self.type.immutable: 
     122            args[self.key] = form.data.get(self.key) 
     123            return 
     124 
     125        # Process the input using the widget, storing the data back on the form. 
     126        try: 
     127            form.data[self.key] = self.makeWidget().processInput(ctx, self.key, args) 
     128        except validation.FieldError, e: 
     129            if e.fieldName is None: 
     130                e.fieldName = self.key 
     131            errors.add(e) 
     132 
     133 
     134 
     135class FieldFragment(rend.Fragment): 
     136    implements(inevow.IRenderer) 
     137 
     138 
     139    docFactory = loaders.stan( 
     140        T.div(id=T.slot('fieldId'), _class=T.slot('class'), 
     141                render=T.directive('field'))[ 
     142            T.label(_class='label', _for=T.slot('id'))[T.slot('label')], 
     143            T.div(_class='inputs')[T.slot('inputs')], 
     144            T.slot('description'), 
     145            T.slot('message'), 
     146            ]) 
     147 
     148 
     149    hiddenDocFactory = loaders.stan( 
     150            T.invisible(render=T.directive('field'))[T.slot('inputs')]) 
     151 
     152 
     153    def __init__(self, field): 
     154        self.field = field 
     155        # Nasty hack to work out if this is a hidden field. Keep the widget 
     156        # for later anyway. 
     157        self.widget = field.makeWidget() 
     158        if getattr(self.widget, 'inputType', None) == 'hidden': 
     159            self.docFactory = self.hiddenDocFactory 
     160 
     161 
     162    def render_field(self, ctx, data): 
     163 
     164        # The field we're rendering 
     165        field = self.field 
     166 
     167        # Get stuff from the context 
     168        formData = iformal.IFormData(ctx) 
     169        formErrors = iformal.IFormErrors(ctx, None) 
     170 
     171        # Find any error 
     172        if formErrors is None: 
     173            error = None 
     174        else: 
     175            error = formErrors.getFieldError(field.key) 
     176 
     177        # Build the error message 
     178        if error is None: 
     179            message = '' 
     180        else: 
     181            message = T.div(class_='message')[error.message] 
     182 
     183        # Create the widget (it's created in __init__ as a hack) 
     184        widget = self.widget 
     185 
     186        # Build the list of CSS classes 
     187        classes = [ 
     188            'field', 
     189            field.type.__class__.__name__.lower(), 
     190            widget.__class__.__name__.lower(), 
     191            ] 
     192        if field.type.required: 
     193            classes.append('required') 
     194        if field.cssClass: 
     195            classes.append(field.cssClass) 
     196        if error: 
     197            classes.append('error') 
     198 
     199        # Create the widget and decide the method that should be called 
     200        if field.type.immutable: 
     201            render = widget.renderImmutable 
     202        else: 
     203            render = widget.render 
     204 
     205        # Fill the slots 
     206        ctx.tag.fillSlots('id', util.render_cssid(field.key)) 
     207        ctx.tag.fillSlots('fieldId', [util.render_cssid(field.key), '-field']) 
     208        ctx.tag.fillSlots('class', ' '.join(classes)) 
     209        ctx.tag.fillSlots('label', field.label) 
     210        ctx.tag.fillSlots('inputs', render(ctx, field.key, formData, 
     211            formErrors)) 
     212        ctx.tag.fillSlots('message', message) 
     213        ctx.tag.fillSlots('description', 
     214                T.div(class_='description')[field.description or '']) 
     215 
     216        return ctx.tag 
     217 
     218 
     219 
     220registerAdapter(FieldFragment, Field, inevow.IRenderer) 
     221 
     222 
     223 
     224class Group(object): 
     225 
     226 
     227    itemParent = None 
     228 
     229 
     230    def __init__(self, name, label=None, description=None): 
     231        if label is None: 
     232            label = util.titleFromName(name) 
     233        self.name = name 
     234        self.label = label 
     235        self.description = description 
     236        self.items = FormItems(self) 
     237        # Forward to FormItems methods 
     238        self.add = self.items.add 
     239        self.getItemByName = self.items.getItemByName 
     240 
     241 
     242    def setItemParent(self, itemParent): 
     243        self.itemParent = itemParent 
     244 
     245 
     246    def process(self, ctx, form, args, errors): 
     247        for item in self.items: 
     248            item.process(ctx, form, args, errors) 
     249 
     250 
     251 
     252class GroupFragment(rend.Fragment): 
     253 
     254 
     255    docFactory = loaders.stan( 
     256            T.fieldset(id=T.slot('id'), render=T.directive('group'))[ 
     257                T.legend[T.slot('label')], 
     258                T.div(class_='description')[T.slot('description')], 
     259                T.slot('items'), 
     260                ] 
     261            ) 
     262 
     263 
     264    def __init__(self, group): 
     265        super(GroupFragment, self).__init__() 
     266        self.group = group 
     267 
     268 
     269    def render_group(self, ctx, data): 
     270        group = self.group 
     271        ctx.tag.fillSlots('id', util.render_cssid(group.name)) 
     272        ctx.tag.fillSlots('label', group.label) 
     273        ctx.tag.fillSlots('description', group.description or '') 
     274        ctx.tag.fillSlots('items', [inevow.IRenderer(item) for item in 
     275                group.items]) 
     276        return ctx.tag 
     277 
     278 
     279 
     280registerAdapter(GroupFragment, Group, inevow.IRenderer) 
     281 
     282 
     283 
    73284class Form(object): 
    74285 
     
    77288    callback = None 
    78289    actions = None 
    79     widgets = None 
    80290 
    81291    def __init__(self, callback=None): 
     
    84294        self.resourceManager = ResourceManager() 
    85295        self.data = {} 
    86         self.items = [] 
    87  
    88     def addField(self, name, type, widgetFactory=None, label=None, description=None, cssClass=None): 
    89         if not util.validIdentifier(name): 
    90             raise ValueError('%r is an invalid field name'%name) 
    91         type.name = name 
    92         if label is None: 
    93             label = util.titleFromName(name) 
    94         self.items.append( (name,type,label,description,cssClass) ) 
    95         if widgetFactory is not None: 
    96             if self.widgets is None: 
    97                 self.widgets = {} 
    98             self.widgets[name] = widgetFactory 
     296        self.items = FormItems(None) 
     297        # Forward to FormItems methods 
     298        self.add = self.items.add 
     299        self.getItemByName = self.items.getItemByName 
     300 
     301    def addField(self, name, type, widgetFactory=None, label=None, 
     302            description=None, cssClass=None): 
     303        self.add(Field(name, type, widgetFactory, label, description, cssClass)) 
    99304 
    100305    def addAction(self, callback, name="submit", validate=True, label=None): 
     
    105310        self.actions.append( Action(callback, name, validate, label) ) 
    106311 
    107     def getField(self,fieldName): 
    108         for name, type, label, description, cssClass in self.items: 
    109             if name == fieldName: 
    110                 return (name, type, label, description, cssClass) 
    111         else: 
    112             return None 
    113              
    114  
    115     def widgetForItem(self, itemName): 
    116  
    117         for name, type, label, description, cssClass in self.items: 
    118             if name == itemName: 
    119                 break 
    120         else: 
    121             raise KeyError() 
    122  
    123         if self.widgets is not None: 
    124             try: 
    125                 widgetFactory = self.widgets[name] 
    126             except KeyError: 
    127                 pass 
    128             else: 
    129                 return widgetFactory(type) 
    130  
    131         return iformal.IWidget(type) 
    132  
    133312    def process(self, ctx): 
    134313 
     
    138317        # Decode the request arg names 
    139318        charset = getPOSTCharset(ctx) 
    140         requestArgs = dict([(k.decode(charset),v) for k,v in requestArgs.iteritems()]) 
    141  
    142         # Unflatten the request into nested dicts. 
    143         args = {} 
    144         for name, value in requestArgs.iteritems(): 
    145             name = name.split('.') 
    146             group, name = name[:-1], name[-1] 
    147             d = args 
    148             for g in group: 
    149                 d = args.setdefault(g,{}) 
    150             d[name] = value 
     319        args = dict([(k.decode(charset),v) for k,v in requestArgs.iteritems()]) 
    151320 
    152321        # Find the callback to use, defaulting to the form default 
     
    170339 
    171340        # Iterate the items and collect the form data and/or errors. 
    172         data = {} 
    173         for name, type, label, description, cssClass in self.items: 
    174             try: 
    175                 if not type.immutable: 
    176                     data[name] = self.widgetForItem(name).processInput(ctx, name, args) 
    177                 else: 
    178                     data[name] = self.data.get(name) 
    179                     errors.data[name] = self.data.get(name) 
    180             except validation.FieldError, e: 
    181                 if validate: 
    182                     if e.fieldName is None: 
    183                         e.fieldName = name 
    184                     errors.add(e) 
    185  
    186         if errors: 
     341        for item in self.items: 
     342            item.process(ctx, self, args, errors) 
     343 
     344        if errors and validate: 
    187345            return errors 
    188346 
     
    192350            return r 
    193351 
    194         d = defer.maybeDeferred(callback, ctx, self, data) 
     352        d = defer.maybeDeferred(callback, ctx, self, self.data) 
    195353        d.addCallback( _clearUpResources ) 
    196354        d.addErrback(self._cbFormProcessingFailed, ctx) 
     
    203361        errors.add(failure.value) 
    204362        return errors 
     363 
     364 
     365 
     366class FormItems(object): 
     367    """ 
     368    A managed collection of form items. 
     369    """ 
     370 
     371 
     372    def __init__(self, itemParent): 
     373        self.items = [] 
     374        self.itemParent = itemParent 
     375 
     376 
     377    def __iter__(self): 
     378        return iter(self.items) 
     379 
     380 
     381    def add(self, item): 
     382        # Check the item name is unique 
     383        if item.name in [i.name for i in self.items]: 
     384            raise ValueError('Item named %r already added to %r' % 
     385                    (item.name, self)) 
     386        # Add to child items and set self the parent 
     387        self.items.append(item) 
     388        item.setItemParent(self.itemParent) 
     389 
     390 
     391    def getItemByName(self, name): 
     392        name = name.split('.', 1) 
     393        if len(name) == 1: 
     394            name, rest = name[0], None 
     395        else: 
     396            name, rest = name[0], name[1] 
     397        for item in self.items: 
     398            if item.name == name: 
     399                if rest is None: 
     400                    return item 
     401                return item.getItemByName(rest) 
     402        raise KeyError("No item called %r" % name) 
     403 
    205404 
    206405 
     
    246445    def _fileFromWidget(self, form, ctx, segments): 
    247446        ctx.remember(form, iformal.IForm) 
    248         widget = form.widgetForItem(segments[0]
     447        widget = form.getItemByName(segments[0]).makeWidget(
    249448        return widget.getResource(ctx, segments[0], segments[1:]) 
    250449 
     
    413612 
    414613    loader = loaders.stan( 
    415         T.form(id=T.slot('id'), action=T.slot('action'), class_='nevow-form', method='post', enctype='multipart/form-data', **{'accept-charset':'utf-8'})[ 
    416             T.fieldset[ 
     614            T.form(**{'id': T.slot('formId'), 'action': T.slot('formAction'), 
     615                'class': 'nevow-form', 'method': 'post', 'enctype': 
     616                'multipart/form-data', 'accept-charset': 'utf-8'})[ 
     617            T.div[ 
    417618                T.input(type='hidden', name='_charset_'), 
    418                 T.input(type='hidden', name=FORMS_KEY, value=T.slot('name')), 
    419                 T.slot('errors'), 
    420                 T.slot('items'), 
    421                 T.div(id=T.slot('fieldId'), pattern='item', _class=T.slot('class'))[ 
    422                     T.label(_class='label', _for=T.slot('id'))[T.slot('label')], 
    423                     T.div(_class='inputs')[T.slot('inputs')], 
    424                     T.slot('description'), 
    425                     T.slot('message'), 
    426                     ], 
    427                 T.div(class_='hiddenitems')[ 
    428                     T.slot('hiddenitems'), 
    429                     T.invisible(pattern="hiddenitem")[T.slot('inputs')] 
    430                     ], 
     619                T.input(type='hidden', name=FORMS_KEY, value=T.slot('formName')), 
     620                T.slot('formErrors'), 
     621                T.slot('formItems'), 
    431622                T.div(class_='actions')[ 
    432                     T.slot('actions'), 
     623                    T.slot('formActions'), 
    433624                    ], 
    434625                ], 
     
    442633    def rend(self, ctx, data): 
    443634        tag = T.invisible[self.loader.load()] 
    444         tag.fillSlots('name', self.original.name) 
    445         tag.fillSlots('id', util.keytocssid(ctx.key)) 
    446         tag.fillSlots('action', url.here) 
    447         tag.fillSlots('errors', self._renderErrors) 
    448         tag.fillSlots('items', self._renderItems) 
    449         tag.fillSlots('hiddenitems', self._renderHiddenItems) 
    450         tag.fillSlots('actions', self._renderActions) 
     635        tag.fillSlots('formName', self.original.name) 
     636        tag.fillSlots('formId', util.keytocssid(ctx.key)) 
     637        tag.fillSlots('formAction', url.here) 
     638        tag.fillSlots('formErrors', self._renderErrors) 
     639        tag.fillSlots('formItems', self._renderItems) 
     640        tag.fillSlots('formActions', self._renderActions) 
    451641        return tag 
    452642 
     
    464654        for error in errors: 
    465655            if isinstance(error, validation.FieldError): 
    466                 name, type, label, description, cssClass = self.original.getField(error.fieldName) 
    467                 errorList[ T.li[ T.strong[ label, ' : ' ], error.message ] ] 
     656                item = self.original.getItemByName(error.fieldName) 
     657                errorList[ T.li[ T.strong[ item.label, ' : ' ], error.message ] ] 
    468658        return T.div(class_='errors')[ T.p['Please correct the following errors:'], errorList ] 
    469659 
     
    472662            yield '' 
    473663            return 
    474         itemPattern = inevow.IQ(ctx).patternGenerator('item') 
    475664        for item in self.original.items: 
    476             widget = self.original.widgetForItem(item[0]) 
    477             if getattr(widget,'inputType','') != 'hidden': 
    478                 yield itemPattern(key=item[0], data=item, render=self._renderItem) 
    479  
    480     def _renderHiddenItems(self, ctx, data): 
    481         if self.original.items is None: 
    482             yield '' 
    483             return 
    484         hiddenItemPattern = inevow.IQ(ctx).patternGenerator('hiddenitem') 
    485         for item in self.original.items: 
    486             widget = self.original.widgetForItem(item[0]) 
    487             if getattr(widget,'inputType','') == 'hidden': 
    488                 yield hiddenItemPattern(key=item[0], data=item, render=self._renderHiddenItem) 
    489  
    490     def _renderItem(self, ctx, data): 
    491  
    492         def _(ctx, data): 
    493  
    494             name, type, label, description, cssClass = data 
    495             form = self.original 
    496             formErrors = iformal.IFormErrors(ctx, None) 
    497             formData = iformal.IFormData(ctx) 
    498  
    499             widget = form.widgetForItem(name) 
    500             if formErrors is None: 
    501                 error = None 
    502             else: 
    503                 error = formErrors.getFieldError(name) 
    504  
    505             # Basic classes are 'field', the type's class name and the widget's 
    506             # class name. 
    507             classes = [ 
    508                 'field', 
    509                 type.__class__.__name__.lower(), 
    510                 widget.__class__.__name__.lower(), 
    511                 ] 
    512             # Add a required class 
    513             if type.required: 
    514                 classes.append('required') 
    515             # Add a user-specified class 
    516             if cssClass: 
    517                 classes.append(cssClass) 
    518  
    519             if error is None: 
    520                 message = '' 
    521             else: 
    522                 classes.append('error') 
    523                 message = T.div(class_='message')[error.message] 
    524  
    525             ctx.tag.fillSlots('class', ' '.join(classes)) 
    526             ctx.tag.fillSlots('fieldId', '%s-field'%util.keytocssid(ctx.key)) 
    527             ctx.tag.fillSlots('id', util.keytocssid(ctx.key)) 
    528             ctx.tag.fillSlots('label', label) 
    529             if type.immutable: 
    530                 render = widget.renderImmutable 
    531             else: 
    532                 render = widget.render 
    533             ctx.tag.fillSlots('inputs', render(ctx, name, formData, formErrors)) 
    534             ctx.tag.fillSlots('message', message) 
    535             ctx.tag.fillSlots('description', T.div(class_='description')[description or '']) 
    536  
    537             return ctx.tag 
    538  
    539         return _ 
    540  
    541     def _renderHiddenItem(self, ctx, data): 
    542  
    543         def _(ctx, data): 
    544  
    545             name, type, label, description, cssClass = data 
    546             form = self.original 
    547             formErrors = iformal.IFormErrors(ctx, None) 
    548             formData = iformal.IFormData(ctx) 
    549  
    550             widget = form.widgetForItem(name) 
    551  
    552             ctx.tag.fillSlots('fieldId', '%s-field'%util.keytocssid(ctx.key)) 
    553             ctx.tag.fillSlots('id', util.keytocssid(ctx.key)) 
    554             ctx.tag.fillSlots('inputs', widget.render(ctx, name, formData, formErrors)) 
    555             return ctx.tag 
    556  
    557         return _ 
     665            yield inevow.IRenderer(item) 
    558666 
    559667    def _renderActions(self, ctx, data): 
  • trunk/formal/formal.css

    r220 r231  
    22  border: 1px solid #ccc; 
    33  padding: 5px; 
    4 } 
    5  
    6 .nevow-form fieldset { 
    7   margin: 0; 
    8   border: 0; 
    9   padding: 0; 
    104} 
    115 
  • trunk/formal/util.py

    r196 r231  
    11import re 
    22from zope.interface import implements 
    3 from nevow import inevow 
     3from nevow import inevow, tags 
    44from formal import iformal 
    55 
     
    3030 
    3131    return ''.join(_()) 
     32 
     33 
     34def render_cssid(fieldKey, *extras): 
     35    """ 
     36    Render the CSS id for the form field's key. 
     37    """ 
     38    l = [tags.slot('formName'), '-', '-'.join(fieldKey.split('.'))] 
     39    for extra in extras: 
     40        l.append('-') 
     41        l.append(extra) 
     42    return l 
    3243 
    3344 
  • trunk/formal/widget.py

    r225 r231  
    88from nevow.i18n import _ 
    99from formal import converters, iformal, validation 
    10 from formal.util import keytocssid 
     10from formal.util import render_cssid 
    1111from formal.form import widgetResourceURL, widgetResourceURLFromContext 
    1212from zope.interface import implements 
     
    3333 
    3434    def _renderTag(self, ctx, key, value, readonly): 
    35         tag=T.input(type=self.inputType, name=key, id=keytocssid(ctx.key), value=value) 
     35        tag=T.input(type=self.inputType, name=key, id=render_cssid(key), value=value) 
    3636        if readonly: 
    3737            tag(class_='readonly', readonly='readonly') 
     
    6969 
    7070    def _renderTag(self, ctx, key, value, disabled): 
    71         tag = T.input(type='checkbox', name=key, id=keytocssid(ctx.key), value='True') 
     71        tag = T.input(type='checkbox', name=key, id=render_cssid(key), value='True') 
    7272        if value == 'True': 
    7373            tag(checked='checked') 
     
    124124 
    125125    def _renderTag(self, ctx, key, value, readonly): 
    126         tag=T.textarea(name=key, id=keytocssid(ctx.key), cols=self.cols, rows=self.rows)[value or ''] 
     126        tag=T.textarea(name=key, id=render_cssid(key), cols=self.cols, rows=self.rows)[value or ''] 
    127127        if readonly: 
    128128            tag(class_='readonly', readonly='readonly') 
     
    161161            values = ('', '') 
    162162        return [ 
    163             T.input(type='password', name=key, id=keytocssid(ctx.key), value=values[0]), 
     163            T.input(type='password', name=key, id=render_cssid(key), value=values[0]), 
    164164            T.br, 
    165             T.label(for_='%s__confirm'%keytocssid(ctx.key))[' Confirm '], 
    166             T.input(type='password', name=key, id='%s__confirm'%keytocssid(ctx.key), value=values[1]), 
     165            T.label(for_=render_cssid(key, 'confirm'))[' Confirm '], 
     166            T.input(type='password', name=key, id=render_cssid(key, 'confirm'), value=values[1]), 
    167167            ] 
    168168 
     
    170170        values = ('', '') 
    171171        return [ 
    172             T.input(type='password', name=key, id=keytocssid(ctx.key), value=values[0], class_='readonly', readonly='readonly'), 
     172            T.input(type='password', name=key, id=render_cssid(key), value=values[0], class_='readonly', readonly='readonly'), 
    173173            T.br, 
    174             T.label(for_='%s__confirm'%keytocssid(ctx.key))[' Confirm '], 
    175             T.input(type='password', name=key, id='%s__confirm'%keytocssid(ctx.key), value=values[1], class_='readonly', readonly='readonly') 
     174            T.label(for_=render_cssid(key, 'confirm'))[' Confirm '], 
     175            T.input(type='password', name=key, id=render_cssid(key, 'confirm'), 
     176                    value=values[1], class_='readonly', readonly='readonly') 
    176177        ] 
    177178 
     
    249250                yield option 
    250251 
    251         tag=T.select(name=key, id=keytocssid(ctx.key), data=self.options)[renderOptions] 
     252        tag=T.select(name=key, id=render_cssid(key), data=self.options)[renderOptions] 
    252253        if disabled: 
    253254            tag(class_='disabled', disabled='disabled') 
     
    355356        tag = T.invisible[self.template.load(ctx)] 
    356357        tag.fillSlots('key', key) 
    357         tag.fillSlots('id', keytocssid(ctx.key)) 
     358        tag.fillSlots('id', render_cssid(key)) 
    358359        tag.fillSlots('options', optionTags) 
    359360        tag.fillSlots('otherValue', otherValue) 
     
    383384 
    384385        def renderOption(ctx, itemKey, itemLabel, num, selected): 
    385             cssid = (keytocssid(ctx.key),'-',num) 
     386            cssid = render_cssid(key, num) 
    386387            tag = T.input(name=key, type='radio', id=cssid, value=itemKey) 
    387388            if selected: 
     
    632633                optLabel = iformal.ILabel(item).label() 
    633634                optValue = converter.fromType(optValue) 
    634                 optid = (keytocssid(ctx.key),'-',n) 
    635                 checkbox = T.input(type='checkbox', name=key, value=optValue, id=optid ) 
     635                optid = render_cssid(key, n) 
     636                checkbox = T.input(type='checkbox', name=key, value=optValue, 
     637                        id=optid) 
    636638                if optValue in values: 
    637639                    checkbox = checkbox(checked='checked') 
     
    683685 
    684686    def _renderTag(self, ctx, key, disabled): 
    685         tag=T.input(name=key, id=keytocssid(ctx.key),type='file') 
     687        tag=T.input(name=key, id=render_cssid(key),type='file') 
    686688        if disabled: 
    687689            tag(class_='disabled', disabled='disabled') 
     
    733735 
    734736        yield T.input(name=namer('value'),value=value,type='hidden') 
    735         tag=T.input(name=key, id=keytocssid(ctx.key),type='file') 
     737        tag=T.input(name=key, id=render_cssid(key),type='file') 
    736738        if disabled: 
    737739            tag(class_='disabled', disabled='disabled') 
     
    863865            yield T.p[T.strong['Nothing uploaded']] 
    864866 
    865         yield T.input(name=key, id=keytocssid(ctx.key),type='file') 
     867        yield T.input(name=key, id=render_cssid(key),type='file') 
    866868 
    867869        # Id of uploaded file in the resource manager 
     
    983985        else: 
    984986            value = iformal.IStringConvertible(self.original).fromType(args.get(key)) 
    985         return T.input(type=self.inputType, name=key, id=keytocssid(ctx.key), value=value) 
     987        return T.input(type=self.inputType, name=key, id=render_cssid(key), value=value) 
    986988 
    987989    def renderImmutable(self, ctx, key, args, errors): 
  • trunk/formal/widgets/multiselect.py

    r196 r231  
    22from nevow import tags as T 
    33from formal import iformal 
    4 from formal.util import keytocssid 
     4from formal.util import render_cssid 
    55 
    66_UNSET = object() 
     
    7373                yield option 
    7474 
    75         tag=T.select(name=key, id=keytocssid(ctx.key), data=self.options, multiple="multiple")[renderOptions] 
     75        tag=T.select(name=key, id=render_cssid(key), data=self.options, multiple="multiple")[renderOptions] 
    7676 
    7777        if disabled: 
  • trunk/formal/widgets/restwidget.py

    r196 r231  
    55from nevow import inevow, loaders, rend, tags as T 
    66from formal import iformal, widget 
    7 from formal.util import keytocssid 
     7from formal.util import render_cssid 
    88from formal.form import widgetResourceURLFromContext 
    99 
     
    2525    def _renderTag(self, ctx, key, value, readonly): 
    2626        tag=T.invisible() 
    27         ta=T.textarea(name=key, id=keytocssid(ctx.key), cols=self.cols, rows=self.rows)[value or ''] 
     27        ta=T.textarea(name=key, id=render_cssid(key), cols=self.cols, rows=self.rows)[value or ''] 
    2828        if readonly: 
    2929            ta(class_='readonly', readonly='readonly') 
     
    3737            else: 
    3838                form = iformal.IForm( ctx ) 
    39                 srcId = keytocssid(ctx.key) 
    40                 previewDiv = srcId + '-preview-div' 
    41                 frameId = srcId + '-preview-frame' 
     39                srcId = render_cssid(key) 
     40                previewDiv = render_cssid(key, 'preview-div') 
     41                frameId = render_cssid(key, 'preview-frame') 
    4242                targetURL = widgetResourceURLFromContext(ctx, form.name).child(key).child( srcId ) 
    4343                tag[T.br()] 
    44                 tag[T.button(onClick="return Forms.Util.previewShow('%s', '%s', '%s');"%(previewDiv, frameId, targetURL))['Preview ...']] 
     44                onclick = ["return Forms.Util.previewShow('",previewDiv, "', '", 
     45                        frameId, "', '", targetURL, "');"] 
     46                tag[T.button(onclick=onclick)['Preview ...']] 
    4547                tag[T.div(id=previewDiv, class_="preview-hidden")[ 
    4648                        T.iframe(class_="preview-frame", name=frameId, id=frameId), 
    4749                        T.br(), 
    48                         T.button(onClick="return Forms.Util.previewHide('%s');"%(previewDiv))['Close'] 
     50                        T.button(onclick=["return Forms.Util.previewHide('", previewDiv, "');"])['Close'] 
    4951                    ] 
    5052                ] 
  • trunk/setup.py

    r230 r231  
    33setup( 
    44    name='formal', 
    5     version='0.8.2', 
     5    version='0.9', 
    66    description='HTML forms framework for Nevow', 
    77    author='Matt Goodall',