TurboGears widgets first look

5 views
Skip to first unread message

Kevin Dangoor

unread,
Nov 10, 2005, 4:01:43 PM11/10/05
to turbo...@googlegroups.com
As I mentioned previously (in "TurboGears forms first look":
http://tinyurl.com/c28c7), turbogears.forms has the concept of
"widgets" that are put together to create a form. In fact, widgets are
so important to how forms are put together that I've renamed the
package turbogears.widgets. Form objects are, in fact, widgets (and
now live in turbogears.widgets.forms).

You'll remember from the "first look" that my opinion is that doing
on-the-fly generation of the form has some significant advantages over
straight code generation. By using widgets all over the place, you can
customize the look and behavior of the widget and have that change
apply everywhere.

I know that there are times when you need complete 100% control over
how things look on the web. Even though you can extensively customize
widgets, there will be circumstances where you need to go even
further. For this reason, widgets also support output of Kid for the
purposes of customization. You'll be able to take that output and drop
it directly into your template so that you can modify however you need
to.

What I'm going to talk about in the following sections is all up for
discussion. Now that there's actual code, people can play with it and
decide what they like and don't like. In truth, I expect that there
will be a lot more discussion after the release of 0.9 and that's
fine. We'll clean up the rough edges between 0.9 and 1.0.

Using Widgets
-------------

Here's an example from the "first look":
widgets.TextField("age", default=0, validator=validators.Int())

The first parameter is the name that will be given to the field on the
form. You can set a default value, and you can also change the
validator used by the widget.

The role of validators here is not just validation: the FormEncode
validator interface provides from_python and to_python conversion. In
the simple case above, this means that the value coming in from the
web is converted to an int on its way in.

Widgets have two similar methods for using them: insert and render. In
practice, insert is the one that is used most often. The difference
between them is that render returns HTML (or whatever text form you're
expecting) and insert returns a Element generator, which is convenient
for inclusion in a surrounding Kid template.

Insert (and render) both take a "value" parameter. The basic idea is
something like this:

<div py:replace="agefield.insert(age)"/>

And, that is roughly what a Form object will do when it is insert-ed
into your page.

The validator is important because you can have something like a
select widget that needs to translate between IDs and names. Or, you
could have a repeating form element widget that takes in a collection
of data as its value.

Customizing Widgets
-------------------

The easiest customization you can do is to include a stylesheet that
implements the classes used by the widgets. All of the standard
widgets should use CSS classes extensively and consistently to make
them easy to customize.

Beyond that, you can replace the look of a widget without changing its
behavior very easily. Let's say you wanted the age field in the
example above to say "years old" after the field. (This is a goofy
example, but run with me for a second here...)

widgets.TextField("age", default=0, validator=validators.Int(),
template="myproject.templates.agewidget"

And agewidget.kid could look like this:
<div xmlns:py="http://purl.org/kid/ns#"><input type="text"
name="${widget.name}" value="${widget_value}"/> years old</div>

That's not hard. How do you know what to put in that template? The
TurboGears Toolbox will help with that, but this message isn't about
the Toolbox :)

Sometimes just changing the generated markup isn't enough and you need
to change the behavior of the widget. Widgets are standard Python, so
subclassing will let you customize further.

Writing Widgets
---------------
I'll go for what is currently the most complex widget to show the
parts of writing a widget:

class CalendarDatePicker(Widget):
css=[CSSLink(static, "calendar-system.css")]
javascript=[JSLink(static, "calendar.js"),
JSLink(static, "lang/calendar-en.js"),
JSLink(static, "calendar-setup.js")]
template = """<div><input type="text" id="${widget.name}"
name="${widget.name}" value="${widget_value}"/>
<button id="${widget.name}-trigger">Choose</button>
<script type="text/javascript">
Calendar.setup(
{
inputField : "${widget.name}",
ifFormat : "%m/%d/%Y",
button : "${widget.name}-trigger"
}
);
</script></div>
"""
validator = validators.DateConverter()
_default = None
sampleValue = datetime.now()

def __init__(self, name=None, default=None, **kw):
if default is not None:
self._default = default
super(CalendarDatePicker, self).__init__(name, **kw)

def _get_default(self):
if self._default is None:
return datetime.now()
return self._default

default = property(_get_default)

def source(self):
return self.render("${%s.strftime('%%m/%%d/%%Y')}" % self.name,
format="xml", convert=False)

All widgets subclass turbogears.widgets.Widget.

Widgets can specify that they need their own CSS and JavaScript. There
are CSSLink, JSLink, CSSSource and JSSource objects to specify these.
The Link and CSSSource objects automatically appear in the <head>. The
JSSource ones can appear in the <head>, just inside the <body> or at
the bottom of the <body>. At the moment, those insertions are
happening in master.kid, but I think it needs to move to sitetemplate.

These are only included once. If you have 5 calendar widgets on a
page, only 1 set of CSS and JavaScript references will appear at the
top.

There is a function called "register_static_directory" in
turbogears.widgets. The way this is generally used is (at the module
level, in your widget module):

register_static_directory(__name__,
pkg_resources.resource_filename(__name__, "static"))

This will register widget statics for your module assuming that they
appear in a directory called static underneath your module's
directory. The "static" parameter that you see in the CSSLinks above
is just set like this in turbogears.widgets:

static = __name__

This widget shows off the use of a different default validator, so
that incoming values automatically become standard Python datetime
objects. It also changes the default (if you don't specify one) to be
datetime.now() by making default into a property.

The source method is used for generating Kid output that can be used
for the "code generation" style of working.

You can also see the sampleValue that is set. This is used when
generating a visual sample that appears in the TurboGears Toolbox
(which is not what this message is about :).

So, how flexible is a widget?

- the template can look like whatever you want (and can be inline or
specified via the standard "full.package.reference")
- it can require its own CSS
- it can have its own JavaScript
- the validator can be changed to handle different to/from_python behavior
- you can override create_dict to alter how the dictionary that is
passed to the template is derived
- you can change the default default value
- you can change how the visual sample is created
- you can alter the default label text used on forms

Overall, pretty flexible. I'm sure people will find ways in which it's
not flexible enough... but, this should meet a bunch of needs.

Current Status
--------------

We need widgets, and the current ones need to be fleshed out. The
Label widget is probably the only one that's complete. Take a look at
turbogears/widgets/__init__.py if you'd like to help out with that.

Work needs to be done on TurboGears Toolbox.

A function for instantiating widgets from an SQLObject needs to be created.

etc. etc.

But, I wanted to get a first look out there. I think this is ready for
people to start playing with and trying out (and certainly commenting
on!).
--
Kevin Dangoor
Author of the Zesty News RSS newsreader

email: k...@blazingthings.com
company: http://www.BlazingThings.com
blog: http://www.BlueSkyOnMars.com

pierrebai

unread,
Nov 10, 2005, 5:08:44 PM11/10/05
to TurboGears
The main thing I see missing is the ability to specify the class of the
XHTML element so that a widget (and a widget template.kid) can be
reused and have its look customized using CSS.

Michele Cella

unread,
Nov 10, 2005, 6:54:41 PM11/10/05
to TurboGears
Kevin Dangoor wrote:
> As I mentioned previously (in "TurboGears forms first look":
> http://tinyurl.com/c28c7), turbogears.forms has the concept of
> "widgets" that are put together to create a form. In fact, widgets are
> so important to how forms are put together that I've renamed the
> package turbogears.widgets. Form objects are, in fact, widgets (and
> now live in turbogears.widgets.forms).

Yes, renaming the package in widgets is the right thing IMO since
widget will be (are) first class component for TG.

>
> I know that there are times when you need complete 100% control over
> how things look on the web. Even though you can extensively customize
> widgets, there will be circumstances where you need to go even
> further. For this reason, widgets also support output of Kid for the
> purposes of customization. You'll be able to take that output and drop
> it directly into your template so that you can modify however you need
> to.
>

Great!

> ...


>
> Customizing Widgets
> -------------------
>
> The easiest customization you can do is to include a stylesheet that
> implements the classes used by the widgets. All of the standard
> widgets should use CSS classes extensively and consistently to make
> them easy to customize.

Is there an easy way to customize classes names used by widgets without
specifying a different template? maybe a dictionary? I'm sure I'm
missing something here (excuse me in this case)

>
> That's not hard. How do you know what to put in that template? The
> TurboGears Toolbox will help with that, but this message isn't about
> the Toolbox :)

The Toolbox looks cool indeed! Can't wait to see it in action.

>
> Sometimes just changing the generated markup isn't enough and you need
> to change the behavior of the widget. Widgets are standard Python, so
> subclassing will let you customize further.

Brilliant!

>
> Writing Widgets
> ---------------
> I'll go for what is currently the most complex widget to show the
> parts of writing a widget:
>
> class CalendarDatePicker(Widget):
> css=[CSSLink(static, "calendar-system.css")]
> javascript=[JSLink(static, "calendar.js"),
> JSLink(static, "lang/calendar-en.js"),
> JSLink(static, "calendar-setup.js")]

Regarding the calendar widget, I've seen you have put all its
javascripts under the static directory, maybe (since there are many
things there and it will probably grow) it's better to put them on
static/calendar/ (easier to maintain/update), the same goes for others
widgets.

>
> But, I wanted to get a first look out there. I think this is ready for
> people to start playing with and trying out (and certainly commenting
> on!).

Great work IMHO, it's clear that you have put much thought (and time)
on this problem.
0.9 will simply rock!

Ciao
Michele

Levi

unread,
Nov 11, 2005, 3:03:56 AM11/11/05
to TurboGears

Kevin Dangoor wrote:
>
> Using Widgets
> -------------
>
> Here's an example from the "first look":
> widgets.TextField("age", default=0, validator=validators.Int())

This might seem strange, but first thing that jumps out about this
example is the "style" doesn't feel right. Or at least it doesn't feel
consistent with sqlobject. Maybe something more like this:

class MyForm (widgets.Form):
age = widgets.TextField(
default=0,
validators=(
validators.Int(min=4, max=12),
validators.Required())

I still need to read about formencode, so I'm not sure if my list of
validators even makes sense. Now that I think of it, composition (ala
composite pattern) would probably beat lists for testability anyway.

> Widgets have two similar methods for using them: insert and render. In
> practice, insert is the one that is used most often. The difference
> between them is that render returns HTML (or whatever text form you're
> expecting) and insert returns a Element generator, which is convenient
> for inclusion in a surrounding Kid template.

Smart! I like it.

> The validator is important because you can have something like a
> select widget that needs to translate between IDs and names. Or, you
> could have a repeating form element widget that takes in a collection
> of data as its value.

Hmm. I really need to go read about formencode.... (going now :)

Ronald Jaramillo

unread,
Nov 11, 2005, 8:58:33 AM11/11/05
to turbo...@googlegroups.com
This is really yummy! I play a bit with it yesterday (http://www.checkandshare.com/blog/?p=24) and have a couple of questions.

Toolbox:
In toolbox-start cherrypy.config is set to  "server.logToScreen":True, but (at least in my setup) nothing is sent to the console. How do I turn this on?

CSS:
I couldn't get my style sheet over in my test page ( css=[CSSLink(static, "grid.css")] ) . I ended up adding the link to the css in my widget template.

Validation:
- What about client side validation (for example javascript when the widgets loose focus)
Should this be implemented on a per widget basis?

Cheers
Ronald


Henriksvej 15

2400 København NV

+45 22 27 85 11

ron...@manoamano.dk


Kevin Dangoor

unread,
Nov 11, 2005, 9:53:02 AM11/11/05
to turbo...@googlegroups.com
Good idea (and Michele agreed with you as well). I've opened a ticket
to do this.

Kevin

Kevin Dangoor

unread,
Nov 11, 2005, 9:54:24 AM11/11/05
to turbo...@googlegroups.com
On 11/10/05, Michele Cella <michel...@gmail.com> wrote:
> > class CalendarDatePicker(Widget):
> > css=[CSSLink(static, "calendar-system.css")]
> > javascript=[JSLink(static, "calendar.js"),
> > JSLink(static, "lang/calendar-en.js"),
> > JSLink(static, "calendar-setup.js")]
>
> Regarding the calendar widget, I've seen you have put all its
> javascripts under the static directory, maybe (since there are many
> things there and it will probably grow) it's better to put them on
> static/calendar/ (easier to maintain/update), the same goes for others
> widgets.

You're right, that's a big package and should have its own subdirectory.

> Great work IMHO, it's clear that you have put much thought (and time)
> on this problem.
> 0.9 will simply rock!

Thanks! And I certainly hope 0.9 rocks!

Kevin

Kevin Dangoor

unread,
Nov 11, 2005, 10:08:19 AM11/11/05
to turbo...@googlegroups.com
On 11/11/05, Levi <levi...@gmail.com> wrote:
> Kevin Dangoor wrote:
> >
> > Using Widgets
> > -------------
> >
> > Here's an example from the "first look":
> > widgets.TextField("age", default=0, validator=validators.Int())
>
> This might seem strange, but first thing that jumps out about this
> example is the "style" doesn't feel right. Or at least it doesn't feel
> consistent with sqlobject. Maybe something more like this:
>
> class MyForm (widgets.Form):
> age = widgets.TextField(
> default=0,
> validators=(
> validators.Int(min=4, max=12),
> validators.Required())

The example that you've put together here actually makes me think even
more that it should be the way it is now. Here's what I mean:

Form elements have "names". This is not for Python's sake: this is
part of the HTML that needs to be generated...

<input type="text" name="${name}">

My example didn't really have to have a form. You could take that
widget and plunk it straight into your Kid, if you wished.

Now, the declarative style, through a metaclass, *could* set the name
on the widgets and keep track of ordering. But, is it really that much
better than

form = TableForm(TextField('age'), TextField('name'))

And, I really like the fact that I have an instance here and not a
class. I still think that the form I've defined is a *thing* not a
*kind of thing*, which implies an instance and not a class.

Unfortunately, one way is not provably better than the other (emacs
vs. vi, anyone?). If people really prefer the declarative style, it
can certainly be made to work. It just feels less like "normal" OO
code to me.

(By the way, the way you define SQLObjects *is* normal OO, because in
those cases you are defining classes and not instances. FormEncode's
schemas are where the line gets blurred.)

> I still need to read about formencode, so I'm not sure if my list of
> validators even makes sense. Now that I think of it, composition (ala
> composite pattern) would probably beat lists for testability anyway.

FormEncode does provide ways to combine validators.

> > The validator is important because you can have something like a
> > select widget that needs to translate between IDs and names. Or, you
> > could have a repeating form element widget that takes in a collection
> > of data as its value.
>
> Hmm. I really need to go read about formencode.... (going now :)

It's a nice package... good thing to build on.

Kevin

Kevin Dangoor

unread,
Nov 11, 2005, 10:14:19 AM11/11/05
to turbo...@googlegroups.com
On 11/11/05, Ronald Jaramillo <ron...@manoamano.dk> wrote:
> Toolbox:
> In toolbox-start cherrypy.config is set to "server.logToScreen":True, but (at least in my setup) nothing is sent to the console. How do I turn this on?

Hmm... works fine for me. If you start via tg-admin toolbox, logging
is off... if you start via that script, logging should be on.

> CSS:
> I couldn't get my style sheet over in my test page ( css=[CSSLink(static, "grid.css")] ) . I ended up adding the link to the css in my widget template.

Take a look at the master.kid template in quickstart... you'll see how
those links get dropped in. This is the part that I think needs to
move to sitetemplate.

> Validation:
> - What about client side validation (for example javascript when the widgets loose focus)
> Should this be implemented on a per widget basis?

This is a tricky one. What would be *really* cool is to have
JavaScript associated with the FormEncode validators. So, if you put
an Int validator on a field, you could get client side validation
(with appropriate messages) for free. We can probably build up a set
of these in turbogears.validators.

I'm not sure if I want to hold up 0.9 for client side validation, but
if someone wants to add it, I'd certainly support that!

Kevin

Michele Cella

unread,
Nov 11, 2005, 10:45:02 AM11/11/05
to TurboGears
Kevin Dangoor wrote:
>
> The example that you've put together here actually makes me think even
> more that it should be the way it is now. Here's what I mean:
>
> ...

>
> And, I really like the fact that I have an instance here and not a
> class. I still think that the form I've defined is a *thing* not a
> *kind of thing*, which implies an instance and not a class.
>
> Unfortunately, one way is not provably better than the other (emacs
> vs. vi, anyone?). If people really prefer the declarative style, it
> can certainly be made to work. It just feels less like "normal" OO
> code to me.
>
> (By the way, the way you define SQLObjects *is* normal OO, because in
> those cases you are defining classes and not instances. FormEncode's
> schemas are where the line gets blurred.)
>

I totally agree (also on the FormEncode way of doing this).
I don't see any need to put the form you are constructing into another
class definition that needs to be instanced later, there is already the
FormTable widgets class that act as a constructor of the instance you
are building and want to use.
But my vision can clearly be wrong, as you said is just like vim vs
emacs.

Ciao
Michele

Levi

unread,
Nov 11, 2005, 12:28:56 PM11/11/05
to TurboGears
Interesting points. I'll have chew on them. In my mind, forms are
entities, just like tables are entities. Each carries it's own
attributes (ie: form elements, template, etc.) and invariants (ie: form
level validations).

Kevin Dangoor

unread,
Nov 12, 2005, 8:30:25 AM11/12/05
to turbo...@googlegroups.com
Consider this example:

class Book(SQLObject):
title = StringCol()
author = StringCol()

That statement doesn't reflect *a* book. That statement describes what
books are all like.

twocities = Book(title="A Tale of Two Cities", author="Charles Dikkens")

This statement describes a single book, by the well-known Dutch author
Charles Dikkens. :)

class SomeForm(TableForm):
age = TextField()
name = TextField()
city = TextField()

In standard OO terminology, that statement doesn't describe a form. It
describes a *kind* of form.

someform = TableForm(TextField('age'))

describes a particular form.

Aside from that, the mechanism required to preserve ordering seems
smelly. Python keeps class and instance attributes in dictionaries,
which are unordered. To maintain the ordering, if I understand Ian's
suggestion well enough, the form metaclass would need to initialize a
counter that each widget will increment as it's instantiated. (Or some
similar mechanism, like having each widget added to a list behind the
scenes as it's instantiated.) That's not pleasant, to me.

Of course, behind the scenes implementation details for the framework
don't always have to be the best if it results in a better API, but
I'm not completely sure that's the case.


On 11/11/05, Levi <levi...@gmail.com> wrote:
>

Levi

unread,
Nov 12, 2005, 10:47:20 AM11/12/05
to TurboGears
You don't have to waste a whole lot of time on this thread. I
appreciate your replies and find the issue interesting, but realize the
tg community would rather see you hammering out widgets and books :)

>From a modeling perspective, I believe it's valid (but pedantic) to say
"a login form is a type of form". In fact, I think tg could pretty
safely provide a LoginForm (extends TableForm) widget. A default
template would be vanilla w/ lots-o css hooks, be bound to tg_user and
tg_role tables, etc. The template could handle ordering, etc. etc..
(heck, @expose could even pick up 'required_role' and 'login_url' key
words).

>From an implementation perspective we (the oopers of the world) have
learned that composition/instance based design is waaaay more powerful
(less rigid really) than inheritance. For that reason alone, I'm
completely on board with your implementation. Beyond that, I'm into
your implementation because it's intelligent, working code :)

If determining the type -v- instance mattered, I would have to note
that forms don't appear to be stateful. And (I can't believe I'm saying
this, dimiss it immediately!) that it could be implemented as a
singleton. Of course, all of my comments are makeing me feel like I'm
becoming an "architect astronaut"
(http://www.joelonsoftware.com/articles/fog0000000018.html).

Actually, I was a little dismissive of this thread when I sat down to
write. In truth, I'm glad it's not being trivialized. Style and design
are ___realllllly___ important. TG's base products (SQLObject,
MochiKet, et al.) are perfect examples of thoughful design. So, thanks
for thinking out loud.

-- Levi

Kevin Dangoor

unread,
Nov 12, 2005, 11:03:39 AM11/12/05
to turbo...@googlegroups.com
On 11/12/05, Levi <levi...@gmail.com> wrote:
>
> You don't have to waste a whole lot of time on this thread. I
> appreciate your replies and find the issue interesting, but realize the
> tg community would rather see you hammering out widgets and books :)

As you mention at the end of your email, these little details can make
the difference between a framework that works but annoys you every
time you use it, and one that you love using.

> >From a modeling perspective, I believe it's valid (but pedantic) to say
> "a login form is a type of form". In fact, I think tg could pretty
> safely provide a LoginForm (extends TableForm) widget. A default
> template would be vanilla w/ lots-o css hooks, be bound to tg_user and
> tg_role tables, etc. The template could handle ordering, etc. etc..
> (heck, @expose could even pick up 'required_role' and 'login_url' key
> words).

Hmm... This is an interesting point. Though, as you said, forms are
stateless. you could just as easily do:

login_form = TableForm(TextField(tg_user), PasswordField(tg_password))

or some such.

> Actually, I was a little dismissive of this thread when I sat down to
> write. In truth, I'm glad it's not being trivialized. Style and design
> are ___realllllly___ important. TG's base products (SQLObject,
> MochiKet, et al.) are perfect examples of thoughful design. So, thanks
> for thinking out loud.

