[Checkins] SVN: z3c.formdemo/trunk/src/z3c/formdemo/ - Moved some generally useful table stuff to formdemo browser package.

Stephan Richter srichter at cosmos.phy.tufts.edu
Thu Jun 21 22:20:21 EDT 2007


Log message for revision 76933:
  - Moved some generally useful table stuff to formdemo browser package.
  
  - Developed a new and pretty complex demo.
  
  

Changed:
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/__init__.py
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/address.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.css
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addresses.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/browser.py
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/configure.zcml
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.py
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.py
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/email.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/emails.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/form-macros.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/interfaces.py
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phone.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phones.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/addressbook/text-shadow.js
  A   z3c.formdemo/trunk/src/z3c/formdemo/browser/formatter.py
  U   z3c.formdemo/trunk/src/z3c/formdemo/browser/index.pt
  A   z3c.formdemo/trunk/src/z3c/formdemo/browser/table_sorted_header.pt
  U   z3c.formdemo/trunk/src/z3c/formdemo/configure.zcml
  D   z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/formatter.py
  U   z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/spreadsheet.py
  D   z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/table_sorted_header.pt

-=-
Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/__init__.py
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/__init__.py	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/__init__.py	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/address.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/address.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/address.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,20 @@
+<div metal:use-macro="macro:form-header" />
+
+<div class="row">
+  <div class="inline" tal:define="widget view/widgets/street">
+    <div metal:use-macro="macro:widget-noerror-row" />
+  </div>
+</div>
+<div class="row">
+  <div class="inline" tal:define="widget view/widgets/city">
+    <div metal:use-macro="macro:widget-noerror-row" />
+  </div>
+  <div class="inline" tal:define="widget view/widgets/state">
+    <div metal:use-macro="macro:widget-noerror-row" />
+  </div>
+  <div class="inline" tal:define="widget view/widgets/zip">
+    <div metal:use-macro="macro:widget-noerror-row" />
+  </div>
+</div>
+
+<div metal:use-macro="macro:form-buttons" />


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/address.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.css
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.css	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.css	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,211 @@
+h1 {
+  background-color: #619FDF;
+  border: 1px solid #3B81C8;
+  color: white;
+  padding: 5px 7px 2px 7px;
+  font-weight: bold;
+  margin-bottom: 20px;
+  -moz-border-radius: 3px;
+}
+
+h1 span {
+  text-shadow: 1px 1px 1px black;
+}
+
+h2 {
+  border-top: 2px solid #3B81C8;
+  border-bottom: 2px solid #3B81C8;
+  color: #3B81C8;
+  font-weight: bold;
+  padding: 0;
+  margin: 10px 0px 5px 0px;
+}
+
+h2 span {
+  font-size: 95%;
+  font-weight: normal;
+  text-shadow: 1px 1px 1px #285989;
+}
+
+input, select, textarea {
+  background-color: #FFFFA0;
+}
+
+input[type=submit] {
+  background-color: #619FDF;
+  border: 2px solid #3B81C8;
+  color: white;
+  font-weight: bold;
+  -moz-border-radius: 3px;
+}
+
+span.dateselect .year {
+  width: 6em;
+}
+
+span.dateselect .month {
+  width: 4em;
+}
+
+span.dateselect .day {
+  width: 4em;
+}
+
+hr.separator {
+  height: 2px;
+  color: #3B81C8;
+  background-color: #3B81C8;
+  margin-top: 15px;
+  border: 0;
+}
+
+div.status {
+  color: red;
+}
+
+div#layout {
+  clear: both;
+}
+
+div#contact-table {
+  float: left;
+  margin-right: 20px;
+}
+
+div#contact-form {
+  float: right;
+  border: 2px solid #3B81C8;
+  -moz-border-radius: 3px;
+  padding: 10px;
+  width: 400px;
+}
+
+div.section-content {
+  margin: 0px 10px;
+}
+
+table.contact-list {
+  border-collapse: collapse;
+}
+
+table.contact-list th {
+  background-color: #619FDF;
+  border-top: 1px solid #3B81C8;
+  border-bottom: 1px solid #3B81C8;
+  padding: 3px 5px;
+  text-align: left;
+  color: white;
+}
+
+table.contact-list th a {
+  color: white;
+}
+
+table.contact-list th a:visited {
+  color: white;
+}
+
+table.contact-list th a:hover {
+  color: white;
+}
+
+table.contact-list td {
+  padding: 2px 5px;
+  border-bottom: 1px solid #DBE1E6;
+}
+
+table.contact-list tr.even {
+  background-color: #EDF4F9;
+}
+
+table.contact-list tr.selected {
+  background-color: #FFFFA0;
+}
+
+table.contact-list a {
+  color: black;
+}
+
+table.contact-list a:visited {
+  color: black;
+}
+
+table.contact-list a:hover {
+  color: black;
+}
+
+div#addresses {
+  margin-top: 10px;
+}
+
+div#add-address {
+  margin-top: 10px;
+}
+
+select#form-widgets-addressName {
+  width: 150px;
+}
+
+fieldset.address {
+  background-color: #EDF4F9;
+  border: 1px solid #DBE1E6;
+  margin-top: 7px;
+}
+
+fieldset.address legend {
+  color: #3B81C8;
+}
+
+fieldset.address div.buttons {
+  margin: 0;
+  padding: 0;
+}
+
+div.inline, div.inline div {
+  display: inline;
+}
+
+input.city {
+  width: 12em;
+}
+
+input.state {
+  width: 2em;
+}
+
+input.zip {
+  width: 5em;
+}
+
+
+/* ----[ Phone Styles ]----------------------------------------------------- */
+
+div#phones {
+  margin-top: 20px;
+}
+
+input.countryCode {
+  width: 2em;
+}
+
+input.areaCode {
+  width: 3em;
+}
+
+input.number {
+  width: 9em;
+}
+
+input.extension {
+  width: 6em;
+}
+
+/* ----[ E-Mail Styles ]---------------------------------------------------- */
+
+div#emails {
+  margin-top: 20px;
+}
+
+input.email {
+  width: 20em;
+}


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.css
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,8 @@
+<h1><span>Address Book Demo</span></h1>
+
+<div id="layout">
+  <div id="contact-table"
+       tal:content="structure view/table" />
+  <div id="contact-form"
+       tal:content="structure view/form/render" />
+</div>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addressbook.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addresses.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addresses.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addresses.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,24 @@
+<fieldset class="address" tal:repeat="form view/addressForms">
+  <legend tal:content="form/title">Name</legend>
+  <div tal:content="structure form/render" />
+</fieldset>
+<div id="add-address">
+  <div class="status"
+         tal:define="status view/status"
+         tal:condition="status">
+    <div class="summary"
+         tal:content="view/status">
+      Form status summary
+    </div>
+  </div>
+  <div>
+    <span class="widget"
+           tal:content="structure view/widgets/addressName/render">
+      <input type="text" size="24" value="" />
+    </span>
+    <span class="buttons">
+      <input tal:repeat="action view/actions/values"
+             tal:replace="structure action/render" />
+    </span>
+  </div>
+</div>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/addresses.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/browser.py
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/browser.py	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/browser.py	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,415 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Address Book Views
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.component
+import zope.location
+from zope.app.container import btree
+from zope.app.session.interfaces import ISession
+from zope.pagetemplate.interfaces import IPageTemplate
+from zope.publisher import browser
+from zope.traversing.browser import absoluteURL
+from zope.viewlet.viewlet import CSSViewlet, JavaScriptViewlet
+from z3c.template.interfaces import ILayoutTemplate
+from zc.table import column
+from zc.table.interfaces import ISortableColumn
+
+from z3c.form import form, field, button, subform
+from z3c.formdemo.browser import formatter
+from z3c.formdemo.addressbook import interfaces, contact, dateselect
+
+SESSION_KEY = 'z3c.formdemo.addressbook'
+
+AddressBookCSSViewlet = CSSViewlet('addressbook.css')
+TextShadowViewlet = JavaScriptViewlet('text-shadow.js')
+
+
+class AddressForm(subform.EditSubForm, form.EditForm):
+
+    form.extends(subform.EditSubForm)
+    fields = field.Fields(interfaces.IAddress)
+    name = None
+    deleted = False
+
+    # In this application, we do not want this message
+    noChangesMessage = None
+
+    def updateWidgets(self):
+        super(AddressForm, self).updateWidgets()
+        for name, widget in self.widgets.items():
+            widget.css += ' ' + name
+
+    @property
+    def title(self):
+        return interfaces.AddressNamesVocabulary.getTerm(self.name).title
+
+    @button.handler(form.AddForm.buttons['add'])
+    def handleAdd(self, action):
+        self.handleApply(self, action)
+
+    @button.buttonAndHandler(u'Delete')
+    def handleDelete(self, action):
+        addresses = self.getContent().__parent__
+        del addresses[self.name]
+        self.deleted = True
+
+
+class AddressesForm(form.AddForm):
+    """Form to manage addresses."""
+    # Select the field that specifies the address name.
+    fields = field.Fields(interfaces.IContact['addresses'].key_type)
+    parentForm = None
+
+    def create(self, data):
+        address = contact.Address()
+        address.__name__ = data['addressName']
+        return address
+
+    def add(self, object):
+        addressbook = self.getContent()
+        # Make sure that an address cannot be added twice.
+        if object.__name__ in addressbook:
+            self.status = u'Address already provided for contact.'
+            return None
+        addressbook[object.__name__] = object
+        return object
+
+    def getContent(self):
+        # Get the address container from the contact
+        if interfaces.IContact.providedBy(self.context):
+            return self.context.addresses
+        # We are in the process of adding a contact, so store the addresses
+        # container in a session variable.
+        session = ISession(self.request)[SESSION_KEY]
+        if 'addresses' not in session:
+            session['addresses'] = btree.BTreeContainer()
+        return session['addresses']
+
+    def update(self):
+        # Make sure that we have a unique prefix.
+        self.prefix = self.parentForm.prefix + 'addresses.'
+        super(AddressesForm, self).update()
+        # For each address, create an address form.
+        self.addressForms = []
+        for name, address in self.getContent().items():
+            form = AddressForm(address, self.request, self.parentForm)
+            form.name = name
+            # The prefix is created at runtime to guarantee uniqueness
+            form.prefix = self.prefix + name + '.'
+            form.update()
+            # Updating the address can also mean its deletion. If deleted, it
+            # is not added to the list.
+            if not form.deleted:
+                self.addressForms.append(form)
+
+    def render(self):
+        # Boilerplate when workign with view templates.
+        template = zope.component.getMultiAdapter(
+            (self, self.request), IPageTemplate)
+        return template(self)
+
+
+class PhoneForm(subform.EditSubForm):
+    form.extends(subform.EditSubForm)
+    fields = field.Fields(interfaces.IPhone)
+    attrName = None
+    error = None
+    mode = 'input'
+
+    def updateWidgets(self):
+        super(PhoneForm, self).updateWidgets()
+        for name, widget in self.widgets.items():
+            widget.css += ' ' + name
+
+    def getContent(self):
+        # Get the phone attribute from the contact
+        if interfaces.IContact.providedBy(self.context):
+            return getattr(self.context, self.attrName)
+        # We are in the process of adding a contact, so store the phone
+        # in a session variable.
+        session = ISession(self.request)[SESSION_KEY]
+        if self.attrName not in session:
+            session[self.attrName] = contact.Phone()
+        return session[self.attrName]
+
+    @button.handler(form.AddForm.buttons['add'])
+    def handleAdd(self, action):
+        self.handleApply(self, action)
+
+    @property
+    def id(self):
+        return (self.prefix + 'widgets.countryCode').replace('.', '-')
+
+    @property
+    def label(self):
+        return interfaces.IContact[self.attrName].title
+
+    @property
+    def required(self):
+        return interfaces.IContact[self.attrName].required
+
+
+class PhonesForm(browser.BrowserPage):
+    parentForm = None
+
+    def update(self):
+        self.prefix = self.parentForm.prefix + 'phones.'
+        self.forms = []
+        for name in ('homePhone', 'workPhone', 'cellPhone'):
+            form = PhoneForm(self.context, self.request, self.parentForm)
+            form.prefix = self.prefix + name + '.'
+            form.attrName = name
+            form.update()
+            self.forms.append(form)
+
+    def render(self):
+        template = zope.component.getMultiAdapter(
+            (self, self.request), IPageTemplate)
+        return template(self)
+
+
+class EMailForm(subform.EditSubForm, form.EditForm):
+    form.extends(subform.EditSubForm)
+    fields = field.Fields(interfaces.IEMail['fullAddress'])
+    index = None
+    deleted = False
+
+    # In this application, we do not want this message
+    noChangesMessage = None
+
+    def updateWidgets(self):
+        super(EMailForm, self).updateWidgets()
+        for name, widget in self.widgets.items():
+            widget.css += ' email'
+
+    @button.handler(form.AddForm.buttons['add'])
+    def handleAdd(self, action):
+        self.handleApply(self, action)
+
+    @button.buttonAndHandler(u'Delete')
+    def handleDelete(self, action):
+        emails = self.getContent().__parent__
+        del emails[self.index]
+        self.deleted = True
+
+
+class EMailsForm(form.AddForm):
+    fields = field.Fields(interfaces.IEMail['fullAddress'])
+    parentForm = None
+
+    def updateWidgets(self):
+        super(EMailsForm, self).updateWidgets()
+        for name, widget in self.widgets.items():
+            widget.css += ' email'
+
+    def create(self, data):
+        address = contact.EMail(**data)
+        return address
+
+    def add(self, object):
+        self.getContent().append(object)
+        zope.location.locate(object, self.getContent())
+        self.widgets.ignoreRequest = True
+        self.widgets.update()
+        return object
+
+    def getContent(self):
+        # Get the address container from the contact
+        if interfaces.IContact.providedBy(self.context):
+            return self.context.emails
+        # We are in the process of adding a contact, so store the email list
+        # in a session variable.
+        session = ISession(self.request)[SESSION_KEY]
+        if 'emails' not in session:
+            session['emails'] = contact.EMails()
+        return session['emails']
+
+    def update(self):
+        self.prefix = self.parentForm.prefix + 'emails.'
+        super(EMailsForm, self).update()
+        self.emailForms = []
+        for index, email in enumerate(self.getContent()):
+            form = EMailForm(email, self.request, self.parentForm)
+            form.index = index
+            form.prefix = self.prefix + str(index) + '.'
+            form.update()
+            if not form.deleted:
+                self.emailForms.append(form)
+
+    def render(self):
+        template = zope.component.getMultiAdapter(
+            (self, self.request), IPageTemplate)
+        return template(self)
+
+
+class ContactAddForm(form.AddForm):
+    fields = field.Fields(interfaces.IContact).select(
+        'firstName', 'lastName', 'birthday')
+    fields['birthday'].widgetFactory = dateselect.DateSelectFieldWidget
+    prefix = 'contact.add.'
+
+    def update(self):
+        self.updateWidgets()
+        self.updateActions()
+
+        self.addressesForm = AddressesForm(self.context, self.request)
+        self.addressesForm.parentForm = self
+        self.addressesForm.update()
+
+        self.phonesForm = PhonesForm(self.context, self.request)
+        self.phonesForm.parentForm = self
+        self.phonesForm.update()
+
+        self.emailsForm = EMailsForm(self.context, self.request)
+        self.emailsForm.parentForm = self
+        self.emailsForm.update()
+
+        self.actions.execute()
+
+
+    def create(self, data):
+        newContact = contact.Contact(**data)
+
+        newContact.addresses = self.addressesForm.getContent()
+        zope.location.locate(newContact.addresses, newContact, 'addresses')
+        del ISession(self.request)[SESSION_KEY]['addresses']
+
+        for phoneForm in self.phonesForm.forms:
+            phone = phoneForm.getContent()
+            zope.location.locate(phone, newContact, phoneForm.attrName)
+            setattr(newContact, phoneForm.attrName, phone)
+            del ISession(self.request)[SESSION_KEY][phoneForm.attrName]
+
+        newContact.emails = self.emailsForm.getContent()
+        zope.location.locate(newContact.emails, newContact, 'emails')
+        del ISession(self.request)[SESSION_KEY]['emails']
+
+        return newContact
+
+
+    def add(self, object):
+        count = 0
+        while 'contact-%i' %count in self.context:
+            count += 1;
+        self._name = 'contact-%i' %count
+        self.context[self._name] = object
+        return object
+
+
+    def nextURL(self):
+        return self.request.getURL()
+
+AddContactLabel = button.StaticButtonActionAttribute(
+    u'Add Contact', button=form.AddForm.buttons['add'], form=ContactAddForm)
+
+
+class ContactEditForm(form.EditForm):
+    form.extends(form.EditForm)
+    fields = field.Fields(interfaces.IContact).select(
+        'firstName', 'lastName', 'birthday')
+    fields['birthday'].widgetFactory = dateselect.DateSelectFieldWidget
+    prefix = 'contact.edit.'
+
+    # In this application, we do not want this message
+    noChangesMessage = None
+
+    @button.buttonAndHandler(u'Delete')
+    def handleDelete(self, action):
+        # Delete the contact from the address book
+        contact = self.getContent()
+        addressbook = contact.__parent__
+        del addressbook[contact.__name__]
+        # Reset the selected item
+        ISession(self.request)[SESSION_KEY]['selectedContact'] = None
+
+    @button.buttonAndHandler(u'Done')
+    def handleDone(self, action):
+        # Reset the selected item
+        ISession(self.request)[SESSION_KEY]['selectedContact'] = None
+
+    def update(self):
+        super(ContactEditForm, self).update()
+        self.addressesForm = AddressesForm(self.context, self.request)
+        self.addressesForm.parentForm = self
+        self.addressesForm.update()
+
+        self.phonesForm = PhonesForm(self.context, self.request)
+        self.phonesForm.parentForm = self
+        self.phonesForm.update()
+
+        self.emailsForm = EMailsForm(self.context, self.request)
+        self.emailsForm.parentForm = self
+        self.emailsForm.update()
+
+
+class SelectContactColumn(column.GetterColumn):
+    zope.interface.implements(ISortableColumn)
+
+    def renderCell(self, item, formatter):
+        value = super(SelectContactColumn, self).renderCell(item, formatter)
+        return '<a href="%s?selectContact=%s">%s</a>' %(
+            formatter.request.getURL(), item.__name__, value)
+
+
+class AddressBook(browser.BrowserPage):
+
+    columns = (
+        SelectContactColumn(
+            u'Last Name', lambda i, f: i.lastName, name='lastName'),
+        SelectContactColumn(
+            u'First Name', lambda i, f: i.firstName, name='firstName'),
+        )
+
+    @apply
+    def selectedContact():
+        def get(self):
+            session = ISession(self.request)[SESSION_KEY]
+            return session.get('selectedContact')
+        def set(self, value):
+            session = ISession(self.request)[SESSION_KEY]
+            session['selectedContact'] = value
+        return property(get, set)
+
+    def update(self):
+        # Select a new contact
+        if 'selectContact' in self.request:
+            self.selectedContact = self.context[self.request['selectContact']]
+        # Setup the form
+        if self.selectedContact:
+            self.form = ContactEditForm(self.selectedContact, self.request)
+            self.form.update()
+        if not self.selectedContact:
+            self.form = ContactAddForm(self.context, self.request)
+            self.form.update()
+        # Setup the table
+        rows = [content for content in self.context.values()
+                if interfaces.IContact.providedBy(content)]
+
+        self.table = formatter.SelectedItemFormatter(
+            self.context, self.request, rows,
+            prefix = SESSION_KEY + '.', columns=self.columns,
+            sort_on=[('lastName', False)])
+        self.table.sortKey = 'z3c.formdemo.addressbook.sort-on'
+        self.table.cssClasses['table'] = 'contact-list'
+        self.table.widths = (150, 150)
+        self.table.selectedItem = self.selectedContact
+
+    def __call__(self):
+        self.update()
+        layout = zope.component.getMultiAdapter((self, self.request),
+            ILayoutTemplate)
+        return layout(self)


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/browser.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/configure.zcml
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/configure.zcml	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/configure.zcml	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,152 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:z3c="http://namespaces.zope.org/z3c"
+    i18n_domain="z3c.formdemo">
+
+  <!-- Content Security Declarations -->
+
+  <class class=".contact.Address">
+    <allow interface=".interfaces.IAddress" />
+    <require permission="zope.Public"
+             set_schema=".interfaces.IAddress" />
+  </class>
+
+  <class class=".contact.EMail">
+    <allow interface=".interfaces.IEMail" />
+    <require permission="zope.Public"
+             set_schema=".interfaces.IEMail" />
+  </class>
+
+  <class class=".contact.Phone">
+    <allow interface=".interfaces.IPhone" />
+    <require permission="zope.Public"
+             set_schema=".interfaces.IPhone" />
+  </class>
+
+  <class class=".contact.Contact">
+    <allow interface=".interfaces.IContact" />
+    <require permission="zope.Public"
+             set_schema=".interfaces.IContact" />
+  </class>
+
+  <!-- Supporting resources -->
+
+  <browser:zrt-resource
+      name="addressbook.css"
+      file="addressbook.css"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <browser:viewlet
+      name="addressbook.css"
+      view=".browser.AddressBook"
+      manager="z3c.formdemo.skin.ICSS"
+      class=".browser.AddressBookCSSViewlet"
+      permission="zope.Public"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      weight="1000"
+      />
+
+  <browser:zrt-resource
+      name="text-shadow.js"
+      file="text-shadow.js"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <browser:viewlet
+      name="text-shadow.js"
+      view=".browser.AddressBook"
+      manager="z3c.formdemo.skin.IJavaScript"
+      class=".browser.TextShadowViewlet"
+      permission="zope.Public"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <z3c:macro
+      name="widget-noerror-row"
+      macro="widget-noerror-row"
+      template="form-macros.pt"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <!-- Date-Select Widget -->
+
+  <adapter factory=".dateselect.DateSelectDataConverter" />
+
+  <z3c:widgetTemplate
+      mode="input"
+      widget=".dateselect.DateSelectWidget"
+      layer="z3c.form.interfaces.IFormLayer"
+      template="dateselect.pt"
+      />
+
+  <!-- Address book view -->
+
+  <z3c:pagelet
+      name="addressbook.html"
+      for="zope.app.container.interfaces.IContainer"
+      class=".browser.AddressBook"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      permission="zope.Public"
+      />
+
+  <z3c:template
+      template="addressbook.pt"
+      for=".browser.AddressBook"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <adapter
+      factory=".browser.AddContactLabel"
+      name="title" />
+
+  <z3c:template
+      template="contact.pt"
+      for=".browser.ContactAddForm"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <z3c:template
+      template="contact.pt"
+      for=".browser.ContactEditForm"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <z3c:template
+      template="addresses.pt"
+      for=".browser.AddressesForm"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <z3c:template
+      template="address.pt"
+      for=".browser.AddressForm"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <z3c:template
+      template="phones.pt"
+      for=".browser.PhonesForm"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <z3c:template
+      template="phone.pt"
+      for=".browser.PhoneForm"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <z3c:template
+      template="emails.pt"
+      for=".browser.EMailsForm"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+  <z3c:template
+      template="email.pt"
+      for=".browser.EMailForm"
+      layer="z3c.formdemo.layer.IDemoBrowserLayer"
+      />
+
+</configure>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,20 @@
+<div metal:use-macro="macro:form">
+  <metal:block fill-slot="above-buttons">
+    <div id="addresses">
+      <h2><span>Addresses</span></h2>
+      <div class="section-content"
+           tal:content="structure view/addressesForm/render" />
+    </div>
+    <div id="phones">
+      <h2><span>Phone Numbers</span></h2>
+      <div class="section-content"
+           tal:content="structure view/phonesForm/render" />
+    </div>
+    <div id="emails">
+      <h2><span>E-Mail Addresses</span></h2>
+      <div class="section-content"
+           tal:content="structure view/emailsForm/render" />
+    </div>
+    <hr class="separator" />
+  </metal:block>
+</div>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.py
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.py	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.py	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,92 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Address Book Views
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import persistent
+import zope.interface
+import zope.location
+from zope.app.container import contained
+from zope.schema.fieldproperty import FieldProperty
+
+from z3c.formdemo.addressbook import interfaces
+
+
+class Address(contained.Contained, persistent.Persistent):
+    zope.interface.implements(interfaces.IAddress)
+
+    street = FieldProperty(interfaces.IAddress['street'])
+    city = FieldProperty(interfaces.IAddress['city'])
+    state = FieldProperty(interfaces.IAddress['state'])
+    zip = FieldProperty(interfaces.IAddress['zip'])
+
+    def __init__(self, **data):
+        for name, value in data.items():
+            setattr(self, name, value)
+
+
+class EMails(zope.location.Location, list):
+    pass
+
+class EMail(contained.Contained, persistent.Persistent):
+    zope.interface.implements(interfaces.IEMail)
+
+    user = FieldProperty(interfaces.IEMail['user'])
+    host = FieldProperty(interfaces.IEMail['host'])
+
+    def __init__(self, **data):
+        for name, value in data.items():
+            setattr(self, name, value)
+
+    @apply
+    def fullAddress():
+        def get(self):
+            return self.user + u'@' + self.host
+        def set(self, value):
+            self.user, self.host = value.split('@')
+        return property(get, set)
+
+
+class Phone(contained.Contained, persistent.Persistent):
+    zope.interface.implements(interfaces.IPhone)
+
+    countryCode = FieldProperty(interfaces.IPhone['countryCode'])
+    areaCode = FieldProperty(interfaces.IPhone['areaCode'])
+    number = FieldProperty(interfaces.IPhone['number'])
+    extension = FieldProperty(interfaces.IPhone['extension'])
+
+    def __init__(self, **data):
+        for name, value in data.items():
+            setattr(self, name, value)
+
+
+class Contact(contained.Contained, persistent.Persistent):
+    zope.interface.implements(interfaces.IContact)
+
+    firstName = FieldProperty(interfaces.IContact['firstName'])
+    lastName = FieldProperty(interfaces.IContact['lastName'])
+    birthday = FieldProperty(interfaces.IContact['birthday'])
+
+    addresses = None
+    emails = None
+    homePhone = None
+    cellPhone = None
+    workPhone = None
+
+    def __init__(self, **data):
+        # Save all values
+        for name, value in data.items():
+            setattr(self, name, value)


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/contact.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,5 @@
+<span class="dateselect">
+  <div tal:replace="structure view/month/render" /> /
+  <div tal:replace="structure view/day/render" /> /
+  <div tal:replace="structure view/year/render" />
+</span>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.py
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.py	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.py	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,88 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Date Selection
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import datetime
+import zope.component
+import zope.interface
+import zope.schema
+from zope.schema import vocabulary
+from z3c.form import interfaces, widget
+from z3c.form.browser import select
+
+
+class DateSelectWidget(widget.Widget):
+
+    selects = ( ('year', range(1920, 2011), 0),
+                ('month', range(1, 13), 1),
+                ('day', range(1, 32), 2) )
+
+    def update(self):
+        for (name, options, loc) in self.selects:
+            selectWidget = select.SelectWidget(self.request)
+            selectWidget.terms = vocabulary.SimpleVocabulary.fromValues(options)
+            selectWidget.required = True
+            selectWidget.name = self.name + '.' + name
+            selectWidget.id = selectWidget.name.replace('.', '-')
+            selectWidget.css = name
+            setattr(self, name, selectWidget)
+
+        super(DateSelectWidget, self).update()
+
+        for (name, options, loc) in self.selects:
+            selectWidget = getattr(self, name)
+            if self.value and not selectWidget.value:
+                selectWidget.value = (self.value[loc],)
+            selectWidget.update()
+
+
+    def extract(self, default=interfaces.NOVALUE):
+        """See z3c.form.interfaces.IWidget."""
+        value = (self.year.extract(default),
+                 self.month.extract(default),
+                 self.day.extract(default))
+        if default in value:
+            return default
+        return value
+
+
+ at zope.component.adapter(zope.schema.interfaces.IDate, interfaces.IFormLayer)
+ at zope.interface.implementer(interfaces.IFieldWidget)
+def DateSelectFieldWidget(field, request):
+    """IFieldWidget factory for DateSelectWidget."""
+    return widget.FieldWidget(field, DateSelectWidget(request))
+
+
+class DateSelectDataConverter(object):
+    zope.component.adapts(zope.schema.interfaces.IDate, DateSelectWidget)
+    zope.interface.implements(interfaces.IDataConverter)
+
+    def __init__(self, field, widget):
+        self.field = field
+        self.widget = widget
+
+    def toWidgetValue(self, value):
+        """See interfaces.IDataConverter"""
+        if value is self.field.missing_value:
+            return None
+        return (str(value.year), str(value.month), str(value.day))
+
+    def toFieldValue(self, value):
+        """See interfaces.IDataConverter"""
+        if value == None:
+            return self.field.missing_value
+        return datetime.date(*[int(part[0]) for part in value])


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/dateselect.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/email.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/email.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/email.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,5 @@
+<div>
+  <div metal:use-macro="macro:form-header" />
+  <div tal:replace="structure view/widgets/fullAddress/render" />
+  <div tal:replace="structure view/actions/delete/render" />
+</div>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/email.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/emails.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/emails.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/emails.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,23 @@
+<div class="row"
+     tal:repeat="form view/emailForms"
+     tal:content="structure form/render" />
+<div id="add-email">
+  <div class="status"
+         tal:define="status view/status"
+         tal:condition="status">
+    <div class="summary"
+         tal:content="view/status">
+      Form status summary
+    </div>
+  </div>
+  <div>
+    <span class="widget"
+           tal:content="structure view/widgets/fullAddress/render">
+      <input type="text" size="24" value="" />
+    </span>
+    <span class="buttons">
+      <input tal:repeat="action view/actions/values"
+             tal:replace="structure action/render" />
+    </span>
+  </div>
+</div>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/emails.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/form-macros.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/form-macros.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/form-macros.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,14 @@
+<metal:block define-macro="widget-noerror-row">
+<div class="noerror">
+  <div class="label">
+    <label tal:attributes="for widget/name">
+      <span i18n:translate=""
+          tal:content="widget/label">label</span
+      ><span class="required" tal:condition="widget/required">*</span>
+    </label>
+  </div>
+  <div class="widget" tal:content="structure widget/render">
+    <input type="text" size="24" value="" />
+  </div>
+</div>
+</metal:block>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/form-macros.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/interfaces.py
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/interfaces.py	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/interfaces.py	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,125 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Address Book Interfaces
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+import zope.schema
+from zope.schema import vocabulary
+
+AddressNamesVocabulary = vocabulary.SimpleVocabulary((
+    vocabulary.SimpleTerm('home', title=u'Home'),
+    vocabulary.SimpleTerm('work', title=u'Work'),
+    vocabulary.SimpleTerm('other', title=u'Other')
+    ))
+
+class IAddress(zope.interface.Interface):
+    """An address."""
+
+    street = zope.schema.TextLine(
+        title=u'Street',
+        description=u'Street name and number.')
+
+    city = zope.schema.TextLine(
+        title=u'City',
+        description=u'City.')
+
+    state = zope.schema.TextLine(
+        title=u'State',
+        description=u'State or Province.')
+
+    zip = zope.schema.TextLine(
+        title=u'ZIP',
+        description=u'ZIP Code.')
+
+
+class IEMail(zope.interface.Interface):
+    """An E-mail address."""
+
+    user = zope.schema.TextLine(
+        title=u'User')
+
+    host = zope.schema.TextLine(
+        title=u'Host')
+
+    fullAddress = zope.schema.TextLine(
+        title=u'E-mail Address',
+        description=u'The full E-mail address.')
+
+
+class IPhone(zope.interface.Interface):
+    """A phone number."""
+
+    countryCode = zope.schema.TextLine(
+        title=u'Country Code',
+        default=u'1')
+
+    areaCode = zope.schema.TextLine(
+        title=u'Area Code')
+
+    number = zope.schema.TextLine(
+        title=u'Number')
+
+    extension = zope.schema.TextLine(
+        title=u'Extension',
+        required=False)
+
+
+class IContact(zope.interface.Interface):
+    """A contact in the address book."""
+
+    firstName = zope.schema.TextLine(
+        title=u'First Name',
+        description=u'First name of the person.')
+
+    lastName = zope.schema.TextLine(
+        title=u'Last Name',
+        description=u'Last name of the person.')
+
+    birthday = zope.schema.Date(
+        title=u'Birthday',
+        description=u'Birthday of the person.',
+        required=False)
+
+    addresses = zope.schema.Dict(
+        title=u'Addresses',
+        description=u'A mapping of addresses',
+        key_type=zope.schema.Choice(
+            __name__='addressName',
+            vocabulary=AddressNamesVocabulary),
+        value_type=zope.schema.Object(schema=IAddress))
+
+    emails = zope.schema.List(
+        title=u'E-mails',
+        description=u'E-mails of the person.',
+        value_type=zope.schema.Object(schema=IEMail))
+
+    homePhone = zope.schema.Object(
+        title=u'Home Phone',
+        description=u'Home Phone Number.',
+        schema=IPhone)
+
+    cellPhone = zope.schema.Object(
+        title=u'Cell Phone',
+        description=u'Cell Phone Number.',
+        schema=IPhone,
+        required=False)
+
+    workPhone = zope.schema.Object(
+        title=u'Work Phone',
+        description=u'Work Phone Number.',
+        schema=IPhone,
+        required=False)


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phone.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phone.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phone.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,4 @@
++<input tal:replace="structure view/widgets/countryCode/render" />
+( <input tal:replace="structure view/widgets/areaCode/render" /> )
+<input tal:replace="structure view/widgets/number/render" />
+ext. <input tal:replace="structure view/widgets/extension/render" />


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phone.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phones.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phones.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phones.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,4 @@
+<div class="row"
+     tal:repeat="widget view/forms">
+  <div metal:use-macro="macro:widget-row" />
+</div>


