durusmail: quixote-users: Patch: add html_attrs keyword arg to widget classes
Generalizing form/widget API a bit
2003-11-25
2003-11-25
2003-11-25
2003-11-25
2003-11-25
2003-11-26
2003-11-26
2003-11-26
2003-11-26
2003-11-26
2003-11-26
2003-11-26
2003-11-26
2003-11-26
2003-11-26
2003-11-26
2003-11-29
2003-11-30
2003-11-26
2003-11-26
2003-11-26
Patch: add html_attrs keyword arg to widget classes
2003-11-30
Patch: add html_attrs keyword arg to widget classes
2003-12-01
Patch: add html_attrs keyword arg to widget classes
2003-12-01
Patch: add html_attrs keyword arg to widget classes
2003-12-01
2003-12-02
2003-12-03
2003-12-02
Patch: add html_attrs keyword arg to widget classes
2003-12-01
Patch: add html_attrs keyword arg to widget classes
2003-12-01
Patch: add html_attrs keyword arg to widget classes
Neil Schemenauer
2003-12-01
Here's another proposed patch.  The basis is:

 class Widget:
[...]
+    def __init__(self, name, value=None, attrs=None, **kwattrs):
         assert self.__class__ is not Widget, "abstract class"
         self.name = name
         self.error = None
+        self.attrs = merge_attrs(attrs, kwattrs)
         request = get_request()
         if request.form:
             self._parse(request)

So, you can either pass keywords or pass a dictionary.  I think we
have concluded that a dictionary is required.  I've debated for
quite a while whether to support keywords.  Although using a
dictionary is only a little more typing I'ved concluded that they
should be supported as well.  For widgets like 'text' and 'string',
you almost always want to provide some attributes.  Also, the
merging behavor of having both is handy to have.  For example,  the
multiple select widget constructor can be:

   def __init__(self, name, value=None, options=None, **kwargs):
       SingleSelectWidget.__init__(self, name, value, options,
                                   multiple='multiple', **kwargs)

and some funky code can be removed from SelectWidget.render().

  practicality-beats-purity-ly y'rs  Neil


Index: widget.py
===================================================================
--- widget.py   (revision 23231)
+++ widget.py   (working copy)
@@ -24,6 +24,22 @@
     return "%s$%s" % (prefix, name)


+def merge_attrs(base, overrides):
+    """({string: any}, {string: any}) -> {string: any}
+    """
+    items = []
+    if base:
+        items.extend(base.items())
+    if overrides:
+        items.extend(overrides.items())
+    attrs = {}
+    for name, val in items:
+        if name.endswith('_'):
+            name = name[:-1]
+        attrs[name] = val
+    return attrs
+
+
 class Widget:
     """Abstract base class for web widgets.

@@ -31,15 +47,17 @@
       name : string
       value : any
       error : string
+      attrs : {string: any}

     Feel free to access these directly; to set them, use the 'set_*()'
     modifier methods.
     """

-    def __init__(self, name, value=None):
+    def __init__(self, name, value=None, attrs=None, **kwattrs):
         assert self.__class__ is not Widget, "abstract class"
         self.name = name
         self.error = None
+        self.attrs = merge_attrs(attrs, kwattrs)
         request = get_request()
         if request.form:
             self._parse(request)
@@ -106,27 +124,17 @@

     Instance attributes:
       value : string
-      size : int
-      maxlength : int
     """

     # This lets PasswordWidget be a trivial subclass
     HTML_TYPE = "text"

-    def __init__(self, name, value=None,
-                 size=None, maxlength=None):
-        Widget.__init__(self, name, value)
-        self.size = size
-        self.maxlength = maxlength
-
-    def render(self, **attributes):
+    def render(self):
         return htmltag("input", xml_end=True,
                        type=self.HTML_TYPE,
                        name=self.name,
-                       size=self.size,
-                       maxlength=self.maxlength,
                        value=self.value,
-                       **attributes)
+                       **self.attrs)


 class FileWidget(StringWidget):
@@ -161,28 +169,15 @@

     Instance attributes:
       value : string
-      cols : int
-      rows : int
-      wrap : string
-        (see an HTML book for details on text widget wrap options)
     """

-    def __init__(self, name, value=None, cols=None, rows=None, wrap=None):
-        Widget.__init__(self, name, value)
-        self.cols = cols
-        self.rows = rows
-        self.wrap = wrap
-
     def _parse(self, request):
         Widget._parse(self, request)
         if self.value and self.value.find("\r\n") >= 0:
             self.value = self.value.replace("\r\n", "\n")

     def render(self):
