Package: zc.ajaxform
********************Ajax Support********************The zc.ajaxform package provides framework to support:- A single-class application model- Nested-application support- Integration with zope.formlib.. contents::Detailed Documentation**********************Application support===================The zc.ajaxform.application module provides support for writing ajax[#ajax]_ applications.  This framework started out as an experiment insimplifying writing applications with Zope 3.  I was frustrated withZCML situps and generally too much indirection.  I ended up with amodel that I'm pretty happy with.  It might not be for everybody. :)The basic idea is that an application can be provided using a singleZope 3 view plus necessary resource-library definitions.  This viewhas a URL.  It typically provides many ajax methods whose URLs have theview URL as a base.Many applications can be implemented using a simple class that can beregistered as a view.Let's look at a simple stupid application. :)::import zc.ajaxform.applicationimport zope.exceptionsclass Calculator(zc.ajaxform.application.Trusted,zc.ajaxform.application.Application):resource_library_name = None@zc.ajaxform.application.jsonpagedef about(self):return 'Calculator 1.0'@zc.ajaxform.application.jsonpagedef operations(self):return ['add', "subtract"]@zc.ajaxform.application.jsonpagedef value(self):return dict(value=getattr(self.context, 'calculator_value', 0))def do_add(self, value):value += getattr(self.context, 'calculator_value', 0)self.context.calculator_value = valuereturn dict(value=value)@zc.ajaxform.application.jsonpagedef add(self, value):if not isinstance(value, int):return dict(error="The value must be an integer!")return self.do_add(value)@zc.ajaxform.application.jsonpagedef subtract(self, value):if not isinstance(value, int):raise zope.exceptions.UserError("The value must be an integer!")return self.do_add(-value)@zc.ajaxform.application.jsonpagedef noop(self):pass@zc.ajaxform.application.pagedef none(self):return "null"@zc.ajaxform.application.jsonpagedef echo_form(self):def maybe_file(v):if hasattr(v, 'read'):return ("<File upload name=%r content-type=%r size=%r>"% (v.filename, v.headers['content-type'], len(v.read())))else:return vreturn dict((name, maybe_file(v))for (name, v) in self.request.form.items())@zc.ajaxform.application.jsonpagedef doh(self):raise TypeError("Doh!")We subclass zc.ajaxform.application.Trusted. This is a minimalbase class that provides a constructor that takes a context and arequest and removes the security proxy from the context.  Itoverrides the constructor from zc.ajaxform.application.Application.We also subclass zc.ajaxform.application.Application.  This is a baseclass that provides:- a basic constructor that takes context and request arguments and setscorresponding attributes,- traversal to attributes that provide IBrowserPublisher withconversion of dots to underscores,- a default "page" named index.html,- a template method that returns an HTML page with an empty head.- an index_html method that loads a resource library and calls thetemplate,- an interface declaration that it provides IBrowserPublisher, and- an adapter declaration that adaptszope.traversing.interfaces.IContainmentRoot andzope.publisher.interfaces.browser.IBrowserRequest.The main goals of this base class are to make it easy to loadJavascript and to make it easy to define ajax methods to support theJavascript. For that reason, we provide a traverser that traverses toobject attributes that provide IBrowserPublisher.  Thezc.ajaxform.application.jsonpage decorator is also an important part ofthis. It makes methods accessible and automatically marshals theirresult to JSON [#jsoninput]_.  There's also azc.ajaxform.application.page decorator that makes methods accessiblewithout the automatic marshalling.  The use of a default page, ratherthan just a __call__ method is to cause the URL base to be the view,rather than the view's context.  This allows the Javascript code touse relative URLs to refer to the ajax methods.The class expects subclasses to define a resource_library_nameattribute [#missing_resource_library_name]_.  For these applications,you pretty much always want to use an associated Javascript file andother resources (supporting JS, CSS, etc.).  You can suppress the useof the resource library by setting the value of this attribute toNone.For applications that build pages totally in Javascript, the defaulttemplate is adequate.  For applications that need to supportnon-Javascript-enabled browsers, that want to support search-engineoptimization [#sso]_, or that want to provide some Javascript dataduring the initial page load, a custom template can be provided bysimply overriding the template method with a page template or a methodthat calls one.The view can be registered with a simple adapter registration:::<configure xmlns="http://namespaces.zope.org/zope"><adapter name="calculator.html"factory="zc.ajaxform.calculator_example.Calculator"permission="zope.View"/></configure>If we wanted to register it for an object other than the anIContainmentRoot, we could just provide specifically adapted interfacesor classes in the registration.Let's access the calculator with a test browser>>> import zope.testbrowser.testing>>> browser = zope.testbrowser.testing.Browser()>>> browser.open('http://localhost/')Traceback (most recent call last):...HTTPError: HTTP Error 401: UnauthorizedBecause our view was registered to require zope.View, the request wasunauthorized.  Let's login. In the demo setup, we login by justproviding a login form variable.>>> browser.open('http://localhost/calculator.html?login')>>> print browser.contents # doctest: +NORMALIZE_WHITESPACE<html><head><base href="http://localhost/calculator.html/index.html" /></head></html>We registered our view as calculator.html. Because of the way it sets thebrowser default page for itself, it becomes the base href for thepage.  This allows us to access ajax methods using relative URLs.Our calculator view provides a value method.  It uses thezc.ajaxform.application.jsonpage decorator. This does 2 things:- Arranges that the method can be traversed to,- marshals the result to JSON.The way results are marshalled to JSON deserves someexplanation.  To support automation of ajax calls, we:- Always return objects- If there is an error, we include:- an error property providing an error messahe, and/or- when handling form submissions, an errors property with am object valuemapping field names to field-specific error messages.::>>> import simplejson>>> browser.open('http://localhost/@@calculator.html/value')>>> simplejson.loads(browser.contents){u'value': 0}>>> browser.open('http://localhost/@@calculator.html/add?value=hi')>>> simplejson.loads(browser.contents){u'error': u'The value must be an integer!'}Things other than a dictionary can be returned:>>> browser.open('http://localhost/@@calculator.html/about')>>> simplejson.loads(browser.contents)u'Calculator 1.0'>>> browser.open('http://localhost/@@calculator.html/operations')>>> simplejson.loads(browser.contents)[u'add', u'subtract']If you want to marshal JSON yourself, you can use thezc.ajaxform.application.jsonpage decorator:>>> browser.open('http://localhost/@@calculator.html/none')An alternative way to return errors is to raise user errors, as isdone by the subtract method in our example:>>> browser.open('http://localhost/@@calculator.html/subtract?value=hi')>>> simplejson.loads(browser.contents){u'error': u'The value must be an integer!'}This works because there is a view registered forzope.exceptions.interfaces.IUserError, andzc.ajaxform.interfaces.IAjaxRequest.Testing support===============zc.ajaxform.testing has some helper functions to make it easier to testajax calls.The zc.ajaxform.testing.FormServer class provides some convenience formaking ajax calls in which data are sent as form data and returned asJSON.  The class takes a browser and returns an object that can becalled to make server calls:>>> import zc.ajaxform.testing, pprint>>> server = zc.ajaxform.testing.FormServer(browser)>>> pprint.pprint(server('/calculator.html/echo_form',...               {'a': 1.0}, b=[1, 2, 3], c=True, d='d', e=u'e\u1234'...               ), width=1){u'a': u'1.0',u'b': [u'1',u'2',u'3'],u'c': u'1',u'd': u'd',u'e': u'e\u1234'}When we call the server, we pass a URL to invoke, which may berelative, a optional dictionary of parameter values, and optionalkeyword arguments.Note that the application will recieve data as strings, which is whatwe see echoed back in the example above.If the application is written using Zope, then we can enable Zope formmarshalling, by passing a True value when we create the server:>>> server = zc.ajaxform.testing.FormServer(browser, True)>>> pprint.pprint(server('/calculator.html/echo_form',...               {'a': 1.0}, b=[1, 2, 3], c=True, d='d', e=u'e\u1234'...               ), width=1){u'a': 1.0,u'b': [1,2,3],u'c': True,u'd': u'd',u'e': u'e\u1234'}>>> pprint.pprint(server('/calculator.html/add', {'value': 1}), width=1){u'value': 1}>>> pprint.pprint(server('/calculator.html/add', value=1), width=1){u'value': 2}The methods called are assumed to return JSON and the resulting datais converted back into Python.The function pprint method combines pprint and calling:>>> server.pprint('/calculator.html/add', {'value': 1}){u'value': 3}>>> server.pprint('/calculator.html/add', value=1){u'value': 4}>>> server.pprint('/calculator.html/echo_form',...               {'a': 1.0}, b=[1, 2, 3], c=True, d='d', e=u'e\u1234'...               ){u'a': 1.0,u'b': [1,2,3],u'c': True,u'd': u'd',u'e': u'e\u1234'}In the future, there will be versions of these functions that senddata as JSON.We can include file-upload data by including a 3-tuple with a filename, a content type, and a data string:>>> server.pprint('/calculator.html/echo_form',...               b=[1, 2, 3], c=True, d='d',...               file=('foo.xml', 'test/xml', '<foo></foo>'),...               ){u'b': [1,2,3],u'c': True,u'd': u'd',u'file': u"<File upload name=u'foo.xml' content-type='test/xml' size=11>"}as a convenience, you can pass a URL string to the server constructor,which will create a browser for you that has opened that URL.  You canalso omit the brower and an unopened browser will be created.>>> server = zc.ajaxform.testing.FormServer(...     'http://localhost/calculator.html?login')>>> server.browser.url'http://localhost/calculator.html?login'>>> server.pprint('/calculator.html/echo_form', x=1){u'x': u'1'}>>> server = zc.ajaxform.testing.FormServer(zope_form_marshalling=True)>>> server.browser.open('http://localhost/calculator.html?login')>>> server.pprint('/calculator.html/echo_form', x=1){u'x': 1}In the example above, we didn't provide a browser, but we provided thezope_form_marshalling flag as a keyword option... Edge case: we can't traverse to undecorated methods:>>> server.pprint('/calculator.html/do_add', value=1)Traceback (most recent call last):...HTTPError: HTTP Error 404: Not Found"Library" applications======================The "application" model described above is pretty appealing in itssimplicity -- at least to me. :)  Usually, we'd like to make outapplications a bit more flexible in their use.  In particular, weoften want to assemble applications together. At the Javascript level,this often means having an application return a panel that can be usedin some higher-level layout.  At the server level, we need to providea way to access application logic within some larger context.  Thereare two parts to this:1. The containing application needs to support traversal to thesub-application.2. The subapplication needs to know how it was traversed to, at leastif it generates URLs.  For example, the form machinery [#forms]_generates URLs for action handlers.Sub-application should expose the URL needed to access then as abase_href attribute. This is usually a relative URL relative to the baseapplication.There are a number of classes defined in zc.ajaxform.application thathelp with creating sub-applications:SubApplicationThis class, which Application subclasses, provides traversal toattributes that provide IBrowserPublisher.  It also stampsIAjaxRequest on the request object when an object is traversed[#iajaxrequest]_ .(Maybe this request stamping should be done further down thetraversal chain or perhaps only done if X-Requested-With isxmlhttprequest.)PublicTraversableThis class provides security declarations that allow objects to betraversable publically.  This is appropriate for sub-applicationsthat want the same protections as the object being traversed.Let's look at our calculator example as a subapplication:::import zc.ajaxform.applicationimport zope.exceptionsclass Container(zc.ajaxform.application.Application):resource_library_name = None@propertydef calc(self):return Calculator(self.context, self.request, base_href='calc')class Calculator(zc.ajaxform.application.Trusted,zc.ajaxform.application.SubApplication,zc.ajaxform.application.PublicTraversable,):@zc.ajaxform.application.jsonpagedef operations(self):return [['add', self.base_href+'/add'],['add', self.base_href+'/subtract'],]@zc.ajaxform.application.jsonpagedef value(self):return dict(value=getattr(self.context, 'calculator_value', 0))def do_add(self, value):value += getattr(self.context, 'calculator_value', 0)self.context.calculator_value = valuereturn dict(value=value)@zc.ajaxform.application.jsonpagedef add(self, value):if not isinstance(value, int):return dict(error="The value must be an integer!")return self.do_add(value)@zc.ajaxform.application.jsonpagedef subtract(self, value):if not isinstance(value, int):raise zope.exceptions.UserError("The value must be an integer!")return self.do_add(-value)Here, we've defined a container application that simply providestraversal to a calculator subapplication as a static property.  Itcreates the calculator with the application's context and request.  Itpasses a base_href as a keyword argument, which SubApplication'sconstructor accepts.  Our ZCML configuration is pretty simple:::<configure xmlns="http://namespaces.zope.org/zope"><include package="zope.app.component" file="meta.zcml" /><adapter name="container.html"factory="zc.ajaxform.calculator_subapplication_example.Container"permission="zope.View"/></configure>Using the container application, we access the calculator via thecontainer:>>> server.pprint('http://localhost/@@container.html/calc/add', value=1){u'value': 5}We've updated the operations method to include the URL for eachoperation, which is computed based on the base_href:>>> server.pprint('http://localhost/@@container.html/calc/operations')[[u'add',u'calc/add'],[u'add',u'calc/subtract']]Note that we didn't make any security declarations for the Calculatorclass.  We're relying on the protection for the container.  If werestart the browser, we see, indeed, that we can't access thecalculator:>>> server = zc.ajaxform.testing.FormServer()>>> server.pprint('http://localhost/@@container.html/calc/operations'){u'session_expired': True}Dynamic Traversal=================In the previous example, we traversed to a sub-application using astatic property.  Sometimes, we need to traverse dynamically.  Wemight have a container application with a variable number ofsubapplications. Examples include a portlet container and a system formanaging user-defined data types.  In the later case, as users definenew data types, one or more applications get defined for each type.zc.ajaxform.application provides a helper descriptor that allows customtraversers to be implemented with simple Python methods.  Let's lookat a simple example.>>> import zc.ajaxform.application>>> class Foo:...     def __str__(self):...         return 'a '+self.__class__.__name__......     @zc.ajaxform.application.traverser...     def demo_traverse(self, request, name):...         return "traverse: %s %s %s" % (self, request, name)This is a rather silly traverser for demonstration purposes that justreturnes a transformed name.>>> foo = Foo()>>> foo.demo_traverse.publishTraverse("a request", "xxx")'traverse: a Foo a request xxx'We can still call the method:>>> foo.demo_traverse("a request", "xxx")'traverse: a Foo a request xxx'The method provides IBrowserPublisher:>>> import zope.publisher.interfaces.browser>>> zope.publisher.interfaces.browser.IBrowserPublisher.providedBy(...     foo.demo_traverse)TrueThe descriptor has a security declaration that allows it to betraversed but not called from untrusted code:>>> import zope.security.checker>>> checker = zope.security.checker.getChecker(...     zope.security.checker.ProxyFactory(foo.demo_traverse))>>> checker.get_permissions{'publishTraverse': Global(CheckerPublic,zope.security.checker)}>>> checker.set_permissions{}Acquisition===========Applications and sub-applications have __parent__ properties thatreturn their context.  This is to support frameworks that ise__parent__ to perform acquisition.>>> class MyApp(zc.ajaxform.application.Application):...     pass>>> myapp = MyApp(foo, None)>>> myapp.__parent__ is fooTrue>>> class MySubApp(zc.ajaxform.application.SubApplication):...     pass>>> mysubapp = MySubApp(foo, None)>>> mysubapp.__parent__ is fooTrueSystem Errors=============System errors will be rendered as json.>>> server = zc.ajaxform.testing.FormServer(...     'http://localhost/calculator.html?login')>>> server('/calculator.html/doh')Traceback (most recent call last):...HTTPError: HTTP Error 500: Internal Server Error>>> pprint.pprint(simplejson.loads(server.browser.contents), width=1){u'error': u'TypeError: Doh!'}.. [#ajax] Technically, these aren't really AJAX applications, sincewe rarely. if ever, use XML as a serialization format.  Toemphasize this I'll use lower-case "ajax" to refer to the genericapproach of making low-level calls from Javascript rather thandoing page loads... [#jsoninput] In the near future, there will also be support forJSON method input.  This will provide a number of benefits:- It will provide more automatic marshaling of non-stringdata. Now, we either have to de-marshal in the server applicationcode or embed marshaling data into parameter names in clientcode.- It will allow richer data structures than is practical with form data.- It will probably allow faster ajax requests because:- Server-side de-marshalling is done with highly optimized codein simplejson.- We will assume that data passed are valid method arguments andavoid method introspection... [#missing_resource_library_name] A custom attribute error messageis used if this attribute is missing that tries to be moreinformative than the default attribute error... [#sso]  For search-engine optimization, one generally wants acontent page to actually contain its content.  If one depends onJavascript-enabled browsers, one can improve performance andsearch-engine optimization by adding ancilary data in Javascript,so as not to dilute the content... [#forms] See form.txt... [#iajaxrequest] Traversing into a subapplication adds IAjaxRequest to thelist of interfaces provided by the request.>>> import zc.ajaxform.application>>> import zc.ajaxform.interfaces>>> import zope.publisher.browser>>> request = zope.publisher.browser.TestRequest()>>> class SubApp(zc.ajaxform.application.SubApplication):...      @zc.ajaxform.application.jsonpage...      def foo(self):...          return 'bar'>>> subapp = SubApp(object(), request)Now let's try traversing into the subapplication:>>> zc.ajaxform.interfaces.IAjaxRequest.providedBy(request)False>>> subapp.publishTraverse(request, 'foo')()'"bar"'>>> zc.ajaxform.interfaces.IAjaxRequest.providedBy(request)TrueNote that the request keeps any provided interfaces:>>> request = zope.publisher.browser.TestRequest()>>> class IMyInterface(zope.interface.Interface):...     pass>>> zope.interface.directlyProvides(request, IMyInterface)>>> subapp.publishTraverse(request, 'foo')()'"bar"'>>> IMyInterface.providedBy(request)TrueSystem Error Logging====================When an error is generated, zope.app.publishing checks to see if theerror handler provides the System Error interface. If so, zope.app.publishinglogs the error.Rather than use this indirect method of logging, there is an explicitstatement in ExceptionView that imports the logging module and logs and erroritself. We can test this with the zope.testing.loggingsupport functions.First we set up loggingsupport. This keeps track of all error messageswritten via the module passed as the parameter. In this case, zc.ajaxform.>>> import logging>>> import zope.testing.loggingsupport>>> log_handler = zope.testing.loggingsupport.InstalledHandler('zc.ajaxform')Then we create an error.>>> server = zc.ajaxform.testing.FormServer(...     'http://localhost/calculator.html?login')>>> server('/calculator.html/doh')Traceback (most recent call last):...HTTPError: HTTP Error 500: Internal Server Error...And check to see that it was logged.>>> print log_handlerzc.ajaxform.application ERRORSysError created by zc.ajaxformForm Processing===============zc.ajaxform.form provides support for server-generated forms based onthe zope.formlib library.Forms are meant to be used as parts of larger applications.  A formprovides output of JSON data for building javascript forms.  Forms alsoprovide validation and call actions with validated data to performactions on form submit.To create a form, just create a form class as a subclass ofzc.ajaxform.form.Form. This base class provides:- an ajax __call__ method that returns a form definition,- traversal to form actions, in much the same way thatzc.ajaxform.application.Application [#application]_ provides traversalto json methods,- a definitions method that can be used by ajax methods to get a formdefinition as Python data, and- a getObjectData method for getting initial form data from anexisting object.Here's a simple example:::import zc.ajaxform.applicationimport zc.ajaxform.interfacesimport zc.ajaxform.widgetsimport zc.ajaxform.formimport zope.componentimport zope.interfaceimport zope.formlibimport zope.schemaclass IAddress(zope.interface.Interface):street = zope.schema.TextLine(title = u"Street",description = u"The street",)city = zope.schema.TextLine(title = u"City",description = u"The city",)awesomeness = zope.schema.Int(title = u"Awesomeness",description = u"The awesomeness on a scale of 1 to 10",min = 1,max = 10,)class Pets(zc.sourcefactory.basic.BasicSourceFactory):def getValues(self):return (u'Dog', u'Cat', u'Fish')class Pet(zope.schema.TextLine):"""A textline representing a pet.This is just a textline, but we also have a source of common pets thatthe user can choose from."""class IPerson(zope.interface.Interface):first_name = zope.schema.TextLine(title = u"First name",description = u"Given name.",default= u'Happy')last_name = zope.schema.TextLine(title = u"Last name",description = u"Family name.",default= u'Camper')favorite_color = zope.schema.TextLine(title = u"Favorite color",required = False,default= u'Blue')age = zope.schema.Int(title = u"Age",description = u"Age in years",min = 0,max = 200,default= 23)happy = zope.schema.Bool(title = u"Happy",description = u"Are they happy?",default= True)pet = Pet(title=u'Pet',description=u'This person\'s best friend.',required=False,)temperment = zope.schema.Choice(title = u"Temperment",description = u"What is the person like?",values = ['Nice', 'Mean', 'Ornery', 'Right Neighborly'],default = u'Right Neighborly')weight = zope.schema.Decimal(title = u"Weight",description = u"Weight in lbs?")description = zope.schema.Text(title = u"Description",description = u"What do they look like?",default = u'10ft tall\nRazor sharp scales.')secret = zope.schema.TextLine(title = u"Secret Key",description = u"Don't tell anybody",default = u'5ecret sauce')siblings = zope.schema.Int(title = u"Siblings",description = u"Number of siblings",min = 0,max = 8,default = 1)addresses = zope.schema.List(title = u'Addresses',description = u"All my wonderful homes",value_type = zope.schema.Object(schema=IAddress),default= [{'street':'123 fake street','city': 'fakeville','awesomeness': '9'},{'street':'345 false street','city': 'falsetown','awesomeness': '9001'}])other = zope.schema.Text(title = u"Other",description = u"Any other notes",default = u"I've got a magic toenail")class Person:zope.interface.implements(IPerson)def __init__(self, first_name, last_name, favorite_color, age, happy,pet, temperment, weight, description, secret, siblings,addresses, other):self.first_name = first_nameself.last_name = last_nameself.favorite_color = favorite_colorself.age = ageself.happy = happyself.pet = petself.temperment = tempermentself.weight = weightself.description = descriptionself.secret = secretself.siblings = siblingsself.addresses = addressesself.other = otherclass FormExample(zc.ajaxform.application.Application):resource_library_name = Noneclass ExampleForm(zc.ajaxform.form.Form):leftFields = ('first_name', 'last_name', 'age', 'other')form_fields = zope.formlib.form.Fields(IPerson)form_fields['secret'].custom_widget = zc.ajaxform.widgets.Hiddenform_fields['siblings'].custom_widget = zc.ajaxform.widgets.NumberSpinner@zope.formlib.form.action("Register")def register(self, action, data):person = Person(**data)return dict(data = data,self_class_name = self.__class__.__name__,self_app_class_name = self.app.__class__.__name__,self_context_class_name = self.context.__class__.__name__)class PetWidget(zc.ajaxform.widgets.ComboBox):zope.component.adapts(Pet,zc.ajaxform.interfaces.IAjaxRequest)zope.interface.implements(zc.ajaxform.interfaces.IInputWidget)def __init__(self, context, request):super(PetWidget, self).__init__(context, Pets(), request)Note that we've nested our form definition in an application.  We candefine the form class elsewhere and use it, but if a form is only usedin an application, then it's often easiest to define it within anapplication class.  Forms are instantiated by calling them with asingle argument.  This argument, the application, becomes the form's `app`attribute.  The application's context becomes the form's context.  Formclasses are automatically instantiated when a form class is assigned toan attribute in a class and accessed through an instance[#form_classes_are_descriptors]_.Let's try accessing our form, which can be found in its python formin form_example.py:>>> import zope.testbrowser.testing>>> from zc.ajaxform.testing import call_form, print_form>>> browser = zope.testbrowser.testing.Browser()>>> browser.open('http://localhost/form.html?login')>>> print_form(browser, 'http://localhost/form.html/ExampleForm'){u'definition': {u'actions': [{u'label': u'Register',u'name': u'ExampleForm.actions.register',u'url': u'ExampleForm/register'}],u'left_fields': {u'addresses': False,u'age': True,u'description': False,u'favorite_color': False,u'first_name': True,u'happy': False,u'last_name': True,u'other': True,u'pet': False,u'secret': False,u'siblings': False,u'temperment': False,u'weight': False},u'prefix': u'ExampleForm',u'widgets': [{u'fieldHint': u'Given name.',u'fieldLabel': u'First name',u'id': u'first_name',u'minLength': 0,u'name': u'first_name',u'required': True,u'value': u'Happy',u'widget_constructor': u'zope.schema.TextLine'},{u'fieldHint': u'Family name.',u'fieldLabel': u'Last name',u'id': u'last_name',u'minLength': 0,u'name': u'last_name',u'required': True,u'value': u'Camper',u'widget_constructor': u'zope.schema.TextLine'},{u'fieldHint': u'',u'fieldLabel': u'Favorite color',u'id': u'favorite_color',u'minLength': 0,u'name': u'favorite_color',u'required': False,u'value': u'Blue',u'widget_constructor': u'zope.schema.TextLine'},{u'allowBlank': False,u'fieldHint': u'Age in years',u'fieldLabel': u'Age',u'field_max': 200,u'field_min': 0,u'id': u'age',u'name': u'age',u'required': True,u'value': u'23',u'widget_constructor': u'zope.schema.Int'},{u'fieldHint': u'Are they happy?',u'fieldLabel': u'Happy',u'id': u'happy',u'name': u'happy',u'required': True,u'value': True,u'widget_constructor': u'zope.schema.Bool'},{u'fieldHint': u"This person's best friend.",u'fieldLabel': u'Pet',u'id': u'pet',u'name': u'pet',u'required': False,u'values': [[u'c935d187f0b998ef720390f85014ed1e',u'Dog'],[u'fa3ebd6742c360b2d9652b7f78d9bd7d',u'Cat'],[u'071642fa72ba780ee90ed36350d82745',u'Fish']],u'widget_constructor': u'zc.ajaxform.widgets.ComboBox'},{u'allowBlank': False,u'fieldHint': u'What is the person like?',u'fieldLabel': u'Temperment',u'hiddenName': u'temperment.value',u'id': u'temperment',u'name': u'temperment',u'required': True,u'value': u'Right Neighborly',u'values': [[u'Nice',u'Nice'],[u'Mean',u'Mean'],[u'Ornery',u'Ornery'],[u'Right Neighborly',u'Right Neighborly']],u'widget_constructor': u'zope.schema.Choice'},{u'allowBlank': False,u'fieldHint': u'Weight in lbs?',u'fieldLabel': u'Weight',u'id': u'weight',u'name': u'weight',u'required': True,u'widget_constructor': u'zope.schema.Decimal'},{u'fieldHint': u'What do they look like?',u'fieldLabel': u'Description',u'id': u'description',u'minLength': 0,u'name': u'description',u'required': True,u'value': u'10ft tall\nRazor sharp scales.',u'widget_constructor': u'zope.schema.Text'},{u'fieldHint': u"Don't tell anybody",u'fieldLabel': u'Secret Key',u'id': u'secret',u'name': u'secret',u'required': True,u'value': u'5ecret sauce',u'widget_constructor': u'zc.ajaxform.widgets.Hidden'},{u'allowBlank': False,u'fieldHint': u'Number of siblings',u'fieldLabel': u'Siblings',u'field_max': 8,u'field_min': 0,u'id': u'siblings',u'name': u'siblings',u'required': True,u'value': u'1',u'widget_constructor': u'zc.ajaxform.widgets.NumberSpinner'},{u'fieldHint': u'All my wonderful homes',u'fieldLabel': u'Addresses',u'id': u'addresses',u'name': u'addresses',u'record_schema': {u'readonly': False,u'widgets': [{u'fieldHint': u'The street',u'fieldLabel': u'Street',u'id': u'street',u'minLength': 0,u'name': u'street',u'required': True,u'widget_constructor': u'zope.schema.TextLine'},{u'fieldHint': u'The city',u'fieldLabel': u'City',u'id': u'city',u'minLength': 0,u'name': u'city',u'required': True,u'widget_constructor': u'zope.schema.TextLine'},{u'allowBlank': False,u'fieldHint': u'The awesomeness on a scale of 1 to 10',u'fieldLabel': u'Awesomeness',u'field_max': 10,u'field_min': 1,u'id': u'awesomeness',u'name': u'awesomeness',u'required': True,u'widget_constructor': u'zope.schema.Int'}]},u'required': True,u'value': [{u'awesomeness': u'9',u'city': u'fakeville',u'street': u'123 fake street'},{u'awesomeness': u'9001',u'city': u'falsetown',u'street': u'345 false street'}],u'widget_constructor': u'zope.schema.List'},{u'fieldHint': u'Any other notes',u'fieldLabel': u'Other',u'id': u'other',u'minLength': 0,u'name': u'other',u'required': True,u'value': u"I've got a magic toenail",u'widget_constructor': u'zope.schema.Text'}]}}Our application is at: "http://localhost/form.html". The form isexposed as an ajax method named "ExampleForm", which comes from the attributename in the class definition.The form definition contains both action definitions and widgetdefinitions. The widget definitions may be full js field definitionsor name a widget_constructor, which is a Javascript helper provided bythe zc.ajaxform resource library that provides additional information,like Javascript validators, that can't be expressed in JSON.There is an action definition for each action defined in the form.  Theaction information includes the url to post the result to, relative tothe application.Note that the name of the form class is used as the form prefix andthat the form prefix is used as the prefix for widget and action namesand ids [#actionids]_.Let's post a result back:>>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',...            {'first_name': 'Bob',...             'last_name': '',...             'favorite_color': '',...             'age': '-1',...             }){u'errors': {u'addresses': u'Addresses: Missing Input',u'age': u'Value is too small',u'description': u'Description: Missing Input',u'last_name': u'Last name: Missing Input',u'other': u'Other: Missing Input',u'secret': u'Secret Key: Missing Input',u'siblings': u'Siblings: Missing Input',u'temperment': u'Temperment: Missing Input',u'weight': u'Weight: Missing Input'}}The result had 9 problems:- We didn't provide a last name, description, secret key,number of siblings, temperment, or weight, which are all required- In the form we did not specify deleting either of our two currentaddress records, but also ommitted their data, and the first of themreported a missing field. Following this we will delete them both andadd a new record.- We specified an invalid age.Let's pass valid data:>>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',...            {'first_name': 'Bob',...             'last_name': 'Zope',...             'favorite_color': '',...             'age': '11',...             'addresses.street.0': '123 Fake Ln.',...             'addresses.city.0': 'Fakeville',...             'addresses.awesomeness.0': 7,...             'description': 'Hello',...             'other': 'So nice to meet you',...             'secret': 'Oh nothing',...             'siblings': 1,...             'temperment': 'Nice',...             'weight': '170.5',...             'pet': 'Carrier Pigeon'...             }){u'data': {u'addresses': [{u'awesomeness': 7,u'city': u'Fakeville',u'street': u'123 Fake Ln.'}],u'age': 11,u'description': u'Hello',u'favorite_color': u'',u'first_name': u'Bob',u'happy': False,u'last_name': u'Zope',u'other': u'So nice to meet you',u'pet': u'Carrier Pigeon',u'secret': u'Oh nothing',u'siblings': 1,u'temperment': u'Nice',u'weight': u'170.5'},u'self_app_class_name': u'FormExample',u'self_class_name': u'ExampleForm',u'self_context_class_name': u'Folder'}Here we get a successful result.  Our contrived action in the examplesimply echoed back the data it was passed,  Note, in particular that:- the data keys have the form prefix removed, and- the value of the age key is an integer, since the field was aninteger field.Note that for the list field, the original request added a prefix of the listfield name to prevent collisions with a field with the same name in anotherlist field (if it existed).The action also prints out the classes of its self argument, its appand its context. Actions are methods of forms so their `self` argument is theform. The form's `app` is the app through which it is accessed and`context` is the app's context.For list widgets if a field in its record is missing and it is required, theerror reported lists the field name:>>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',...            {'first_name': 'Bob',...             'last_name': 'Zope',...             'favorite_color': '',...             'age': '11',...             'addresses.street.0': '123 Fake Ln.',...             'addresses.city.0': 'Fakeville',...             'addresses.awesomeness.0': 7,...             'addresses.street.1': 'The 2nd Missing field St.',...             'addresses.awesomeness.1': 3,...             'description': 'Hello',...             'other': 'So nice to meet you',...             'secret': 'Oh nothing',...             'siblings': 1,...             'temperment': 'Nice',...             'weight': '170.5',...             'pet': 'Carrier Pigeon'...             }){u'errors': {u'addresses': u'City: Missing Input'}}Let's provide this value now:>>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',...            {'first_name': 'Bob',...             'last_name': 'Zope',...             'favorite_color': '',...             'age': '11',...             'addresses.street.0': '123 Fake Ln.',...             'addresses.city.0': 'Fakeville',...             'addresses.awesomeness.0': 7,...             'addresses.street.1': 'The 2nd Missing field St.',...             'addresses.city.1': 'A Void',...             'addresses.awesomeness.1': '3',...             'description': 'Hello',...             'other': 'So nice to meet you',...             'secret': 'Oh nothing',...             'siblings': 1,...             'temperment': 'Nice',...             'weight': '170.5',...             'pet': 'Carrier Pigeon'...             }){u'data': {u'addresses': [{u'awesomeness': 7,u'city': u'Fakeville',u'street': u'123 Fake Ln.'},{u'awesomeness': 3,u'city': u'A Void',u'street': u'The 2nd Missing field St.'}],u'age': 11,u'description': u'Hello',u'favorite_color': u'',u'first_name': u'Bob',u'happy': False,u'last_name': u'Zope',u'other': u'So nice to meet you',u'pet': u'Carrier Pigeon',u'secret': u'Oh nothing',u'siblings': 1,u'temperment': u'Nice',u'weight': u'170.5'},u'self_app_class_name': u'FormExample',u'self_class_name': u'ExampleForm',u'self_context_class_name': u'Folder'}Reordering items in a list is accomplished by reordering the suffix for therecord fields:>>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',...            {'first_name': 'Bob',...             'last_name': 'Zope',...             'favorite_color': '',...             'age': '11',...             'addresses.street.1': '123 Fake Ln.',...             'addresses.city.1': 'Fakeville',...             'addresses.awesomeness.1': 7,...             'addresses.street.0': 'The 2nd Missing field St.',...             'addresses.city.0': 'A Void',...             'addresses.awesomeness.0': 3,...             'description': 'Hello',...             'other': 'So nice to meet you',...             'secret': 'Oh nothing',...             'siblings': 1,...             'temperment': 'Nice',...             'weight': '170.5',...             'pet': 'Carrier Pigeon'...             }){u'data': {u'addresses': [{u'awesomeness': 3,u'city': u'A Void',u'street': u'The 2nd Missing field St.'},{u'awesomeness': 7,u'city': u'Fakeville',u'street': u'123 Fake Ln.'}],u'age': 11,u'description': u'Hello',u'favorite_color': u'',u'first_name': u'Bob',u'happy': False,u'last_name': u'Zope',u'other': u'So nice to meet you',u'pet': u'Carrier Pigeon',u'secret': u'Oh nothing',u'siblings': 1,u'temperment': u'Nice',u'weight': u'170.5'},u'self_app_class_name': u'FormExample',u'self_class_name': u'ExampleForm',u'self_context_class_name': u'Folder'}Getting definitions from Python-------------------------------Sometimes we want to get form definitions from Python.  The form__call__ method returns a JSON string.  We can get Python data bycalling get_definition.>>> import zc.ajaxform.form_example>>> import zope.publisher.browser>>> request = zope.publisher.browser.TestRequest()>>> import zc.ajaxform.interfaces>>> import zope.interface>>> zope.interface.alsoProvides(...     request, zc.ajaxform.interfaces.IAjaxRequest)>>> ex = zc.ajaxform.form_example.FormExample(None, request)>>> from pprint import pprint>>> pprint(ex.ExampleForm.get_definition(), width=1){'actions': [{'label': 'Register','name': u'ExampleForm.actions.register','url': u'ExampleForm/register'}],'left_fields': {'addresses': False,'age': True,'description': False,'favorite_color': False,'first_name': True,'happy': False,'last_name': True,'other': True,'pet': False,'secret': False,'siblings': False,'temperment': False,'weight': False},'prefix': 'ExampleForm','widgets': [{'fieldHint': u'Given name.','fieldLabel': u'First name','id': 'first_name','minLength': 0,'name': 'first_name','required': True,'value': u'Happy','widget_constructor': 'zope.schema.TextLine'},{'fieldHint': u'Family name.','fieldLabel': u'Last name','id': 'last_name','minLength': 0,'name': 'last_name','required': True,'value': u'Camper','widget_constructor': 'zope.schema.TextLine'},{'fieldHint': u'','fieldLabel': u'Favorite color','id': 'favorite_color','minLength': 0,'name': 'favorite_color','required': False,'value': u'Blue','widget_constructor': 'zope.schema.TextLine'},{'allowBlank': False,'fieldHint': u'Age in years','fieldLabel': u'Age','field_max': 200,'field_min': 0,'id': 'age','name': 'age','required': True,'value': u'23','widget_constructor': 'zope.schema.Int'},{'fieldHint': u'Are they happy?','fieldLabel': u'Happy','id': 'happy','name': 'happy','required': True,'value': True,'widget_constructor': 'zope.schema.Bool'},{'fieldHint': u"This person's best friend.",'fieldLabel': u'Pet','id': 'pet','name': 'pet','required': False,'values': [['c935d187f0b998ef720390f85014ed1e',u'Dog'],['fa3ebd6742c360b2d9652b7f78d9bd7d',u'Cat'],['071642fa72ba780ee90ed36350d82745',u'Fish']],'widget_constructor': 'zc.ajaxform.widgets.ComboBox'},{'allowBlank': False,'fieldHint': u'What is the person like?','fieldLabel': u'Temperment','hiddenName': 'temperment.value','id': 'temperment','name': 'temperment','required': True,'value': 'Right Neighborly','values': [['Nice',u'Nice'],['Mean',u'Mean'],['Ornery',u'Ornery'],['Right Neighborly',u'Right Neighborly']],'widget_constructor': 'zope.schema.Choice'},{'allowBlank': False,'fieldHint': u'Weight in lbs?','fieldLabel': u'Weight','id': 'weight','name': 'weight','required': True,'widget_constructor': 'zope.schema.Decimal'},{'fieldHint': u'What do they look like?','fieldLabel': u'Description','id': 'description','minLength': 0,'name': 'description','required': True,'value': u'10ft tall\nRazor sharp scales.','widget_constructor': 'zope.schema.Text'},{'fieldHint': u"Don't tell anybody",'fieldLabel': u'Secret Key','id': 'secret','name': 'secret','required': True,'value': u'5ecret sauce','widget_constructor': 'zc.ajaxform.widgets.Hidden'},{'allowBlank': False,'fieldHint': u'Number of siblings','fieldLabel': u'Siblings','field_max': 8,'field_min': 0,'id': 'siblings','name': 'siblings','required': True,'value': u'1','widget_constructor': 'zc.ajaxform.widgets.NumberSpinner'},{'fieldHint': u'All my wonderful homes','fieldLabel': u'Addresses','id': 'addresses','name': 'addresses','record_schema': {'readonly': False,'widgets': [{'fieldHint': u'The street','fieldLabel': u'Street','id': 'street','minLength': 0,'name': 'street','required': True,'widget_constructor': 'zope.schema.TextLine'},{'fieldHint': u'The city','fieldLabel': u'City','id': 'city','minLength': 0,'name': 'city','required': True,'widget_constructor': 'zope.schema.TextLine'},{'allowBlank': False,'fieldHint': u'The awesomeness on a scale of 1 to 10','fieldLabel': u'Awesomeness','field_max': 10,'field_min': 1,'id': 'awesomeness','name': 'awesomeness','required': True,'widget_constructor': 'zope.schema.Int'}]},'required': True,'value': [{'awesomeness': u'9','city': u'fakeville','street': u'123 fake street'},{'awesomeness': u'9001','city': u'falsetown','street': u'345 false street'}],'widget_constructor': 'zope.schema.List'},{'fieldHint': u'Any other notes','fieldLabel': u'Other','id': 'other','minLength': 0,'name': 'other','required': True,'value': u"I've got a magic toenail",'widget_constructor': 'zope.schema.Text'}]}Note that we had to stamp the request with IAjaxRequest.  This is doneduring application traversal.  We need it so widgets can get lookedup.Base and prefix---------------Forms have base_href and prefix variables.  The base_href is used to computeURLs for form actions.  A form's base_href defaults to its class name.The form's base_href also includes the base_href of its app, if its app hasa base_href. This is useful for sub-applications. Let's give our sampleapplication a base_href attribute as if it were a sub-application:>>> ex = zc.ajaxform.form_example.FormExample(None, request)>>> ex.base_href = 'sample'>>> ex.ExampleForm.base_href'sample/ExampleForm'>>> pprint(ex.ExampleForm.get_definition(), width=1){'actions': [{'label': 'Register','name': u'sample.ExampleForm.actions.register','url': u'sample/ExampleForm/register'}],'left_fields': {'addresses': False,'age': True,'description': False,'favorite_color': False,'first_name': True,'happy': False,'last_name': True,'other': True,'pet': False,'secret': False,'siblings': False,'temperment': False,'weight': False},'prefix': 'sample.ExampleForm','widgets': [{'fieldHint': u'Given name.','fieldLabel': u'First name','id': 'first_name','minLength': 0,'name': 'first_name','required': True,'value': u'Happy','widget_constructor': 'zope.schema.TextLine'},{'fieldHint': u'Family name.','fieldLabel': u'Last name','id': 'last_name','minLength': 0,'name': 'last_name','required': True,'value': u'Camper','widget_constructor': 'zope.schema.TextLine'},{'fieldHint': u'','fieldLabel': u'Favorite color','id': 'favorite_color','minLength': 0,'name': 'favorite_color','required': False,'value': u'Blue','widget_constructor': 'zope.schema.TextLine'},{'allowBlank': False,'fieldHint': u'Age in years','fieldLabel': u'Age','field_max': 200,'field_min': 0,'id': 'age','name': 'age','required': True,'value': u'23','widget_constructor': 'zope.schema.Int'},{'fieldHint': u'Are they happy?','fieldLabel': u'Happy','id': 'happy','name': 'happy','required': True,'value': True,'widget_constructor': 'zope.schema.Bool'},{'fieldHint': u"This person's best friend.",'fieldLabel': u'Pet','id': 'pet','name': 'pet','required': False,'values': [['c935d187f0b998ef720390f85014ed1e',u'Dog'],['fa3ebd6742c360b2d9652b7f78d9bd7d',u'Cat'],['071642fa72ba780ee90ed36350d82745',u'Fish']],'widget_constructor': 'zc.ajaxform.widgets.ComboBox'},{'allowBlank': False,'fieldHint': u'What is the person like?','fieldLabel': u'Temperment','hiddenName': 'temperment.value','id': 'temperment','name': 'temperment','required': True,'value': 'Right Neighborly','values': [['Nice',u'Nice'],['Mean',u'Mean'],['Ornery',u'Ornery'],['Right Neighborly',u'Right Neighborly']],'widget_constructor': 'zope.schema.Choice'},{'allowBlank': False,'fieldHint': u'Weight in lbs?','fieldLabel': u'Weight','id': 'weight','name': 'weight','required': True,'widget_constructor': 'zope.schema.Decimal'},{'fieldHint': u'What do they look like?','fieldLabel': u'Description','id': 'description','minLength': 0,'name': 'description','required': True,'value': u'10ft tall\nRazor sharp scales.','widget_constructor': 'zope.schema.Text'},{'fieldHint': u"Don't tell anybody",'fieldLabel': u'Secret Key','id': 'secret','name': 'secret','required': True,'value': u'5ecret sauce','widget_constructor': 'zc.ajaxform.widgets.Hidden'},{'allowBlank': False,'fieldHint': u'Number of siblings','fieldLabel': u'Siblings','field_max': 8,'field_min': 0,'id': 'siblings','name': 'siblings','required': True,'value': u'1','widget_constructor': 'zc.ajaxform.widgets.NumberSpinner'},{'fieldHint': u'All my wonderful homes','fieldLabel': u'Addresses','id': 'addresses','name': 'addresses','record_schema': {'readonly': False,'widgets': [{'fieldHint': u'The street','fieldLabel': u'Street','id': 'street','minLength': 0,'name': 'street','required': True,'widget_constructor': 'zope.schema.TextLine'},{'fieldHint': u'The city','fieldLabel': u'City','id': 'city','minLength': 0,'name': 'city','required': True,'widget_constructor': 'zope.schema.TextLine'},{'allowBlank': False,'fieldHint': u'The awesomeness on a scale of 1 to 10','fieldLabel': u'Awesomeness','field_max': 10,'field_min': 1,'id': 'awesomeness','name': 'awesomeness','required': True,'widget_constructor': 'zope.schema.Int'}]},'required': True,'value': [{'awesomeness': u'9','city': u'fakeville','street': u'123 fake street'},{'awesomeness': u'9001','city': u'falsetown','street': u'345 false street'}],'widget_constructor': 'zope.schema.List'},{'fieldHint': u'Any other notes','fieldLabel': u'Other','id': 'other','minLength': 0,'name': 'other','required': True,'value': u"I've got a magic toenail",'widget_constructor': 'zope.schema.Text'}]}Note that the action URL now includes "sample/" as a prefix.  Alsonote that the widget and action names have "" as a prefix.  Theform prefix is simply its base with "/"s converted to "."s.>>> ex.ExampleForm.prefix'sample.ExampleForm'Form data---------Ajax forms are a bit different from normal web forms because the dataand the form definition can be fetched separately.  For example, wemay use the same form to edit multiple objects.  Form objects have agetObjectData method that returns data suitable for editing form fieldvalues.  Let's create a person and use out form to get data for them:>>> bob = zc.ajaxform.form_example.Person(...     first_name='bob',...     last_name='smith',...     favorite_color=None,...     age=11,...     happy=True,...     pet=u'Cockatiel',...     temperment='Nice',...     weight = 175.5,...     description = 'A real cool guy',...     secret = 'Noone knows!',...     siblings = 1,...     addresses = [],...     other = 'stuff')>>> pprint(ex.ExampleForm.getObjectData(bob), width=1){'addresses': [],'age': u'11','description': u'A real cool guy','first_name': u'bob','happy': True,'last_name': u'smith','other': u'stuff','pet': u'Cockatiel','secret': u'Noone knows!','siblings': u'1','temperment': 'Nice','weight': u'175.5'}We didn't set the favorite_color for the person, so it is ommittedfrom the data.We can pass in a dictionary of values that take precedence over object data:>>> pprint(ex.ExampleForm.getObjectData(...            bob, {'age': u'1'}),...        width=1){'addresses': [],'age': u'1','description': u'A real cool guy','first_name': u'bob','happy': True,'last_name': u'smith','other': u'stuff','pet': u'Cockatiel','secret': u'Noone knows!','siblings': u'1','temperment': 'Nice','weight': u'175.5'}Display Options---------------Additional display options may be sent in the widget definition if the widgetcan be adapted to `IDisplayOptions`.  The result of the adaptation only needbe JSON serializable.>>> import zope.app.form.interfaces>>> def example_options(widget):...     field, name = widget.context, widget.context.__name__...     if name == 'favorite_color':...         return {'picker': 'crayons'}...     elif name == 'secret':...         return 'super-secret'...     else:...         return None>>> site_manager = zope.component.getSiteManager()>>> site_manager.registerAdapter(...     example_options,...     required=(zope.app.form.interfaces.IWidget,),...     provided=zc.ajaxform.interfaces.IDisplayOptions)>>> result = call_form(...     browser, 'http://localhost/form.html/ExampleForm')>>> widgets = result['definition']['widgets']>>> for widget in widgets:...     if widget.get('display_options'):...         print widget['name'] + ':', widget['display_options']favorite_color: {u'picker': u'crayons'}secret: super-secretFinally, clean up.>>> site_manager.unregisterAdapter(...     example_options,...     required=(zope.app.form.interfaces.IWidget,),...     provided=zc.ajaxform.interfaces.IDisplayOptions)TrueTo-do (maybe)-------------More widgets!Interface invariantsActions:- conditions- validators- failure handlers.. [#application] See application.txt.. [#form_classes_are_descriptors] Form classes are alsodescriptors. They get called with the instance they're accessedthrough... [#actionids] The Javascript code that sets up action buttons usesaction name as the button's ID.Download********