I agree that this type of conversation is important to have. The
difficulty is that there's generally no provably right answer. If
someone shows me a use case or two that is clearly better one way or
the other, that will help. For now, this is the one that "feels" the
most straightforward to me.

Kevin

Ian Bicking

unread,
Nov 12, 2005, 12:45:05 PM11/12/05
to turbo...@googlegroups.com
Levi wrote:
> If determining the type -v- instance mattered, I would have to note
> that forms don't appear to be stateful. And (I can't believe I'm saying
> this, dimiss it immediately!) that it could be implemented as a
> singleton. Of course, all of my comments are makeing me feel like I'm
> becoming an "architect astronaut"
> (http://www.joelonsoftware.com/articles/fog0000000018.html).

Singletons in Python are usually just module-level globals. Unlike some
languages (Java specifically) you can use any kind of object at the
module level, including instances, and you don't (and shouldn't) need to
worry about what class that instance uses. FormEncode is a little
unusual, though, because it's using an almost prototype-OO pattern.

--
Ian Bicking | ia...@colorstudy.com | http://blog.ianbicking.org

Ian Bicking

unread,
Nov 12, 2005, 12:46:43 PM11/12/05
to turbo...@googlegroups.com
Kevin Dangoor wrote:
> Consider this example:
>
> class Book(SQLObject):
> title = StringCol()
> author = StringCol()
>
> That statement doesn't reflect *a* book. That statement describes what
> books are all like.
>
> twocities = Book(title="A Tale of Two Cities", author="Charles Dikkens")
>
> This statement describes a single book, by the well-known Dutch author
> Charles Dikkens. :)
>
> class SomeForm(TableForm):
> age = TextField()
> name = TextField()
> city = TextField()
>
> In standard OO terminology, that statement doesn't describe a form. It
> describes a *kind* of form.

The converse could be argued just as effectively. This is a form:

'''<form action="foo" method="POST">
<table>...'''

SomeForm isn't a form, it is something that can build forms.

> someform = TableForm(TextField('age'))
>
> describes a particular form.

If it was just a form, then "someform" would be a string. Instead it is
something that can build forms. But in the end, I don't think OO means
anything one way or the other, it's just a question of what metaphors
you want to use. After all, you could argue that Book should be an
object, since it is just a specific Table. In fact, if you were making
a more relationally-focused ORM that would be a very probable choice.
Really these are just aesthetic choices. Given the three relations of
subclass, instance, and contained-by, how you map a problem to them is
largely intuitive.
Reply all
Reply to author
Forward
0 new messages