-        return (htmltag("textarea", name=self.name,
-                        cols=self.cols,
-                        rows=self.rows,
-                        wrap=self.wrap) +
+        return (htmltag("textarea", name=self.name, **self.attrs) +
                 htmlescape(self.value or "") +
                 htmltext(""))

@@ -204,7 +199,8 @@
                        type="checkbox",
                        name=self.name,
                        value="yes",
-                       checked=self.value and ValuelessAttr or None)
+                       checked=self.value and ValuelessAttr or None,
+                       **self.attrs)



@@ -219,23 +215,17 @@
       options : [ (value:any, description:any, key:string) ]
       value : any
         The value is None or an element of dict(options.values()).
-      size : int
-        The number of options that should be presented without scrolling.
     """

-    def __init__(self, name, value=None,
-                 options=None,
-                 size=None,
-                 sort=True,
-                 verify_selection=True):
+    def __init__(self, name, value=None, options=None, sort=True,
+                 verify_selection=True, attrs=None, **kwattrs):
         assert self.__class__ is not SelectWidget, "abstract class"
         self.options = []
         if options is not None:
             assert options, 'cannot pass empty options list'
             self.set_options(options, sort)
         self.verify_selection = verify_selection
-        self.size = size
-        Widget.__init__(self, name, value)
+        Widget.__init__(self, name, value, attrs=attrs, **kwattrs)

     def get_allowed_values(self):
         return [item[0] for item in self.options]
@@ -366,17 +356,7 @@
         return value == self.value

     def render(self):
-        if self.SELECT_TYPE == "multiple_select":
-            multiple = ValuelessAttr
-        else:
-            multiple = None
-        if self.SELECT_TYPE == "option_select":
-            onchange = "submit()"
-        else:
-            onchange = None
-        tags = [htmltag("select", name=self.name,
-                        multiple=multiple, onchange=onchange,
-                        size=self.size)]
+        tags = [htmltag("select", name=self.name, **self.attrs)]
         for object, description, key in self.options:
             if self.is_selected(object):
                 selected = ValuelessAttr
@@ -422,8 +402,9 @@

     def __init__(self, name, value=None,
                  options=None,
-                 delim=None):
-        SingleSelectWidget.__init__(self, name, value, options)
+                 delim=None, attrs=None, **kwattrs):
+        SingleSelectWidget.__init__(self, name, value, options,
+                                    attrs=attrs, **kwattrs)
         if delim is None:
             self.delim = "\n"
         else:
@@ -440,7 +421,8 @@
                         type="radio",
                         name=self.name,
                         value=key,
-                        checked=checked)
+                        checked=checked,
+                        **self.attrs)
             tags.append(r + htmlescape(description) + htmltext(''))
         return htmlescape(self.delim).join(tags)

@@ -456,6 +438,10 @@

     SELECT_TYPE = "multiple_select"

+    def __init__(self, name, value=None, options=None, **kwargs):
+        SingleSelectWidget.__init__(self, name, value, options,
+                                    multiple='multiple', **kwargs)
+
     def set_value(self, value):
         allowed_values = self.get_allowed_values()
         if value in allowed_values:
@@ -495,9 +481,10 @@

     HTML_TYPE = "button"

-    def __init__(self, name, value=None):
+    def __init__(self, name, value=None, attrs=None, **kwattrs):
         self.name = name
         self.error = None
+        self.attrs = merge_attrs(attrs, kwattrs)
         # slightly different behavior here, we always render the
         # tag using the 'value' passed in as a parameter.  The 'value'
         # attribute is a boolean that is true if the button's name appears
@@ -546,7 +533,8 @@
         return htmltag("input", xml_end=True,
                        type="hidden",
                        name=self.name,
-                       value=value)
+                       value=value,
+                       **self.attrs)


 # -- Derived widget types ----------------------------------------------
@@ -564,15 +552,13 @@
     TYPE_ERROR = None                   # human-readable error message
     TYPE_CONVERTER = None               # eg. int(), float()

-    def __init__(self, name,
-                 value=None,
-                 size=None, maxlength=None):
+    def __init__(self, name, value=None, attrs=None, **kwattrs)
         assert self.__class__ is not NumberWidget, "abstract class"
         assert value is None or type(value) is self.TYPE_OBJECT, (
             "form value '%s' not a %s: got %r" % (name,
                                                   self.TYPE_OBJECT,
                                                   value))
-        StringWidget.__init__(self, name, value, size, maxlength)
+        StringWidget.__init__(self, name, value, attrs=attrs, **kwattrs)

     def _parse(self, request):
         StringWidget._parse(self, request)
@@ -616,6 +602,10 @@

     SELECT_TYPE = "option_select"

+    def __init__(self, name, value=None, options=None, **kwargs):
+        SingleSelectWidget.__init__(self, name, value, options,
+                                    onchange='submit()', **kwargs)
+
     def render(self):
         return (SingleSelectWidget.render(self) +
                 htmltext('
reply