Author: justi9
Date: 2009-06-17 15:44:31 -0400 (Wed, 17 Jun 2009)
New Revision: 3456
Modified:
mgmt/trunk/wooly/python/wooly/demo.py
mgmt/trunk/wooly/python/wooly/forms.py
mgmt/trunk/wooly/python/wooly/forms.strings
mgmt/trunk/wooly/python/wooly/pages.py
mgmt/trunk/wooly/python/wooly/server.py
mgmt/trunk/wooly/python/wooly/widgets.py
mgmt/trunk/wooly/python/wooly/widgets.strings
Log:
* Remove debug print, and return frame from pop_frame
* Parameterize the html_class of WidgetSet
* Use the new, changed debug facility in server
* Add a form tab to wooly-demo
* Include an errors attribute on all Form widgets, and use this to
accumulate form input errors
* Introduce SubmitForm, with the process_$mode regime and means of
remembering how you navigated to it, and ButtonForm, for rendering
any form with a set of buttons
* Introduce a content pane, FormFieldSet, in lieu of welding the
fields in directly as we did in FieldForm, and a new
FieldSubmitForm for something similar to the old behavior
* Introduce a 'check' pass on FormField, FormFieldSet, and
FieldSubmitForm
* Make FormErrors and FormFields aware of what form they are in
* Revise the default form style
Modified: mgmt/trunk/wooly/python/wooly/demo.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/demo.py 2009-06-17 19:33:19 UTC (rev 3455)
+++ mgmt/trunk/wooly/python/wooly/demo.py 2009-06-17 19:44:31 UTC (rev 3456)
@@ -7,6 +7,7 @@
from wooly.server import WebServer
from wooly.pages import HtmlPage
from wooly.widgets import *
+from wooly.forms import *
from wooly.resources import StringCatalog
strings = StringCatalog(__file__)
@@ -86,6 +87,9 @@
self.link_tab = LinkDemo(app, "link")
tabs.add_tab(self.link_tab)
+ self.form_tab = FormDemo(app, "form")
+ tabs.add_tab(self.form_tab)
+
class Introduction(Widget):
def render_title(self, session):
return "Introduction"
@@ -122,3 +126,113 @@
return "Hide that secret"
else:
return "Show me a secret"
+
+class FormDemo(Widget):
+ def __init__(self, app, name):
+ super(FormDemo, self).__init__(app, name)
+
+ self.address_form = self.AddressForm(app, "address")
+ self.add_child(self.address_form)
+
+ self.three_form = self.ThreeForm(app, "threeform")
+ self.add_child(self.three_form)
+
+ def render_title(self, session):
+ return "Forms"
+
+ class AddressForm(FieldSubmitForm):
+ def __init__(self, app, name):
+ super(FormDemo.AddressForm, self).__init__(app, name)
+
+ self.name_ = self.Name(app, "first")
+ self.add_field(self.name_)
+
+ self.line_1 = self.Line1(app, "line1")
+ self.add_field(self.line_1)
+
+ self.line_2 = self.Line2(app, "line2")
+ self.add_field(self.line_2)
+
+ def render_title(self, session):
+ return "Enter your address"
+
+ class Name(StringField):
+ def render_title(self, session):
+ return "Name"
+
+ class Line1(StringField):
+ def render_title(self, session):
+ return "Address Line 1"
+
+ class Line2(StringField):
+ def render_title(self, session):
+ return "Address Line 2"
+
+ class ThreeForm(ModeSet, Frame):
+ def __init__(self, app, name):
+ super(FormDemo.ThreeForm, self).__init__(app, name)
+
+ self.one = self.One(app, "one")
+ self.add_mode(self.one)
+
+ self.two = self.Two(app, "two")
+ self.add_mode(self.two)
+
+ self.three = self.Three(app, "three")
+ self.add_mode(self.three)
+
+ class One(FieldSubmitForm):
+ def __init__(self, app, name):
+ super(FormDemo.ThreeForm.One, self).__init__(app, name)
+
+ field = self.OneField(app, "first")
+ self.add_field(field)
+
+ def process_cancel(self, session):
+ self.parent.three.show(session)
+
+ def process_submit(self, session):
+ self.parent.two.show(session)
+
+ def render_title(self, session):
+ return "Step 1"
+
+ class OneField(StringField):
+ def render_title(self, session):
+ return "Alpha"
+
+ class Two(FieldSubmitForm):
+ def __init__(self, app, name):
+ super(FormDemo.ThreeForm.Two, self).__init__(app, name)
+
+ field = self.TwoField(app, "second")
+ self.add_field(field)
+
+ def process_cancel(self, session):
+ self.parent.one.show(session)
+
+ def process_submit(self, session):
+ self.parent.three.show(session)
+
+ def render_title(self, session):
+ return "Step 2"
+
+ class TwoField(StringField):
+ def render_title(self, session):
+ return "Beta"
+
+ class Three(SubmitForm):
+ def __init__(self, app, name):
+ super(FormDemo.ThreeForm.Three, self).__init__(app, name)
+
+ def process_cancel(self, session):
+ self.parent.two.show(session)
+
+ def process_submit(self, session):
+ self.parent.one.show(session)
+
+ def render_title(self, session):
+ return "Step 3"
+
+ def render_content(self, session):
+ return "All done!"
Modified: mgmt/trunk/wooly/python/wooly/forms.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/forms.py 2009-06-17 19:33:19 UTC (rev 3455)
+++ mgmt/trunk/wooly/python/wooly/forms.py 2009-06-17 19:44:31 UTC (rev 3456)
@@ -9,11 +9,18 @@
def __init__(self, app, name):
super(Form, self).__init__(app, name)
+ self.errors = ListAttribute(app, "errors")
+ self.add_attribute(self.errors)
+
self.form_params = set()
self.origin = Parameter(app, "origin")
self.add_parameter(self.origin)
+ # XXX need a better name
+ def has_errors(self, session):
+ return len(self.errors.get(session))
+
def get_origin(self, session):
origin = self.origin.get(session)
@@ -66,41 +73,35 @@
% (name, value))
class FormError(object):
- def __init__(self, message=None):
+ def __init__(self, message=None, widget=None):
self.message = message
+ self.widget = widget
def get_message(self, session):
return self.message
class FormErrorSet(ItemSet):
- def __init__(self, app, name):
- super(FormErrorSet, self).__init__(app, name)
+ def init(self):
+ super(FormErrorSet, self).init()
- self.__errors = self.Errors(app, "errors")
- self.add_attribute(self.__errors)
+ for anc in self.ancestors:
+ if isinstance(anc, Form):
+ self.form = anc
- def add(self, session, error):
- assert isinstance(error, FormError)
+ self.test(self.form, "Not inside a form")
- self.__errors.get(session).append(error)
-
- def get(self, session):
- return self.__errors.get(session)
-
def get_items(self, session, *args):
- return self.get(session)
+ return self.form.errors.get(session)
def do_render(self, session, *args):
- if self.get(session):
+ items = self.get_items(session, *args)
+
+ if items:
return super(FormErrorSet, self).do_render(session, *args)
def render_item_content(self, session, item):
return item.get_message(session)
- class Errors(Attribute):
- def get_default(self, session):
- return list()
-
class FormInput(Widget):
def __init__(self, app, name, param):
super(FormInput, self).__init__(app, name)
@@ -114,8 +115,7 @@
def init(self):
super(FormInput, self).init()
- if self.param is None:
- raise Exception("Parameter not set for %s" % self)
+ self.test(self.param, "Parameter not set")
assert isinstance(self.param, Parameter)
@@ -123,11 +123,10 @@
if isinstance(anc, Form):
self.form = anc
- if self.form:
- self.form.form_params.add(self.param)
- else:
- print "Warning: FormInput '%s' not inside a form" % self
+ self.test(self.form, "Not inside a form")
+ self.form.form_params.add(self.param)
+
def get(self, session):
return self.param.get(session)
@@ -246,7 +245,7 @@
def __init__(self, app, name):
super(FormButton, self).__init__(app, name, None)
- self.param = BooleanParameter(app, "param")
+ self.param = BooleanParameter(app, "invoked")
self.add_parameter(self.param)
def do_process(self, session, *args):
@@ -296,13 +295,23 @@
def __init__(self, app, name):
super(FormField, self).__init__(app, name)
- self.__errors = FormErrorSet(app, "errors")
- self.add_child(self.__errors)
-
self.css_class = "field"
+ self.form = None
+ def init(self):
+ super(FormField, self).init()
+
+ for anc in self.ancestors:
+ if isinstance(anc, Form):
+ self.form = anc
+
+ self.test(self.form, "Not inside a form")
+
+ def check(self, session):
+ pass
+
def validate(self, session):
- errors = self.__errors.get(session)
+ errors = self.form.errors.get(session)
self.do_validate(session, errors)
@@ -311,12 +320,48 @@
def do_validate(self, session, errors):
pass
- def render_field_help(self, session, *args):
+ def render_help(self, session, *args):
pass
def render_form_field_class(self, session, *args):
return self.css_class
+class FormFieldSet(Widget):
+ def __init__(self, app, name):
+ super(FormFieldSet, self).__init__(app, name)
+
+ self.fields = list()
+
+ def add_field(self, field):
+ assert isinstance(field, FormField)
+
+ self.fields.append(field)
+ self.add_child(field)
+
+ def check(self, session):
+ for field in self.fields:
+ field.check(session)
+
+ # XXX get rid of this
+ def validate(self, session):
+ errors = list()
+
+ for field in self.fields:
+ errors.extend(field.validate(session))
+
+ return errors
+
+ def render_message(self, session, *args):
+ pass
+
+ def render_fields(self, session, *args):
+ writer = Writer()
+
+ for field in self.fields:
+ writer.write(field.render(session))
+
+ return writer.to_string()
+
class ScalarField(FormField):
def __init__(self, app, name, input):
super(ScalarField, self).__init__(app, name)
@@ -444,16 +489,28 @@
class ButtonForm(Form):
def __init__(self, app, name):
- super(ButtonForm. self).__init__(app, name)
+ super(ButtonForm, self).__init__(app, name)
+ self.error_display = FormErrorSet(app, "errors")
+ self.add_child(self.error_display)
+
+ self.content = None
+
self.buttons = list()
def add_button(self, button):
- assert type(button) is FormButton
+ assert isinstance(button, FormButton)
self.buttons.append(button)
self.add_child(button)
+ def render_errors(self, session, *args):
+ if self.errors.get(session):
+ return self.error_display.render(session, *args)
+
+ def render_content(self, session, *args):
+ return self.content.render(session, *args)
+
def render_buttons(self, session, *args):
writer = Writer()
@@ -462,30 +519,87 @@
return writer.to_string()
-class FieldForm(Form):
+class SubmitForm(ButtonForm):
def __init__(self, app, name):
- super(FieldForm, self).__init__(app, name)
+ super(SubmitForm, self).__init__(app, name)
- self.fields = list()
+ self.return_url = Parameter(app, "return")
+ self.add_parameter(self.return_url)
- def add_field(self, field):
- assert isinstance(field, FormField)
+ self.cancel = self.Cancel(app, "cancel")
+ self.cancel.set_tab_index(201)
+ self.add_button(self.cancel)
- self.fields.append(field)
- self.add_child(field)
+ self.submit = self.Submit(app, "submit")
+ self.submit.set_tab_index(200)
+ self.add_button(self.submit)
- def validate(self, session):
- errors = list()
+ def submit(self, session):
+ self.submit.set(session, True)
- for field in self.fields:
- errors.extend(field.validate(session))
+ def cancel(self, session):
+ self.cancel.set(session, True)
- return errors
+ def check(self, session):
+ pass
- def render_fields(self, session, *args):
- writer = Writer()
+ def do_process(self, session):
+ if self.cancel.get(session):
+ self.cancel.set(session, False)
- for field in self.fields:
- writer.write(field.render(session))
+ self.process_cancel(session)
+ elif self.submit.get(session):
+ self.submit.set(session, False)
- return writer.to_string()
+ self.process_submit(session)
+ else:
+ self.process_display(session)
+
+ # XXX get rid of this?
+ def process_return(self, session):
+ url = self.return_url.get(session)
+ self.page.set_redirect_url(session, url)
+
+ def process_cancel(self, session):
+ self.process_return(session)
+
+ def process_submit(self, session):
+ pass
+
+ def process_display(self, session):
+ pass
+
+ def render_cancel_content(self, session):
+ return "Cancel"
+
+ def render_submit_content(self, session):
+ return "Submit"
+
+ class Cancel(FormButton):
+ def render_class(self, session, *args):
+ return "cancel"
+
+ def render_content(self, session):
+ return self.parent.render_cancel_content(session)
+
+ class Submit(FormButton):
+ def render_class(self, session):
+ return "submit"
+
+ def render_content(self, session):
+ return self.parent.render_submit_content(session)
+
+class FieldSubmitForm(SubmitForm):
+ def __init__(self, app, name):
+ super(FieldSubmitForm, self).__init__(app, name)
+
+ self.content = FormFieldSet(app, "fields")
+ self.add_child(self.content)
+
+ def add_field(self, field):
+ self.content.add_field(field)
+
+ def check(self, session):
+ super(FieldSubmitForm, self).check(session)
+
+ self.content.check(session)
Modified: mgmt/trunk/wooly/python/wooly/forms.strings
===================================================================
--- mgmt/trunk/wooly/python/wooly/forms.strings 2009-06-17 19:33:19 UTC (rev 3455)
+++ mgmt/trunk/wooly/python/wooly/forms.strings 2009-06-17 19:44:31 UTC (rev 3456)
@@ -1,12 +1,40 @@
[FormErrorSet.css]
-ul.errors {
- color: red;
+div.FormErrorSet {
+ margin: 1em auto;
+ border: none;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+ padding: 1em;
+ width: 36em;
+ background-color: #fbb;
font-weight: bold;
}
+div.FormErrorSet > p {
+ margin: 0;
+}
+
+div.FormErrorSet > p > img {
+ vertical-align: -75%;
+ margin: 0 0.5em 0 0;
+}
+
+div.FormErrorSet > ul {
+ list-style: disc;
+ margin: 0 0 0 36px;
+ padding: 0.66em 2em;
+}
+
[FormErrorSet.html]
-<ul class="errors">{items}</ul>
+<div class="FormErrorSet">
+ <p>
+ <img src="resource?name=warning-36.png"/>
+ Input errors
+ </p>
+ <ul>{items}</ul>
+</div>
+
[FormErrorSet.item_html]
<li>{item_content}</li>
@@ -70,60 +98,62 @@
[OptionInputSet.item_html]
<option value="{item_value}"
{item_selected_attr}>{item_content}</option>
-[FormField.css]
-div.field {
- padding: 0;
- margin: 0;
+[FormField.html]
+<tr>
+ <th>
+ <div class="title">{title}</div>
+ <div class="help">{help}</div>
+ </th>
+ <td>{inputs}</td>
+</tr>
+
+[FormFieldSet.css]
+table.FormFieldSet {
+ width: 100%;
+ border-collapse: collapse;
}
-div.field div.title {
- font-weight: bold;
- font-size: 0.9em;
- float: left;
- margin: 0 1em 1em 0;
+table.FormFieldSet > tbody > tr {
+ padding: 2em;
+ border-top: 1px dotted #ccc;
}
-div.field div.field_help {
- font-size: 0.9em;
- color: #222;
+
+table.FormFieldSet > tbody > tr > th,
+table.FormFieldSet > tbody > tr > td {
+ padding: 0.5em 0;
+ border-top: 1px dotted #ccc;
+ margin: 0.25em 0;
}
-div.field div.inputs {
- margin: 0 0 1em 1em;
+table.FormFieldSet > tbody > tr:first-child > th {
+ border-top: hidden;
+ width: 25%;
}
-div.field div.clear_left {
- clear: left;
+
+table.FormFieldSet > tbody > tr:first-child > td {
+ border-top: hidden;
+ width: 75%;
}
-/* optional styles for displaying a FormField */
-div.compact {
- padding: 0;
- margin: 0;
+table.FormFieldSet > tbody > tr > th {
+ width: 25%;
+ text-align: left;
+ vertical-align: top;
}
-div.compact div.title {
- float: left;
- margin: 0;
- width: 6em;
+
+table.FormFieldSet > tbody > tr > th > div.title {
+ font-weight: bold;
+ font-size: 0.9em;
}
-div.compact div.field_help {
- font-size: 0.9em;
- color: #222;
+
+table.FormFieldSet > tbody > tr > td {
+ width: 75%;
}
-div.compact div.inputs {
- margin-left: 1em;
-}
-div.compact.first {
- margin-top: 1em;
-}
-div.compact.last {
- margin-bottom: 1em;
-}
-[FormField.html]
-<div class="{form_field_class}">
- <div class="title">{title}</div> <div
class="field_help">{field_help}</div><div
class="clear_left"></div>
- {errors}
- <div class="inputs">{inputs}</div><div
style="clear:left;"><!-- --></div>
-</div>
+[FormFieldSet.html]
+<table class="FormFieldSet">
+ <tbody>{fields}</tbody>
+</table>
[RadioFieldOption.html]
<div>
@@ -138,3 +168,54 @@
tabindex="{tab_index}" {checked_attr} {disabled_attr}/>
<label for="{id}">{title}</label>
</div>
+
+[ButtonForm.css]
+form.ButtonForm {
+ background-color: #fafafa;
+ border: 2px solid #fff;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+}
+
+form.ButtonForm > div.title {
+ font-weight: bold;
+ font-size: 1.1em;
+ background-color: #e7e7f7;
+ padding: 0.5em 0.75em;
+ margin-bottom: 0.5em;
+ -moz-border-radius: 0.35em 0.35em 0 0;
+ -webkit-border-radius: 0.35em 0.35em 0 0;
+}
+
+form.ButtonForm > div.content {
+ margin: 0.5em 1em;
+}
+
+form.ButtonForm > div.buttons {
+ text-align: right;
+ background-color: #e7e7f7;
+ padding: 0.5em;
+ margin-top: 0.5em;
+ -moz-border-radius: 0 0 0.35em 0.35em;
+ -webkit-border-radius: 0 0 0.35em 0.35em;
+}
+
+form.ButtonForm > div.buttons > button {
+ margin: 0 0 0 0.5em;
+}
+
+[ButtonForm.html]
+<form id="{id}" class="ButtonForm" method="post"
action="?">
+ <div class="title">{title}</div>
+ <div class="content">
+ {errors}
+ {content}
+ </div>
+ <div class="buttons">{buttons}</div>
+ <div>{hidden_inputs}</div>
+</form>
+<script type="text/javascript">
+//<![CDATA[
+ wooly.doc().elembyid("{id}").node.elements[0].focus();
+//]]>
+</script>
Modified: mgmt/trunk/wooly/python/wooly/pages.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/pages.py 2009-06-17 19:33:19 UTC (rev 3455)
+++ mgmt/trunk/wooly/python/wooly/pages.py 2009-06-17 19:44:31 UTC (rev 3456)
@@ -39,8 +39,8 @@
def pop_frame(self, session):
frame = self.get_frame(session)
- #print "Popping current frame", frame
self.set_frame(session, frame.frame)
+ return frame
def set_default_frame(self, frame):
self.page_frame.default = frame
Modified: mgmt/trunk/wooly/python/wooly/server.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/server.py 2009-06-17 19:33:19 UTC (rev 3455)
+++ mgmt/trunk/wooly/python/wooly/server.py 2009-06-17 19:44:31 UTC (rev 3456)
@@ -181,13 +181,12 @@
writer = Writer()
writer.write("APPLICATION ERROR\n")
- writer.write("\n----- python trace -----\n")
- print_exc(None, writer)
if session.debug:
- writer.write("\n----- process trace -----\n")
- session.debug.print_process_calls(writer)
- writer.write("\n----- render trace -----\n")
- session.debug.print_render_calls(writer)
+ writer.write("\n----- wooly -----\n\n")
+ session.debug.write(writer)
+ writer.write("\n----- python -----\n\n")
+ print_exc(None, writer)
+
return writer.to_string()
Modified: mgmt/trunk/wooly/python/wooly/widgets.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/widgets.py 2009-06-17 19:33:19 UTC (rev 3455)
+++ mgmt/trunk/wooly/python/wooly/widgets.py 2009-06-17 19:44:31 UTC (rev 3456)
@@ -164,6 +164,8 @@
def __init__(self, app, name):
super(ItemSet, self).__init__(app, name)
+ self.html_class = ItemSet.__name__
+
self.items = Attribute(app, "items")
self.add_attribute(self.items)
Modified: mgmt/trunk/wooly/python/wooly/widgets.strings
===================================================================
--- mgmt/trunk/wooly/python/wooly/widgets.strings 2009-06-17 19:33:19 UTC (rev 3455)
+++ mgmt/trunk/wooly/python/wooly/widgets.strings 2009-06-17 19:44:31 UTC (rev 3456)
@@ -51,7 +51,7 @@
<li><a href="{tab_href}"
{tab_class}>{tab_content}</a></li>
[WidgetSet.html]
-<ul class="{class}">{widgets}</ul>
+<ul id="{id}" class="{class}">{widgets}</ul>
[WidgetSet.widget_html]
<li>{widget}</li>
@@ -74,7 +74,7 @@
<a href="{href}" class="Toggle {state}">{content}</a>
[ItemSet.html]
-<ul class="ItemSet">{items}</ul>
+<ul id="{id}" class="{class}">{items}</ul>
[ItemSet.item_html]
<li>{item_content}</li>
@@ -180,16 +180,20 @@
<tr><th>{title}</th><td>{value}</td></tr>
[ActionSet.css]
-ul.ActionSet li {
+ul.ActionSet {
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
}
ul.ActionSet a:before, ul.ActionSet span:before {
- content: "\00BB \0020";
- font-weight: bold;
- color: #dc9f2e;
+ content: "\00BB \0020";
+ font-weight: bold;
+ color: #dc9f2e;
}
+
ul.ActionSet span {
- color: #666;
+ color: #666;
}
[ActionSet.html]