Property changes on: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/phones.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.formdemo/trunk/src/z3c/formdemo/addressbook/text-shadow.js
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/addressbook/text-shadow.js	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/addressbook/text-shadow.js	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,365 @@
+if(window.addEventListener)window.addEventListener('load',textShadows,false);
+else if(window.attachEvent)window.attachEvent('onload',textShadows);
+function setStyles(o,s){
+	var i;
+	s=s.split(';');
+	for(i in s){
+		var p=s[i].split(':');
+		o.style[p[0]]=p[1];
+	}
+}
+function textShadows(){
+	var ua=navigator.userAgent;
+	if(ua.indexOf('KHTML')>=0&&!(ua.indexOf('Safari')>=0))return;
+	var ss=document.styleSheets,a;
+	for(a in ss){
+		var theRules=[],b;
+		if(ss[a].cssRules)theRules=ss[a].cssRules;
+		else if(ss[a].rules)theRules=ss[a].rules;
+		for(b=0; b < theRules.length; b++){
+			var selector=theRules[b].selectorText,r=theRules[b].style.cssText;
+			if(/text-shadow/.test(r)){
+				r=r.replace(/([ ,]) /g,'$1').replace(/.*text-shadow[ :]+/,'').replace(/[ ]*;.*/,'');
+				var shadows=r.split(','),k,els=cssQuery(selector),l;
+				for(l in els){
+					var x=parseInt(els[l].offsetLeft),y=parseInt(els[l].offsetTop),el3=els[l].cloneNode(true);
+					setStyles(el3,'position:absolute;zIndex:50;margin:0');
+					for(k in shadows){
+						var parts=shadows[k].split(' ');
+						var newX=x+parseInt(parts[1]),newY=y+parseInt(parts[2]),rad=parseInt(parts[3]);
+						for(m=0-rad;m<=rad;++m)for(n=0-rad;n<=rad;++n)showShadow(els[l],newX+m,newY+n,parts[0]);
+						var el2=el3.cloneNode(true);
+						setStyles(el2,'left:'+x+'px;top:'+y+'px');
+						els[l].parentNode.appendChild(el2);
+					}
+				}
+			}
+		}
+	}
+}
+function showShadow(el,x,y,color){
+	var el2=el.cloneNode(true);
+	setStyles(el2,'position:absolute;color:'+color+';left:'+x+'px;top:'+y+'px;margin:0;textShadow:none;zIndex:49');
+	el2.style.opacity='.08';
+	el2.style.filter='alpha(opacity=8)';
+	el.parentNode.appendChild(el2);
+}
+
+
+
+/*
+	This work is licensed under a Creative Commons License.
+
+	License: http://creativecommons.org/licenses/by/1.0/
+
+	You are free:
+
+	to copy, distribute, display, and perform the work
+	to make derivative works
+	to make commercial use of the work
+
+	Under the following conditions:
+
+	Attribution. You must give the original author credit
+
+	Author:  Dean Edwards/2004
+	Web:     http://dean.edwards.name/
+*/
+
+/* keeping code tidy! */
+
+/* extendible css query function for common platforms
+
+			tested on IE5.0/5.5/6.0, Mozilla 1.6/Firefox 0.8, Opera 7.23/7.5
+			(all windows platforms - somebody buy me a mac!)
+*/
+
+// -----------------------------------------------------------------------
+//  css query engine
+// -----------------------------------------------------------------------
+
+var cssQuery=function() {
+	var version="1.0.1"; // timestamp: 2004/05/25
+
+	// constants
+	var STANDARD_SELECT=/^[^>\+~\s]/;
+	var STREAM=/[\s>\+~:@#\.]|[^\s>\+~:@#\.]+/g;
+	var NAMESPACE=/\|/;
+	var IMPLIED_SELECTOR=/([\s>\+~\,]|^)([\.:#@])/g;
+	var ASTERISK ="$1*$2";
+	var WHITESPACE=/^\s+|\s*([\+\,>\s;:])\s*|\s+$/g;
+	var TRIM="$1";
+	var NODE_ELEMENT=1;
+	var NODE_TEXT=3;
+	var NODE_DOCUMENT=9;
+
+	// sniff for explorer (cos of one little bug)
+	var isMSIE=/MSIE/.test(navigator.appVersion), isXML;
+
+	// cache results for faster processing
+	var cssCache={};
+
+	// this is the query function
+	function cssQuery(selector, from) {
+		if (!selector) return [];
+		var useCache=arguments.callee.caching && !from;
+		from=(from) ? (from.constructor == Array) ? from : [from] : [document];
+		isXML=checkXML(from[0]);
+		// process comma separated selectors
+		var selectors=parseSelector(selector).split(",");
+		var match=[];
+		for (var i in selectors) {
+			// convert the selector to a stream
+			selector=toStream(selectors[i]);
+			// process the stream
+			var j=0, token, filter, cacheSelector="", filtered=from;
+			while (j < selector.length) {
+				token=selector[j++];
+				filter=selector[j++];
+				cacheSelector += token + filter;
+				// process a token/filter pair
+				filtered=(useCache && cssCache[cacheSelector]) ? cssCache[cacheSelector] : select(filtered, token, filter);
+				if (useCache) cssCache[cacheSelector]=filtered;
+			}
+			match=match.concat(filtered);
+		}
+		// return the filtered selection
+		return match;
+	};
+	cssQuery.caching=false;
+	cssQuery.reset=function() {
+		cssCache={};
+	};
+	cssQuery.toString=function () {
+		return "function cssQuery() {\n  [version " + version + "]\n}";
+	};
+
+	var checkXML=(isMSIE) ? function(node) {
+		if (node.nodeType != NODE_DOCUMENT) node=node.document;
+		return node.mimeType == "XML Document";
+	} : function(node) {
+		if (node.nodeType == NODE_DOCUMENT) node=node.documentElement;
+		return node.localName != "HTML";
+	};
+
+	function parseSelector(selector) {
+		return selector
+		// trim whitespace
+		.replace(WHITESPACE, TRIM)
+		// encode attribute selectors
+		.replace(attributeSelector.ALL, attributeSelector.ID)
+		// e.g. ".class1" --> "*.class1"
+		.replace(IMPLIED_SELECTOR, ASTERISK);
+	};
+
+	// convert css selectors to a stream of tokens and filters
+	//  it's not a real stream. it's just an array of strings.
+	function toStream(selector) {
+		if (STANDARD_SELECT.test(selector)) selector=" " + selector;
+		return selector.match(STREAM) || [];
+	};
+
+	var pseudoClasses={ // static
+		// CSS1
+		"link": function(element) {
+			for (var i=0; i < document.links; i++) {
+				if (document.links[i] == element) return true;
+			}
+		},
+		"visited": function(element) {
+			// can't do this without jiggery-pokery
+		},
+		// CSS2
+		"first-child": function(element) {
+			return !previousElement(element);
+		},
+		// CSS3
+		"last-child": function(element) {
+			return !nextElement(element);
+		},
+		"root": function(element) {
+			var document=element.ownerDocument || element.document;
+			return Boolean(element == document.documentElement);
+		},
+		"empty": function(element) {
+			for (var i=0; i < element.childNodes.length; i++) {
+				if (isElement(element.childNodes[i]) || element.childNodes[i].nodeType == NODE_TEXT) return false;
+			}
+			return true;
+		}
+		// add your own...
+	};
+
+	var QUOTED=/([\'\"])[^\1]*\1/;
+	function quote(value) {return (QUOTED.test(value)) ? value : "'" + value + "'"};
+	function unquote(value) {return (QUOTED.test(value)) ? value.slice(1, -1) : value};
+
+	var attributeSelectors=[];
+
+	function attributeSelector(attribute, compare, value) {
+		// properties
+		this.id=attributeSelectors.length;
+		// build the test expression
+		var test="element.";
+		switch (attribute.toLowerCase()) {
+			case "id":
+				test += "id";
+				break;
+			case "class":
+				test += "className";
+				break;
+			default:
+				test += "getAttribute('" + attribute + "')";
+		}
+		// continue building the test expression
+		switch (compare) {
+			case "=":
+				test += "==" + quote(value);
+				break;
+			case "~=":
+				test="/(^|\\s)" + unquote(value) + "(\\s|$)/.test(" + test + ")";
+				break;
+			case "|=":
+				test="/(^|-)" + unquote(value) + "(-|$)/.test(" + test + ")";
+				break;
+		}
+		push(attributeSelectors, new Function("element", "return " + test));
+	};
+	attributeSelector.prototype.toString=function() {
+		return attributeSelector.PREFIX + this.id;
+	};
+	// constants
+	attributeSelector.PREFIX="@";
+	attributeSelector.ALL=/\[([^~|=\]]+)([~|]?=?)([^\]]+)?\]/g;
+	// class methods
+	attributeSelector.ID=function(match, attribute, compare, value) {
+		return new attributeSelector(attribute, compare, value);
+	};
+
+	// select a set of matching elements.
+	// "from" is an array of elements.
+	// "token" is a character representing the type of filter
+	//  e.g. ">" means child selector
+	// "filter" represents the tag name, id or class name that is being selected
+	// the function returns an array of matching elements
+	function select(from, token, filter) {
+		//alert("token="+token+",filter="+filter);
+		var namespace="";
+		if (NAMESPACE.test(filter)) {
+			filter=filter.split("|");
+			namespace=filter[0];
+			filter=filter[1];
+		}
+		var filtered=[], i;
+		switch (token) {
+			case " ": // descendant
+				for (i in from) {
+					var subset=getElementsByTagNameNS(from[i], filter, namespace);
+					for (var j=0; j < subset.length; j++) {
+						if (isElement(subset[j]) && (!namespace || compareNamespace(subset[j], namespace)))
+							push(filtered, subset[j]);
+					}
+				}
+				break;
+			case ">": // child
+				for (i in from) {
+					var subset=from[i].childNodes;
+					for (var j=0; j < subset.length; j++)
+						if (compareTagName(subset[j], filter, namespace)) push(filtered, subset[j]);
+				}
+				break;
+			case "+": // adjacent (direct)
+				for (i in from) {
+					var adjacent=nextElement(from[i]);
+					if (adjacent && compareTagName(adjacent, filter, namespace)) push(filtered, adjacent);
+				}
+				break;
+			case "~": // adjacent (indirect)
+				for (i in from) {
+					var adjacent=from[i];
+					while (adjacent=nextElement(adjacent)) {
+						if (adjacent && compareTagName(adjacent, filter, namespace)) push(filtered, adjacent);
+					}
+				}
+				break;
+			case ".": // class
+				filter=new RegExp("(^|\\s)" + filter + "(\\s|$)");
+				for (i in from) if (filter.test(from[i].className)) push(filtered, from[i]);
+				break;
+			case "#": // id
+				for (i in from) if (from[i].id == filter) push(filtered, from[i]);
+				break;
+			case "@": // attribute selector
+				filter=attributeSelectors[filter];
+				for (i in from) if (filter(from[i])) push(filtered, from[i]);
+				break;
+			case ":": // pseudo-class (static)
+				filter=pseudoClasses[filter];
+				for (i in from) if (filter(from[i])) push(filtered, from[i]);
+				break;
+		}
+		return filtered;
+	};
+
+	var getElementsByTagNameNS=(isMSIE) ? function(from, tagName) {
+		return (tagName == "*" && from.all) ? from.all : from.getElementsByTagName(tagName);
+	} : function(from, tagName, namespace) {
+		return (namespace) ? from.getElementsByTagNameNS("*", tagName) : from.getElementsByTagName(tagName);
+	};
+
+	function compareTagName(element, tagName, namespace) {
+		if (namespace && !compareNamespace(element, namespace)) return false;
+		return (tagName == "*") ? isElement(element) : (isXML) ? (element.tagName == tagName) : (element.tagName == tagName.toUpperCase());
+	};
+
+	var PREFIX=(isMSIE) ? "scopeName" : "prefix";
+	function compareNamespace(element, namespace) {
+		return element[PREFIX] == namespace;
+	};
+
+	// return the previous element to the supplied element
+	//  previousSibling is not good enough as it might return a text or comment node
+	function previousElement(element) {
+		while ((element=element.previousSibling) && !isElement(element)) continue;
+		return element;
+	};
+
+	// return the next element to the supplied element
+	function nextElement(element) {
+		while ((element=element.nextSibling) && !isElement(element)) continue;
+		return element;
+	};
+
+	function isElement(node) {
+		return Boolean(node.nodeType == NODE_ELEMENT && node.tagName != "!");
+	};
+
+
+	// use a baby push function because IE5.0 doesn't support Array.push
+	function push(array, item) {
+		array[array.length]=item;
+	};
+
+	// fix IE5.0 String.replace
+	if ("i".replace(/i/,function(){return""})) {
+		// preserve String.replace
+		var string_replace=String.prototype.replace;
+		// create String.replace for handling functions
+		var function_replace=function(regexp, replacement) {
+			var match, newString="", string=this;
+			while ((match=regexp.exec(string))) {
+				// five string replacement arguments is sufficent for cssQuery
+				newString += string.slice(0, match.index) + replacement(match[0], match[1], match[2], match[3], match[4]);
+				string=string.slice(match.lastIndex);
+			}
+			return newString + string;
+		};
+		// replace String.replace
+		String.prototype.replace=function (regexp, replacement) {
+			this.replace=(typeof replacement == "function") ? function_replace : string_replace;
+			return this.replace(regexp, replacement);
+		};
+	}
+
+	return cssQuery;
+}();

Copied: z3c.formdemo/trunk/src/z3c/formdemo/browser/formatter.py (from rev 76833, z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/formatter.py)
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/browser/formatter.py	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/browser/formatter.py	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,128 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""List Formatter Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from xml.sax.saxutils import quoteattr
+from zope.app.pagetemplate import ViewPageTemplateFile
+from zope.app.session.interfaces import ISession
+from zc.table import table, column, interfaces
+
+
+class ListFormatter(table.SortingFormatterMixin, table.AlternatingRowFormatter):
+    """Provides a width for each column."""
+
+    sortedHeaderTemplate = ViewPageTemplateFile('table_sorted_header.pt')
+
+    sortKey = 'formdemo.table.sort-on'
+    widths = None
+    columnCSS = None
+
+    def __init__(self, *args, **kw):
+        # Figure out sorting situation
+        kw['ignore_request'] = True
+        request = args[1]
+        prefix = kw.get('prefix')
+        session = ISession(request)[self.sortKey]
+        if 'sort-on' in request:
+            name = request['sort-on']
+            if prefix and name.startswith(prefix):
+                name = name[len(prefix):]
+                oldName, oldReverse = session.get(prefix, (None, None))
+                if oldName == name:
+                    session[prefix] = (name, not oldReverse)
+                else:
+                    session[prefix] = (name, False)
+        # Now get the sort-on data from the session
+        if prefix in session:
+            kw['sort_on'] = [session[prefix]]
+
+        super(ListFormatter, self).__init__(*args, **kw)
+        self.columnCSS = {}
+
+        self.sortOn = (None, None)
+        if 'sort_on' in kw:
+            for name, reverse in kw['sort_on']:
+                self.columnCSS[name] = 'sorted-on'
+            self.sortOn = kw['sort_on'][0]
+
+    def getHeader(self, column):
+        contents = column.renderHeader(self)
+        if (interfaces.ISortableColumn.providedBy(column)):
+            contents = self._wrapInSortUI(contents, column)
+        return contents
+
+    def _wrapInSortUI(self, header, column):
+        name = column.name
+        if self.prefix:
+            name = self.prefix + name
+        isAscending = self.sortOn[0] == column.name and not self.sortOn[1]
+        isDecending = self.sortOn[0] == column.name and self.sortOn[1]
+        return self.sortedHeaderTemplate(
+            header=header, name=name,
+            isAscending=isAscending, isDecending=isDecending)
+
+    def renderContents(self):
+        """Avoid to render empty table (tr) rows."""
+        rows = self.renderRows()
+        if not rows:
+            return '  <thead%s>\n%s  </thead>\n' % (
+                self._getCSSClass('thead'), self.renderHeaderRow())
+        else:
+            return '  <thead%s>\n%s  </thead>\n  <tbody>\n%s  </tbody>\n' % (
+                self._getCSSClass('thead'), self.renderHeaderRow(),
+                rows)
+
+    def renderHeader(self, column):
+        width = ''
+        if self.widths:
+            idx = list(self.visible_columns).index(column)
+            width = ' width="%i"' %self.widths[idx]
+        klass = self.cssClasses.get('tr', '')
+        if column.name in self.columnCSS:
+            klass += klass and ' ' or '' + self.columnCSS[column.name]
+        return '      <th%s class=%s>\n        %s\n      </th>\n' % (
+            width, quoteattr(klass), self.getHeader(column))
+
+
+    def renderCell(self, item, column):
+        klass = self.cssClasses.get('tr', '')
+        if column.name in self.columnCSS:
+            klass += klass and ' ' or '' + self.columnCSS[column.name]
+        return '    <td class=%s>\n      %s\n    </td>\n' % (
+            quoteattr(klass), self.getCell(item, column))
+
+    def renderExtra(self):
+        """Avoid use of resourcelibrary in original class."""
+        return ''
+
+
+class SelectedItemFormatter(ListFormatter):
+
+    selectedItem = None
+
+    def renderRow(self, item):
+        self.row += 1
+        klass = self.cssClasses.get('tr', '')
+        if klass:
+            klass += ' '
+        if item == self.selectedItem:
+            klass += 'selected'
+        else:
+            klass += self.row_classes[self.row % 2]
+
+        return '  <tr class=%s>\n%s  </tr>\n' % (
+            quoteattr(klass), self.renderCells(item))

Modified: z3c.formdemo/trunk/src/z3c/formdemo/browser/index.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/browser/index.pt	2007-06-22 02:07:24 UTC (rev 76932)
+++ z3c.formdemo/trunk/src/z3c/formdemo/browser/index.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -28,4 +28,7 @@
   <div>
     <a href="../++skin++Z3CFormDemo/spreadsheet.html">Spreadsheet</a>
   </div>
+  <div>
+    <a href="../++skin++Z3CFormDemo/addressbook.html">Address Book</a>
+  </div>
 </div>

Copied: z3c.formdemo/trunk/src/z3c/formdemo/browser/table_sorted_header.pt (from rev 76833, z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/table_sorted_header.pt)
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/browser/table_sorted_header.pt	                        (rev 0)
+++ z3c.formdemo/trunk/src/z3c/formdemo/browser/table_sorted_header.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -0,0 +1,16 @@
+<html tal:omit-tag="">
+  <a href=""
+     tal:attributes="href string:${request/URL}?sort-on=${options/name}"
+     tal:content="options/header" />
+  &nbsp;
+  <img src="" width="7" height="4" style="vertical-align: middle"
+       alt="sort ascending"
+       tal:condition="options/isAscending"
+       tal:attributes="src
+           context/++resource++SpreadsheetImages/ascending.gif" />
+  <img src="" width="7" height="4" style="vertical-align: middle"
+       alt="sort ascending"
+       tal:condition="options/isDecending"
+       tal:attributes="src
+           context/++resource++SpreadsheetImages/decending.gif" />
+</html>

Modified: z3c.formdemo/trunk/src/z3c/formdemo/configure.zcml
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/configure.zcml	2007-06-22 02:07:24 UTC (rev 76932)
+++ z3c.formdemo/trunk/src/z3c/formdemo/configure.zcml	2007-06-22 02:20:21 UTC (rev 76933)
@@ -18,5 +18,6 @@
   <include package=".calculator" />
   <include package=".wizard" />
   <include package=".spreadsheet" />
+  <include package=".addressbook" />
 
 </configure>

Deleted: z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/formatter.py
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/formatter.py	2007-06-22 02:07:24 UTC (rev 76932)
+++ z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/formatter.py	2007-06-22 02:20:21 UTC (rev 76933)
@@ -1,129 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2007 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""List Formatter Implementation
-
-$Id$
-"""
-__docformat__ = "reStructuredText"
-from xml.sax.saxutils import quoteattr
-from zope.app.pagetemplate import ViewPageTemplateFile
-from zope.app.session.interfaces import ISession
-from zc.table import table, column, interfaces
-
-SORTED_ON_KEY = 'reflib.table.sorted-on'
-
-
-class ListFormatter(table.SortingFormatterMixin, table.AlternatingRowFormatter):
-    """Provides a width for each column."""
-
-    sortedHeaderTemplate = ViewPageTemplateFile('table_sorted_header.pt')
-
-    widths = None
-    columnCSS = None
-
-    def __init__(self, *args, **kw):
-        # Figure out sorting situation
-        kw['ignore_request'] = True
-        request = args[1]
-        prefix = kw.get('prefix')
-        session = ISession(request)[SORTED_ON_KEY]
-        if 'sort-on' in request:
-            name = request['sort-on']
-            if prefix and name.startswith(prefix):
-                name = name[len(prefix):]
-                oldName, oldReverse = session.get(prefix, (None, None))
-                if oldName == name:
-                    session[prefix] = (name, not oldReverse)
-                else:
-                    session[prefix] = (name, False)
-        # Now get the sort-on data from the session
-        if prefix in session:
-            kw['sort_on'] = [session[prefix]]
-
-        super(ListFormatter, self).__init__(*args, **kw)
-        self.columnCSS = {}
-
-        self.sortOn = (None, None)
-        if 'sort_on' in kw:
-            for name, reverse in kw['sort_on']:
-                self.columnCSS[name] = 'sorted-on'
-            self.sortOn = kw['sort_on'][0]
-
-    def getHeader(self, column):
-        contents = column.renderHeader(self)
-        if (interfaces.ISortableColumn.providedBy(column)):
-            contents = self._wrapInSortUI(contents, column)
-        return contents
-
-    def _wrapInSortUI(self, header, column):
-        name = column.name
-        if self.prefix:
-            name = self.prefix + name
-        isAscending = self.sortOn[0] == column.name and not self.sortOn[1]
-        isDecending = self.sortOn[0] == column.name and self.sortOn[1]
-        return self.sortedHeaderTemplate(
-            header=header, name=name,
-            isAscending=isAscending, isDecending=isDecending)
-
-    def renderContents(self):
-        """Avoid to render empty table (tr) rows."""
-        rows = self.renderRows()
-        if not rows:
-            return '  <thead%s>\n%s  </thead>\n' % (
-                self._getCSSClass('thead'), self.renderHeaderRow())
-        else:
-            return '  <thead%s>\n%s  </thead>\n  <tbody>\n%s  </tbody>\n' % (
-                self._getCSSClass('thead'), self.renderHeaderRow(),
-                rows)
-
-    def renderHeader(self, column):
-        width = ''
-        if self.widths:
-            idx = list(self.visible_columns).index(column)
-            width = ' width="%i"' %self.widths[idx]
-        klass = self.cssClasses.get('tr', '')
-        if column.name in self.columnCSS:
-            klass += klass and ' ' or '' + self.columnCSS[column.name]
-        return '      <th%s class=%s>\n        %s\n      </th>\n' % (
-            width, quoteattr(klass), self.getHeader(column))
-
-
-    def renderCell(self, item, column):
-        klass = self.cssClasses.get('tr', '')
-        if column.name in self.columnCSS:
-            klass += klass and ' ' or '' + self.columnCSS[column.name]
-        return '    <td class=%s>\n      %s\n    </td>\n' % (
-            quoteattr(klass), self.getCell(item, column))
-
-    def renderExtra(self):
-        """Avoid use of resourcelibrary in original class."""
-        return ''
-
-
-class SelectedItemFormatter(ListFormatter):
-
-    selectedItem = None
-
-    def renderRow(self, item):
-        self.row += 1
-        klass = self.cssClasses.get('tr', '')
-        if klass:
-            klass += ' '
-        if item == self.selectedItem:
-            klass += 'selected'
-        else:
-            klass += self.row_classes[self.row % 2]
-
-        return '  <tr class=%s>\n%s  </tr>\n' % (
-            quoteattr(klass), self.renderCells(item))

Modified: z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/spreadsheet.py
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/spreadsheet.py	2007-06-22 02:07:24 UTC (rev 76932)
+++ z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/spreadsheet.py	2007-06-22 02:20:21 UTC (rev 76933)
@@ -22,7 +22,8 @@
 from z3c.formui import layout
 from zc.table import table, column
 
-from z3c.formdemo.spreadsheet import content, formatter
+from z3c.formdemo.browser import formatter
+from z3c.formdemo.spreadsheet import content
 
 
 class SpreadsheetDataColumn(column.SortingColumn):
@@ -160,4 +161,5 @@
             self.context, self.request, rows,
             prefix = self.sessionKey + '.', columns=columns,
             sort_on=[('lastName', False)])
+        self.table.sortKey = 'formdemo.spreadsheet.sort-on'
         self.table.widths = self.columnWidths + (100,)

Deleted: z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/table_sorted_header.pt
===================================================================
--- z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/table_sorted_header.pt	2007-06-22 02:07:24 UTC (rev 76932)
+++ z3c.formdemo/trunk/src/z3c/formdemo/spreadsheet/table_sorted_header.pt	2007-06-22 02:20:21 UTC (rev 76933)
@@ -1,16 +0,0 @@
-<html tal:omit-tag="">
-  <a href=""
-     tal:attributes="href string:${request/URL}?sort-on=${options/name}"
-     tal:content="options/header" />
-  &nbsp;
-  <img src="" width="7" height="4" style="vertical-align: middle"
-       alt="sort ascending"
-       tal:condition="options/isAscending"
-       tal:attributes="src
-           context/++resource++SpreadsheetImages/ascending.gif" />
-  <img src="" width="7" height="4" style="vertical-align: middle"
-       alt="sort ascending"
-       tal:condition="options/isDecending"
-       tal:attributes="src
-           context/++resource++SpreadsheetImages/decending.gif" />
-</html>



More information about the Checkins mailing list