{ "info": { "author": "Paul Carduner and the Zope Community", "author_email": "zope-dev@zope.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Zope3", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP" ], "description": "This package is going to provide javascript support/enhancements to\nthe z3c.form library.\n\nDetailed Documentation\n**********************\n\n===========================\nForm Javascript Integration\n===========================\n\nThis package is designed to provide a Python API to common Javascript\nfeatures for forms written with the ``z3c.form*`` packages. While the\nreference backend-implementation is for the JQuery library, any other\nJavascript library can be hooked into the Python API.\n\nThe documents are ordered in the way they should be read:\n\n- ``jsaction.txt`` [must read]\n\n This document describes how JS scripts can be connected to events on a\n any widget, inclduing buttons.\n\n- ``jsvalidator.txt`` [must read]\n\n This document demonstrates how \"live\" widget value validation can be\n achieved.\n\n- ``jsevent.txt`` [advanced users]\n\n This documents describes the generalization that allows hooking up script to\n events on any field.\n\n- ``jqueryrenderer.txt`` [advanced users]\n\n This document demonstrates all necessary backend renderer components\n necessary to accomplish any of the features of this package.\n\n=============================\nJavascript Events for Buttons\n=============================\n\nIn the ``z3c.form`` package, buttons are most commonly rendered as \"submit\"\ninput fields within a form, meaning that the form will always be\nsubmitted. When working with Javascript, on the other hand, a click on the\nbutton often simply executes a script. The ``jsaction`` module of this package\nis designed to implement the latter kind.\n\n >>> from z3c.formjs import jsaction\n\n\nJavascript Buttons\n------------------\n\nBefore we can write a form that uses Javascript buttons, we have to define\nthem first. One common way to define buttons in ``z3c.form`` is to write a\nschema describing them; so let's do that now:\n\n >>> import zope.interface\n >>> class IButtons(zope.interface.Interface):\n ... hello = jsaction.JSButton(title=u'Hello World!')\n ... dblhello = jsaction.JSButton(title=u'Double Hello World!')\n\nInstead of declaring ``z3c.form.button.Button`` fields, we are now using a\nderived Javascript button field. While there is no difference initially, they\nwill later be rendered differently. (Basically, ``JSButton`` fields render as\nbutton widgets.)\n\n\nWidget Selector\n---------------\n\nLike for regular fields, the action of the buttons is defined using handlers,\nin our case Javascript handler. Selectors are used to determine the DOM\nelement or elements for which a handler is registered. The widget selector\nuses a widget to provide the selector API:\n\n >>> from z3c.form.testing import TestRequest\n >>> request = TestRequest()\n\n >>> from z3c.form.browser import text\n >>> msg = text.TextWidget(request)\n >>> msg.id = 'form-msg'\n >>> msg.name = 'msg'\n\n >>> selector = jsaction.WidgetSelector(msg)\n >>> selector\n <WidgetSelector \"form-msg\">\n\nSince the widget selector can determine the widget's id, it is also an id\nselector (see ``jsevent.txt``):\n\n >>> from z3c.formjs import interfaces\n >>> interfaces.IIdSelector.providedBy(selector)\n True\n >>> selector.id\n 'form-msg'\n\nThis has the advantage that we can reuse the renderer of the id\nselector.\n\n\nJavascript Event Subscriptions\n------------------------------\n\nAs discussed in ``jsevent.txt``, all the Javascript event subscriptions are\nstored on the view in a special attribute called ``jsSubscriptions``. While\nupdating the form, one can simply add subscriptions to this registry. So let's\nsay we have the following handler:\n\n >>> def showSelectedWidget(event, selector, request):\n ... return 'alert(\"%r\");' %(selector.widget)\n\nWe now want to connect this handler to the ``msg`` widget to be executed when\nthe mouse is clicked within this element:\n\n >>> import zope.interface\n >>> from z3c.formjs import jsevent\n\n >>> class Form(object):\n ... zope.interface.implements(interfaces.IHaveJSSubscriptions)\n ... jsSubscriptions = jsevent.JSSubscriptions()\n ...\n ... def update(self):\n ... self.jsSubscriptions.subscribe(\n ... jsevent.CLICK, selector, showSelectedWidget)\n\n >>> form = Form()\n >>> form.update()\n\nAfter registering the subscription-related renderers,\n\n >>> from z3c.formjs import testing\n >>> testing.setupRenderers()\n\nwe can use the subscription rendering viewlet to check the subscription\noutput:\n\n >>> viewlet = jsevent.JSSubscriptionsViewlet(None, request, form, None)\n >>> viewlet.update()\n >>> print viewlet.render()\n <script type=\"text/javascript\">\n $(document).ready(function(){\n $(\"#form-msg\").bind(\"click\", function(){alert(\"<TextWidget 'msg'>\");});\n })\n </script>\n\nThe z3c.formjs package provides a viewlet manager with this viewlet\nalready registered for it. The viewlet manager has the name\n``z3c.formjs.interfaces.IDynamicJavaScript`` and can be rendered in\nany template with the following:\n\n <script tal:replace=\"structure\n provider:z3c.formjs.interfaces.IDynamicJavaScript\">\n </script>\n\n\nForms with Javascript Buttons\n-----------------------------\n\nThe next step is create the form. Luckily we do not need any fields to\nrender a form. Also, instead of using usual\n``z3c.form.button.handler()`` function, we now have a special handler\ndecorator that connects a button to a Javascript event, along with an\nadditional decorator that creates the button at the same time. The\noutput of the handler itself is a string that is used as the\nJavascript script that is executed.\n\n >>> from z3c.form import button, form\n\n >>> class Form(form.Form):\n ... buttons = button.Buttons(IButtons)\n ...\n ... @jsaction.handler(buttons['hello'])\n ... def showHelloWorldMessage(self, event, selector):\n ... return 'alert(\"%s\");' % selector.widget.title\n ...\n ... @jsaction.handler(buttons['dblhello'], event=jsevent.DBLCLICK)\n ... def showDoubleHelloWorldMessage(self, event, selector):\n ... return 'alert(\"%s\");' % selector.widget.title\n ...\n ... @jsaction.buttonAndHandler(u\"Click Me\")\n ... def handleClickMe(self, event, selector):\n ... return 'alert(\"You clicked the Click Me button.\");'\n\n\n\nThe ``handler()`` decorator takes two arguments, the button (acting as the DOM\nelement selector) and the event to which to bind the action. By default the\nevent is ``jsevent.CLICK``.\n\nAnd that is really everything that is required from a user's point of\nview. Let us now see how those handler declarations are converted into actions\nand Javascript subscriptions. First we need to initialize the form:\n\n >>> from z3c.form.testing import TestRequest\n >>> request = TestRequest()\n\n >>> demoform = Form(None, request)\n\nWe also need to register an adapter to create an action from a button:\n\n >>> from z3c.form.interfaces import IButtonAction\n >>> zope.component.provideAdapter(\n ... jsaction.JSButtonAction, provides=IButtonAction)\n\nFinally, for the Javascript subscriptions to be registered, we need an event\nlistener that reacts to \"after widget/action update\" events:\n\n >>> zope.component.provideHandler(jsaction.createSubscriptionsForWidget)\n\nAction managers are instantiated using the form, request, and\ncontext/content. A button-action-manager implementation is avaialble in the\n``z3c.form.button`` package:\n\n >>> actions = button.ButtonActions(demoform, request, None)\n >>> actions.update()\n\nOnce the action manager is updated, the buttons should be available as\nactions:\n\n >>> actions.keys()\n ['hello', 'dblhello', '436c69636b204d65']\n >>> actions['hello']\n <JSButtonAction 'form.buttons.hello' u'Hello World!'>\n\nSince special Javascript handlers were registered for those buttons, creating\nand updating the actions has also caused the form to become an\n``IHaveJSSubscriptions`` view:\n\n >>> from z3c.formjs import interfaces\n\n >>> interfaces.IHaveJSSubscriptions.providedBy(demoform)\n True\n >>> demoform.jsSubscriptions\n <z3c.formjs.jsevent.JSSubscriptions object at ...>\n\nThe interesting part about button subscriptions is the selector.\n\n >>> selector = list(demoform.jsSubscriptions)[0].selector\n >>> selector\n <WidgetSelector \"form-buttons-hello\">\n\nAs you can see, the system automatically created a widget selector:\n\n >>> selector.id\n 'form-buttons-hello'\n >>> selector.widget\n <JSButtonAction 'form.buttons.hello' u'Hello World!'>\n\nWith the declarations in place, we can now go on.\n\n\nRendering the Form\n------------------\n\nLet's now see what we need to do to make the form render correctly and\ncompletely.\n\n >>> demoform = Form(None, request)\n\nFirst we need some of the standard ``z3c.form`` registrations:\n\n >>> from z3c.form import field, button\n >>> zope.component.provideAdapter(field.FieldWidgets)\n >>> zope.component.provideAdapter(button.ButtonActions)\n\nNext we need to register the template for our button actions:\n\n >>> from zope.pagetemplate.interfaces import IPageTemplate\n >>> from z3c.form import widget\n >>> from z3c.form.interfaces import IButtonWidget, INPUT_MODE\n >>> from z3c.form.testing import getPath\n\n >>> zope.component.provideAdapter(\n ... widget.WidgetTemplateFactory(getPath('button_input.pt'), 'text/html'),\n ... (None, None, None, None, IButtonWidget),\n ... IPageTemplate, name=INPUT_MODE)\n\nWe also need to setup a Javascript viewlet manager and register the\nsubscription viewlet for it, so that the subscriptions actually appear in the\nHTML page. (This is a bit tedious to do using the Python API, but using ZCML\nthis is much simpler.)\n\n* Hook up the \"provider\" TALES expression type:\n\n >>> from zope.pagetemplate.engine import TrustedEngine\n >>> from zope.contentprovider import tales\n >>> TrustedEngine.registerType('provider', tales.TALESProviderExpression)\n\n* Create a viewlet manager that does not require security to be setup:\n\n >>> from zope.viewlet import manager\n >>> class JSViewletManager(manager.ViewletManagerBase):\n ... def filter(self, viewlets):\n ... return viewlets\n\n* Register the viewlet manager as a content provider known as \"javascript\":\n\n >>> from z3c.form.interfaces import IFormLayer\n >>> from zope.contentprovider.interfaces import IContentProvider\n >>> zope.component.provideAdapter(\n ... JSViewletManager,\n ... (None, IFormLayer, None),\n ... IContentProvider,\n ... name='javascript')\n\n* Register the JS Subscriber viewlet for this new viewlet manager:\n\n >>> from zope.viewlet.interfaces import IViewlet\n >>> zope.component.provideAdapter(\n ... jsevent.JSSubscriptionsViewlet,\n ... (None, IFormLayer, interfaces.IHaveJSSubscriptions,\n ... JSViewletManager), IViewlet, name='subscriptions')\n\nFinally, we need a template for our form:\n\n >>> testing.addTemplate(demoform, 'buttons_form.pt')\n\nWe can now render the form:\n\n >>> demoform.update()\n >>> print demoform.render()\n <html>\n <head>\n <script type=\"text/javascript\">\n $(document).ready(function(){\n $(\"#form-buttons-hello\").bind(\"click\",\n function(){alert(\"Hello World!\");});\n $(\"#form-buttons-dblhello\").bind(\"dblclick\",\n function(){alert(\"Double Hello World!\");});\n $(\"#form-buttons-436c69636b204d65\").bind(\"click\",\n function(){alert(\"You clicked the Click Me button.\");});\n })\n </script>\n </head>\n <body>\n <div class=\"action\">\n <input type=\"button\" id=\"form-buttons-hello\"\n name=\"form.buttons.hello\" class=\"button-widget jsbutton-field\"\n value=\"Hello World!\" />\n </div>\n <div class=\"action\">\n <input type=\"button\" id=\"form-buttons-dblhello\"\n name=\"form.buttons.dblhello\" class=\"button-widget jsbutton-field\"\n value=\"Double Hello World!\" />\n </div>\n <div class=\"action\">\n <input type=\"button\" id=\"form-buttons-436c69636b204d65\"\n name=\"form.buttons.436c69636b204d65\"\n class=\"button-widget jsbutton-field\" value=\"Click Me\" />\n </div>\n </body>\n </html>\n\nAs you can see, the subscriptions are correctly placed into the header, while\nthe buttons render as usual with exception to the input type, which is now a\n\"button\".\n\n\nMultiple Handlers\n-----------------\n\nSince there are multiple events in Javascript, one element can have multiple\nhandlers. So let's define a new form that declares two handlers for the same\nbutton:\n\n >>> class Form(form.Form):\n ... buttons = button.Buttons(IButtons).select('hello')\n ...\n ... @jsaction.handler(buttons['hello'])\n ... def showHelloWorldMessage(self, event, selector):\n ... return 'alert(\"Hello World!\");'\n ...\n ... @jsaction.handler(buttons['hello'], event=jsevent.DBLCLICK)\n ... def showDoubleHelloWorldMessage(self, event, selector):\n ... return 'alert(\"Hello World! x 2\");'\n\nLet's now instantiate and update the form:\n\n >>> demoform = Form(None, request)\n >>> demoform.update()\n\nThe subscriptions are now available:\n\n >>> list(demoform.jsSubscriptions)\n [<JSSubscription\n event=<JSEvent \"click\">,\n selector=<WidgetSelector \"form-buttons-hello\">,\n handler=<JSHandler <function showHelloWorldMessage ...>>>,\n <JSSubscription\n event=<JSEvent \"dblclick\">,\n selector=<WidgetSelector \"form-buttons-hello\">,\n handler=<JSHandler <function showDoubleHelloWorldMessage ...>>>]\n\nNext we can look at a case where one handler is registered for all buttons and\nevents, and another overrides the click of the \"hello\" button to something\nelse:\n\n >>> from z3c.form.interfaces import IButton\n >>> class Form(form.Form):\n ... buttons = button.Buttons(IButtons)\n ...\n ... @jsaction.handler(IButton, interfaces.IJSEvent)\n ... def showHelloWorldMessage(self, event, selector):\n ... return '''alert(\"The event '%s' occured.\");''' %event.name\n ...\n ... @jsaction.handler(buttons['hello'], event=jsevent.CLICK)\n ... def showDoubleHelloWorldMessage(self, event, selector):\n ... return 'alert(\"Hello World clicked!\");'\n\n >>> demoform = Form(None, request)\n >>> demoform.update()\n\nRendering the subscriptions gives the following result:\n\n >>> renderer = zope.component.getMultiAdapter(\n ... (demoform.jsSubscriptions, request), interfaces.IRenderer)\n >>> renderer.update()\n >>> print renderer.render()\n $(document).ready(function(){\n $(\"#...-hello\").bind(\"dblclick\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"change\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"load\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"blur\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"focus\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"keydown\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"keyup\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"mousedown\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"mousemove\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"mouseout\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"mouseover\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"mouseup\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"resize\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"select\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"submit\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"click\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"dblclick\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"change\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"load\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"blur\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"focus\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"keydown\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"keyup\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"mousedown\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"mousemove\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"mouseout\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"mouseover\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"mouseup\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"resize\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"select\", function(){alert(\"The ...\");});\n $(\"#...-dblhello\").bind(\"submit\", function(){alert(\"The ...\");});\n $(\"#...-hello\").bind(\"click\", function(){alert(\"Hello World clicked!\");});\n })\n\nWhile this output might seem excessive, it demonstrates that the generic\n``IJSEvent`` subscription truly causes a subscription to all events. Further,\na more specific directive takes precendence over the more generic one. This is\ndue to the built-in adapter registry of the ``JSHandlers`` class.\n\nFinally, handler declarations can also be chained, allowing a handler to be\nregistered for multiple field-event combinations that cannot be expressed by\ncommon interfaces:\n\n >>> class Form(form.Form):\n ... buttons = button.Buttons(IButtons)\n ...\n ... @jsaction.handler(IButtons['hello'], jsevent.CLICK)\n ... @jsaction.handler(IButtons['hello'], jsevent.DBLCLICK)\n ... def showHelloWorldMessage(self, event, selector):\n ... return '''alert(\"The event '%s' occured.\");''' %event.name\n\n >>> demoform = Form(None, request)\n >>> demoform.update()\n\nRendering the subscriptions gives the following result:\n\n >>> renderer = zope.component.getMultiAdapter(\n ... (demoform.jsSubscriptions, request), interfaces.IRenderer)\n >>> renderer.update()\n >>> print renderer.render()\n $(document).ready(function(){\n $(\"#form-buttons-hello\").bind(\"click\", function(){alert(\"The ...\");});\n $(\"#form-buttons-hello\").bind(\"dblclick\", function(){alert(\"The ...\");});\n })\n\n\nAttaching Events to Form Fields\n-------------------------------\n\nJavascript handlers do not only work for buttons, but also for fields. Let's\ncreate a simple schema that we can use to create a form:\n\n >>> import zope.schema\n\n >>> class IPerson(zope.interface.Interface):\n ... name = zope.schema.TextLine(title=u'Name')\n ... age = zope.schema.Int(title=u'Age')\n\nEven though somewhat pointless, whenever the \"age\" field is clicked on or the\n\"name\" widget value changed, we would like to get an alert:\n\n >>> class PersonAddForm(form.AddForm):\n ... fields = field.Fields(IPerson)\n ...\n ... @jsaction.handler(fields['age'])\n ... def ageClickEvent(self, event, selector):\n ... return 'alert(\"The Age was Clicked!\");'\n ...\n ... @jsaction.handler(fields['name'], event=jsevent.CHANGE)\n ... def nameChangeEvent(self, event, selector):\n ... return 'alert(\"The Name was Changed!\");'\n\nWe also need to register all of the default ``z3c.form`` registrations:\n\n >>> from z3c.form.testing import setupFormDefaults\n >>> setupFormDefaults()\n\nAfter adding a simple template for the form, it can be rendered:\n\n >>> addform = PersonAddForm(None, request)\n >>> testing.addTemplate(addform, 'simple_edit.pt')\n >>> addform.update()\n >>> print addform.render()\n <html>\n <head>\n <script type=\"text/javascript\">\n $(document).ready(function(){\n $(\"#form-widgets-name\").bind(\"change\",\n function(){alert(\"The Name was Changed!\");});\n $(\"#form-widgets-age\").bind(\"click\",\n function(){alert(\"The Age was Clicked!\");});\n })\n </script>\n </head>\n <body>\n <BLANKLINE>\n <BLANKLINE>\n <form action=\".\">\n <div class=\"row\">\n <label for=\"form-widgets-name\">Name</label>\n <BLANKLINE>\n <input id=\"form-widgets-name\" name=\"form.widgets.name\"\n class=\"text-widget required textline-field\"\n value=\"\" type=\"text\" />\n <BLANKLINE>\n </div>\n <div class=\"row\">\n <label for=\"form-widgets-age\">Age</label>\n <BLANKLINE>\n <input id=\"form-widgets-age\" name=\"form.widgets.age\"\n class=\"text-widget required int-field\" value=\"\"\n type=\"text\" />\n <BLANKLINE>\n </div>\n <div class=\"action\">\n <BLANKLINE>\n <input id=\"form-buttons-add\" name=\"form.buttons.add\"\n class=\"submit-widget button-field\" value=\"Add\"\n type=\"submit\" />\n <BLANKLINE>\n </div>\n </form>\n </body>\n </html>\n\nAs you can see, the form rendered perferctly, even allowing classic and\nJavascript handlers to co-exist.\n\n\nAppendix A: Javascript Event Handlers Manager\n---------------------------------------------\n\nThe ``IJSEventHandlers`` implementataion (``JSHandlers`` class) is really an\nadvanced component with great features, so it deserves some additional\nattention.\n\n >>> handlers = jsaction.JSHandlers()\n >>> handlers\n <JSHandlers []>\n\nWhen a handlers component is initialized, it creates an internal adapter\nregistry. If a handler is registered for a button, it simply behaves as an\ninstance-adapter.\n\n >>> handlers._registry\n <zope.interface.adapter.AdapterRegistry object at ...>\n\nThe object itself is pretty simple. To add a handler, we first have to create\na handler, ...\n\n >>> def doSomething(form, event, selector):\n ... pass\n >>> handler = jsaction.JSHandler(doSomething)\n\nThe only special thing about the handler is that it has the same name\nas the function.\n\n >>> handler.__name__\n 'doSomething'\n\nand a field/button:\n\n >>> button1 = jsaction.JSButton(name='button1', title=u'Button 1')\n\nLet's now add the handler:\n\n >>> handlers.addHandler(button1, jsevent.CLICK, handler)\n\nBut you can also register handlers for groups of fields, either by interface\nor class:\n\n >>> class SpecialButton(jsaction.JSButton):\n ... pass\n\n >>> handlers.addHandler(\n ... SpecialButton, jsevent.CLICK, jsaction.JSHandler('specialAction'))\n\n >>> handlers\n <JSHandlers\n [<JSHandler <function doSomething at ...>>,\n <JSHandler 'specialAction'>]>\n\nNow all special buttons should use that handler:\n\n >>> button2 = SpecialButton(name='button2', title=u'Button 2')\n >>> button3 = SpecialButton(name='button3', title=u'Button 3')\n\n >>> handlers.getHandlers(button2)\n ((<JSEvent \"click\">, <JSHandler 'specialAction'>),)\n >>> handlers.getHandlers(button3)\n ((<JSEvent \"click\">, <JSHandler 'specialAction'>),)\n\nHowever, registering a more specific handler for button 2 will override the\ngeneral handler:\n\n >>> handlers.addHandler(\n ... button2, jsevent.CLICK, jsaction.JSHandler('specificAction2'))\n\n >>> handlers.getHandlers(button2)\n ((<JSEvent \"click\">, <JSHandler 'specificAction2'>),)\n >>> handlers.getHandlers(button3)\n ((<JSEvent \"click\">, <JSHandler 'specialAction'>),)\n\nThe same flexibility that is available to the field is also available for the\nevent.\n\n >>> handlers = jsaction.JSHandlers()\n\nSo let's register a generic handler for all events:\n\n >>> handlers.addHandler(\n ... jsaction.JSButton, jsevent.JSEvent,\n ... jsaction.JSHandler('genericEventAction'))\n\nSo when asking for the handlers of button 1, we get a very long list:\n\n >>> handlers.getHandlers(button1)\n ((<JSEvent \"click\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"dblclick\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"change\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"load\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"blur\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"focus\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"keydown\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"keyup\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mousedown\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mousemove\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mouseout\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mouseover\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mouseup\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"resize\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"select\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"submit\">, <JSHandler 'genericEventAction'>))\n\nSo at this point you might ask: How is the complete set of events determined?\nAt this point we use the list of all events as listed in the\n``jsevent.EVENTS`` variable.\n\nLet's now register a special handler for the \"click\" event:\n\n >>> handlers.addHandler(\n ... button1, jsevent.CLICK, jsaction.JSHandler('clickEventAction'))\n\nSo this registration takes precedence over the generic one:\n\n >>> handlers.getHandlers(button1)\n ((<JSEvent \"click\">, <JSHandler 'clickEventAction'>),\n (<JSEvent \"dblclick\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"change\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"load\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"blur\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"focus\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"keydown\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"keyup\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mousedown\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mousemove\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mouseout\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mouseover\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"mouseup\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"resize\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"select\">, <JSHandler 'genericEventAction'>),\n (<JSEvent \"submit\">, <JSHandler 'genericEventAction'>))\n\nYou can also add handlers objects:\n\n >>> handlers = jsaction.JSHandlers()\n >>> handlers.addHandler(\n ... button1, jsevent.CLICK, jsaction.JSHandler('button1ClickAction'))\n\n >>> handlers2 = jsaction.JSHandlers()\n >>> handlers2.addHandler(\n ... button2, jsevent.CLICK, jsaction.JSHandler('button2ClickAction'))\n\n >>> handlers + handlers2\n <JSHandlers\n [<JSHandler 'button1ClickAction'>,\n <JSHandler 'button2ClickAction'>]>\n\nHowever, adding other components is not supported:\n\n >>> handlers + 1\n Traceback (most recent call last):\n ...\n NotImplementedError\n\nThe handlers also provide a method to copy the handlers to a new instance:\n\n >>> copy = handlers.copy()\n >>> isinstance(copy, jsaction.JSHandlers)\n True\n >>> copy is handlers\n False\n\nThis is commonly needed when one wants to extend the handlers of a super-form.\n\n\nAppendix B: The Subscription-Creating Event Subscriber\n------------------------------------------------------\n\nThe ``createSubscriptionsForWidget(event)`` event subscriber listens to\n``IAfterWidgetUpdateEvent`` events and is responsible for looking up any\nJavascript action handlers and create event subscriptions for them.\n\nSo let's setup the environment:\n\n >>> class Form(form.Form):\n ... buttons = button.Buttons(IButtons)\n ...\n ... @jsaction.handler(buttons['hello'])\n ... def showHelloWorldMessage(self, event, selector):\n ... return 'alert(\"Hello World!\");'\n\n >>> form = Form(None, request)\n\nOf course, not just any widget can have Javascript handlers. First of all, the\nwidget must be a field widget:\n\n >>> from z3c.form import widget\n >>> simpleWidget = widget.Widget(request)\n\n >>> jsaction.createSubscriptionsForWidget(\n ... widget.AfterWidgetUpdateEvent(simpleWidget))\n\n >>> interfaces.IHaveJSSubscriptions.providedBy(form)\n False\n\nAnd even if the widget is a field widget,\n\n >>> from z3c.form.browser.button import ButtonFieldWidget\n >>> helloWidget = ButtonFieldWidget(form.buttons['hello'], request)\n\nit still needs to be a form-aware widget:\n\n >>> jsaction.createSubscriptionsForWidget(\n ... widget.AfterWidgetUpdateEvent(helloWidget))\n\n >>> interfaces.IHaveJSSubscriptions.providedBy(form)\n False\n\nSo let's now make it work and add the form to the widget:\n\n >>> from z3c.form.interfaces import IFormAware\n >>> helloWidget.form = form\n >>> zope.interface.alsoProvides(helloWidget, IFormAware)\n\nAfter the subscriber successfully completes, we should have a sJavascript\nsubscription attached to the form:\n\n >>> jsaction.createSubscriptionsForWidget(\n ... widget.AfterWidgetUpdateEvent(helloWidget))\n\n >>> interfaces.IHaveJSSubscriptions.providedBy(form)\n True\n >>> len(list(form.jsSubscriptions))\n 1\n >>> list(form.jsSubscriptions)\n [<JSSubscription\n event=<JSEvent \"click\">, selector=<WidgetSelector \"hello\">,\n handler=<JSHandler <function showHelloWorldMessage at ...>>>]\n\nIn the event that the widget is updated multiple times, and the\nsubscriber gets called multiple times, duplicate subscriptions will\nnot be created.\n\n >>> jsaction.createSubscriptionsForWidget(\n ... widget.AfterWidgetUpdateEvent(helloWidget))\n >>> len(list(form.jsSubscriptions))\n 1\n\nFinally, if the form does not have any Javascript handlers, in other words, it\ndoes not have a ``jsHandlers`` attribute, then the subscriber also aborts:\n\n >>> form = Form(None, request)\n >>> helloWidget.form = object()\n\n >>> jsaction.createSubscriptionsForWidget(\n ... widget.AfterWidgetUpdateEvent(helloWidget))\n\n >>> interfaces.IHaveJSSubscriptions.providedBy(form)\n False\n\nAnd that's all.\n\n==========================\nJavaScript Form Validation\n==========================\n\nThis package also supports widget value validation via Javascript. In\nparticular, the ``jsvalidator`` module implements server-side validation via\nAJAX.\n\n >>> from z3c.formjs import jsvalidator\n\nThere are two components to the validation API. The first is the validator, a\nform mix-in class that makes the validation functionality via a URL and\ndefines the communication protocol of the validation; for example, it defines\nwhat path must be accessed for the validation and what data to send and\nreturn. The second component is the validation script, which is responsible\nfor defining the Javascript code that is executed when validation is\nrequested.\n\n\nMessage Validator\n-----------------\n\nThe goal of the specific message validator is to validate a value, then\nconvert any error into a message and insert the message into the page's\ncontent.\n\nSo let's do some necessary setups:\n\n >>> from z3c.form.testing import setupFormDefaults\n >>> setupFormDefaults()\n\n >>> import zope.component\n >>> from z3c.form import error\n >>> zope.component.provideAdapter(error.ValueErrorViewSnippet)\n\nWe now create a simple form in which all widgets will be validated:\n\n >>> import zope.interface\n >>> import zope.schema\n\n >>> class IAddress(zope.interface.Interface):\n ... zip = zope.schema.Int(title=u\"Zip Code\")\n\n >>> from z3c.form import form, field\n >>> from z3c.form.interfaces import IField\n >>> from z3c.formjs import jsevent, jsaction\n\n >>> class AddressEditForm(jsvalidator.MessageValidator, form.AddForm):\n ... fields = field.Fields(IAddress)\n ...\n ... @jsaction.handler(IField, event=jsevent.CHANGE)\n ... def fieldValidator(self, event, selector):\n ... return self.ValidationScript(self, selector.widget).render()\n\nAfter instantiating the form, ...\n\n >>> from z3c.form.testing import TestRequest\n >>> request = TestRequest()\n >>> edit = AddressEditForm(None, request)\n >>> edit.update()\n\nwe can execute the handler to ensure we get some output:\n\n >>> from z3c.formjs import testing\n >>> testing.setupRenderers()\n\n >>> from z3c.formjs import jsaction\n >>> print edit.fieldValidator(\n ... None, jsaction.WidgetSelector(edit.widgets['zip']))\n $.get('/validate', function(data){ alert(data) })\n\nValidators use AJAX handlers to communicate with the server. Commonly the AJAX\nhandler is looked up via the \"ajax\" view -- see ``ajax.txt`` for more\ndetails. In this case we just create a small helper function:\n\n >>> from z3c.formjs import ajax\n\n >>> def AJAXPlugin(view):\n ... return ajax.AJAXRequestTraverserPlugin(view, view.request)\n\n\n >>> from z3c.formjs import ajax, interfaces\n >>> from zope.publisher.interfaces.browser import IBrowserRequest\n\n >>> zope.component.provideSubscriptionAdapter(\n ... ajax.AJAXRequestTraverserPlugin,\n ... (interfaces.IFormTraverser, IBrowserRequest))\n\nwe can traverse to the ``validate`` method from the \"ajax\" view and render\nit. Let's first render some valid input:\n\n >>> request = TestRequest(form={'widget-name' : 'zip',\n ... 'form.widgets.zip' : u'29132'})\n >>> edit = AddressEditForm(None, request)\n >>> edit.update()\n >>> AJAXPlugin(edit).publishTraverse(None, 'validate')()\n u''\n\nAs you can see there is no error message. Let's now provide an invalid ZIP\ncode. As you can see, we get the expected error message:\n\n >>> request = TestRequest(form={'widget-name': 'zip',\n ... 'form.widgets.zip':'notazipcode'})\n >>> edit = AddressEditForm(None, request)\n >>> edit.update()\n >>> AJAXPlugin(edit).publishTraverse(None, 'validate')()\n u'The entered value is not a valid integer literal.'\n\nOf course, one cannot just traverse to any attribute in the form:\n\n >>> AJAXPlugin(edit).publishTraverse(None, 'ValidationScript')()\n Traceback (most recent call last):\n ...\n NotFound: Object: <AddressEditForm ...>, name: 'ValidationScript'\n\nAnd that's it.\n\n===============================\nConnecting to Javascript Events\n===============================\n\nThe ``jsevent`` module of this package implements a mechanism to connect a\nJavascript script to an event of a DOM element. So let's have a look at how\nthis works.\n\n >>> from z3c.formjs import interfaces, jsevent\n\nTo implement this functionality, we need to model three components: events,\nDOM elements (selector), and the script (handler). We will also need a manager\nto keep track of all the mappings. This is indeed somewhat similar to the Zope\n3 event model, though we do not need DOM elements to connect the events there.\n\n\nSubscription Manager\n--------------------\n\nSo first we need to create a subscription manager in which to collect the\nsubscriptions:\n\n >>> manager = jsevent.JSSubscriptions()\n\nInitially, we have no registered events:\n\n >>> list(manager)\n []\n\nWe now want to subscribe to the \"click\" event of a DOM element with the id\n\"message\". When the event occurs, we would like to display a simple \"Hello\nWorld\" message.\n\nThe events are available in all capital letters, for example:\n\n >>> jsevent.CLICK\n <JSEvent \"click\">\n\nThe DOM element is selected using a selector, in our case an id selector:\n\n >>> selector = jsevent.IdSelector('message')\n >>> selector\n <IdSelector \"message\">\n\nThe handler of the event is a callable accepting the event, selector and the\nrequest:\n\n >>> def showHelloWorldAlert(event, selector, request):\n ... return u'alert(\"Hello World!\")'\n\nWe have finally all the pieces together to subscribe the event:\n\n >>> manager.subscribe(jsevent.CLICK, selector, showHelloWorldAlert)\n <JSSubscription event=<JSEvent \"click\">,\n selector=<IdSelector \"message\">,\n handler=<function showHelloWorldAlert at ...>>\n\nSo now we can see the subscription:\n\n >>> list(manager)\n [<JSSubscription event=<JSEvent \"click\">,\n selector=<IdSelector \"message\">,\n handler=<function showHelloWorldAlert at ...>>]\n\nWe can also get a specific subscription from the manager.\n\n >>> manager['showHelloWorldAlert']\n [<JSSubscription event=<JSEvent \"click\">,\n selector=<IdSelector \"message\">,\n handler=<function showHelloWorldAlert at ...>>]\n\nSo now, how does this get rendered into Javascript code? Since this package\nstrictly separates definition from rendering, a renderer will be responsible\nto produce the output.\n\n\nRenderers\n---------\n\nSo let's define some renderers for the various components. We have already\nprepared renderers for testing purposes in the ``testing`` support module. The\nfirst one is for the id selector\n\n >>> import zope.component\n >>> from z3c.formjs import testing\n >>> zope.component.provideAdapter(testing.IdSelectorRenderer)\n\nOf course, like all view components, the renderer supports the update/render\npattern. We can now render the selector:\n\n >>> from zope.publisher.browser import TestRequest\n >>> request = TestRequest()\n\n >>> renderer = zope.component.getMultiAdapter(\n ... (selector, request), interfaces.IRenderer)\n >>> renderer.update()\n >>> renderer.render()\n u'#message'\n\nNext we need a renderer for the subscription. Let's assume we can bind the\nsubscription as follows: ``$(<selector>).bind(\"<event>\", <script>)``\n\n >>> zope.component.provideAdapter(testing.SubscriptionRenderer)\n\nRendering the subscription then returns this:\n\n >>> renderer = zope.component.getMultiAdapter(\n ... (list(manager)[0], request), interfaces.IRenderer)\n >>> renderer.update()\n >>> print renderer.render()\n $(\"#message\").bind(\"click\", function(){alert(\"Hello World!\")});\n\nAnd now to the grant finale. We create a renderer for the subscription manager.\n\n >>> zope.component.provideAdapter(testing.ManagerRenderer)\n\nLet's now render the entire manager.\n\n >>> renderer = zope.component.getMultiAdapter(\n ... (manager, request), interfaces.IRenderer)\n >>> renderer.update()\n >>> print renderer.render()\n $(document).ready(function(){\n $(\"#message\").bind(\"click\", function(){alert(\"Hello World!\")});\n })\n\n\nThe Subscription Decorator\n--------------------------\n\nWhen defining JS event subscriptions from within a presentation component,\nusing the low-level subscription API is somewhat cumbersome. Thus, there\nexists a decorator called ``subscribe``, which can convert a component method\nas a subscription handler. Let's have a look:\n\n >>> class MyView(object):\n ...\n ... @jsevent.subscribe(jsevent.IdSelector('myid'), jsevent.DBLCLICK)\n ... def alertUser(event, selector, request):\n ... return u\"alert('`%s` event occured on DOM element `%s`');\" %(\n ... event.name, selector.id)\n\nAs you can see, the function is never really meant to be a method, but a\nsubscription handler; thus no ``self`` as first argument. The subscription is\nnow available in the subscriptions manager of the view:\n\n >>> list(MyView.jsSubscriptions)\n [<JSSubscription event=<JSEvent \"dblclick\">,\n selector=<IdSelector \"myid\">,\n handler=<function alertUser at ...>>]\n\nLet's now render the subscription:\n\n >>> renderer = zope.component.getMultiAdapter(\n ... (list(MyView.jsSubscriptions)[0], request), interfaces.IRenderer)\n >>> renderer.update()\n >>> print renderer.render()\n $(\"#myid\").bind(\"dblclick\",\n function(){alert('`dblclick` event occured on DOM element `myid`');});\n\nSubscribe-decorators can also be chained, so that the same handler can be used\nfor multiple selectors and events:\n\n >>> class MyView(object):\n ...\n ... @jsevent.subscribe(jsevent.IdSelector('myid'), jsevent.CLICK)\n ... @jsevent.subscribe(jsevent.IdSelector('myid'), jsevent.DBLCLICK)\n ... def alertUser(event, selector, request):\n ... return u\"alert('`%s` event occured on DOM element `%s`');\" %(\n ... event.name, selector.id)\n\nIn this example we register this handler for both the click and double click\nevent for the DOM element with the id \"myid\".\n\n >>> list(MyView.jsSubscriptions)\n [<JSSubscription event=<JSEvent \"dblclick\">,\n selector=<IdSelector \"myid\">,\n handler=<function alertUser at ...>>,\n <JSSubscription event=<JSEvent \"click\">,\n selector=<IdSelector \"myid\">,\n handler=<function alertUser at ...>>]\n\n\nJavascript Viewlet\n------------------\n\nPutting in the Javascript by hand in every layout is a bit lame. Instead we\ncan just register a viewlet for the JS viewlet manager that renders the\nsubscriptions if a manager is found.\n\nTo use the viewlet we need a view that provides a subscription manager:\n\n >>> class View(object):\n ... zope.interface.implements(interfaces.IHaveJSSubscriptions)\n ... jsSubscriptions = manager\n\nWe can now initialize, update, and finally render the viewlet:\n\n >>> viewlet = jsevent.JSSubscriptionsViewlet(\n ... object(), request, View(), object())\n >>> viewlet.update()\n >>> print viewlet.render()\n <script type=\"text/javascript\">\n $(document).ready(function(){\n $(\"#message\").bind(\"click\", function(){alert(\"Hello World!\")});\n })\n </script>\n\n\nSelectors\n---------\n\nThe module provides several DOM element selectors. It is the responsibility of\nthe corresponding rednerer to interpret the selector.\n\nId Selector\n~~~~~~~~~~~\n\nThe id selector selects a DOM element by id, as seen above. It is simply\ninitialized using the the id:\n\n >>> idselect = jsevent.IdSelector('myid')\n >>> idselect\n <IdSelector \"myid\">\n\nThe id is also available as attribute:\n\n >>> idselect.id\n 'myid'\n\nWe already saw before how it gets rendered:\n\n >>> renderer = zope.component.getMultiAdapter(\n ... (idselect, request), interfaces.IRenderer)\n >>> renderer.update()\n >>> renderer.render()\n u'#myid'\n\nCSS Selector\n~~~~~~~~~~~~\n\nThe CSS selector selects a DOM element using an arbitrary CSS selector\nexpression. This selector is initialized using the expression:\n\n >>> cssselect = jsevent.CSSSelector('div.myclass')\n >>> cssselect\n <CSSSelector \"div.myclass\">\n\nThe CSS selector expression is also available as attribute:\n\n >>> cssselect.expr\n 'div.myclass'\n\nLet's now see an example on how the CSS selector can be rendered:\n\n >>> zope.component.provideAdapter(testing.CSSSelectorRenderer)\n\n >>> renderer = zope.component.getMultiAdapter(\n ... (cssselect, request), interfaces.IRenderer)\n >>> renderer.update()\n >>> renderer.render()\n u'div.myclass'\n\nSince most JS libraries support CSS selectors by default, the renderer simply\nconverts the expression to unicode.\n\n\nAvailable Events\n----------------\n\nThis package maps all of the available JavaScript events. Here is the complete\nlist:\n\n >>> jsevent.CLICK\n <JSEvent \"click\">\n >>> jsevent.DBLCLICK\n <JSEvent \"dblclick\">\n >>> jsevent.CHANGE\n <JSEvent \"change\">\n >>> jsevent.LOAD\n <JSEvent \"load\">\n >>> jsevent.BLUR\n <JSEvent \"blur\">\n >>> jsevent.FOCUS\n <JSEvent \"focus\">\n >>> jsevent.KEYDOWN\n <JSEvent \"keydown\">\n >>> jsevent.KEYUP\n <JSEvent \"keyup\">\n >>> jsevent.MOUSEDOWN\n <JSEvent \"mousedown\">\n >>> jsevent.MOUSEMOVE\n <JSEvent \"mousemove\">\n >>> jsevent.MOUSEOUT\n <JSEvent \"mouseout\">\n >>> jsevent.MOUSEOVER\n <JSEvent \"mouseover\">\n >>> jsevent.MOUSEUP\n <JSEvent \"mouseup\">\n >>> jsevent.RESIZE\n <JSEvent \"resize\">\n >>> jsevent.SELECT\n <JSEvent \"select\">\n >>> jsevent.SUBMIT\n <JSEvent \"submit\">\n\nThese are also provided as utilities so they can be looked up by name.\n\n >>> import zope.component\n >>> zope.component.provideUtility(jsevent.CLICK, name='click')\n\nOf course, we can now just look up the utility:\n\n >>> zope.component.getUtility(interfaces.IJSEvent, 'click')\n <JSEvent \"click\">\n\n\n=======\nCHANGES\n=======\n\nVersion 0.5.0 (2009-07-23)\n--------------------------\n\n- Feature: Update to the latest package versions.\n\n- Bug: Avoid ``ForbiddenAttribute`` in ``jsvalidator.MessageValidator``.\n\n\nVersion 0.4.1 (2008-12-16)\n--------------------------\n\n- Restructure: Use WeightOrderedViewletManager from zope.viewlet instead\n of z3c.viewlet, that removes additional egg requirement.\n\n\nVersion 0.4.0 (2008-08-26)\n--------------------------\n\n- Feature: There is now a special unique prefix generator that uses\n `z3c.form`'s new ``createCSSId()`` function to generate css selectable\n prefixes for ajax forms.\n\n- Feature: There is now a viewlet manager already registered with all\n the viewlets necessary to use `z3c.formjs`. You can now just do:\n\n <script tal:replace=\"structure\n provider:z3c.formjs.interfaces.IDynamicJavaScript\">\n </script>\n\n- Feature: When AJAX handlers return complex data structures (dictionaries,\n lists and tuples), the data is automatically converted into JSON\n format before delivery.\n\n- Restructure: Make package run on latest z3c.form 1.9.0 release.\n\n- Bug: Widgets that were being updated multiple times were generating\n duplicate javascript event subscriptions. This is now fixed.\n\n\nVersion 0.3.0 (2007-10-03)\n--------------------------\n\n- Feature: Made a JavaScript renderer for calls to JS Functions.\n\n- Feature: Implemented tools to make server side events propagate to\n the client.\n\n- Feature: Now the ``jsevent.subscribe`` and ``jsaction.handler`` decorators\n can be chained together, allowing them to be called multiple time for the\n same methods.\n\n- Feature: Implemented ability to switch widget modes on a form.\n\n\nVersion 0.2.0 (2007-07-18)\n--------------------------\n\n- Feature: Registration of public AJAX server calls via a simple\n decorator. The calls are made available via a special ``ajax`` view on the\n original view.\n\n- Feature: Allow registering of JS subscriptions via a decorator within the\n presentation component.\n\n- Feature: Added a new CSS selector.\n\n- Feature: Implementation of AJAX-driven widget value validation.\n\n- Restructure: Completely overhauled the entire API to be most easy to use and\n have the most minimal implementation.\n\n- Bug: The package is now 100% tested.\n\n- Feature: Implementation of AJAX request handlers in forms.\n\nVersion 0.1.0 (2007-06-29)\n--------------------------\n\n- Initial Release\n\n * Feature: JS event association with fields and buttons.\n\n\n====\nTODO\n====\n\n - client side js validators for simple fields. (maybe we can use an\n existing library?)", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://pypi.python.org/pypi/z3c.formjs", "keywords": "zope3 form javascript", "license": "ZPL 2.1", "maintainer": null, "maintainer_email": null, "name": "z3c.formjs", "package_url": "https://pypi.org/project/z3c.formjs/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/z3c.formjs/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://pypi.python.org/pypi/z3c.formjs" }, "release_url": "https://pypi.org/project/z3c.formjs/0.5.0/", "requires_dist": null, "requires_python": null, "summary": "Javascript integration into ``z3c.form``", "version": "0.5.0" }, "last_serial": 731503, "releases": { "0.2.0": [ { "comment_text": "", "digests": { "md5": "42ef7f4e79198fab2691a23b1b77f786", "sha256": "2c3ce4e6338c7dddcbeb351d3da682cd72ee4e362c577f1c0fc519209d936710" }, "downloads": -1, "filename": "z3c.formjs-0.2.0-py2.4.egg", "has_sig": false, "md5_digest": "42ef7f4e79198fab2691a23b1b77f786", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 59841, "upload_time": "2007-07-19T03:40:58", "url": "https://files.pythonhosted.org/packages/4f/da/f006998c5d776dd148a57a9857495c35335bf0de1b3faf3b60f4de99d5a4/z3c.formjs-0.2.0-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "96798cff5cb7d9c8fc8331be312e690d", "sha256": "d774f7c8274275e91c0de30e3ede28860f19973624613af2ed7d0b5d4332df4b" }, "downloads": -1, "filename": "z3c.formjs-0.2.0.tar.gz", "has_sig": false, "md5_digest": "96798cff5cb7d9c8fc8331be312e690d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44890, "upload_time": "2007-07-19T03:40:48", "url": "https://files.pythonhosted.org/packages/fc/c2/30891948389942dc3f1520b855af64084029ba26edffcfac1054dfa97651/z3c.formjs-0.2.0.tar.gz" } ], "0.2.1": [ { "comment_text": "", "digests": { "md5": "65fa1c2028d6e16e961d2b29e03b8946", "sha256": "f3a3e93f72a420445cdf141e1db869c7593e0c65eafb6a6df421feaa093b83fd" }, "downloads": -1, "filename": "z3c.formjs-0.2.1-py2.4.egg", "has_sig": false, "md5_digest": "65fa1c2028d6e16e961d2b29e03b8946", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 59840, "upload_time": "2007-07-30T22:45:40", "url": "https://files.pythonhosted.org/packages/a9/25/d81d99a8d9ff58aa0277eb33552df1a9567489c7d33432753255cc3956a4/z3c.formjs-0.2.1-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "b0b199682dc682f67faa66e5eab5e94b", "sha256": "122a9e98f3f93d84f12d941d1344f17d6308d6a106aa221c37ad5a62a30937c2" }, "downloads": -1, "filename": "z3c.formjs-0.2.1.tar.gz", "has_sig": false, "md5_digest": "b0b199682dc682f67faa66e5eab5e94b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 44937, "upload_time": "2007-07-30T22:45:39", "url": "https://files.pythonhosted.org/packages/3d/63/4b98712142450a72784b979a479bef5df79310b9ebebebe51fa906727df2/z3c.formjs-0.2.1.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "d59ede6b08c6f2901a65561b4d66aeca", "sha256": "3f590923ad4ededf788f53ecd5f973365c9d719d21e81e60e7cdd3c44fb6e7d1" }, "downloads": -1, "filename": "z3c.formjs-0.3.0.tar.gz", "has_sig": false, "md5_digest": "d59ede6b08c6f2901a65561b4d66aeca", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 58161, "upload_time": "2007-10-04T03:16:15", "url": "https://files.pythonhosted.org/packages/0f/59/8de5113c41ae176c3cf6b037b23754c0b108063530f4a26db5024254a2dd/z3c.formjs-0.3.0.tar.gz" } ], "0.4.0": [ { "comment_text": "", "digests": { "md5": "6edf3f686e5762d1e5be1fa3c4d7e3ac", "sha256": "7b5df1fc25fd8ef21ab11b5f4cc53114178680e5ed77872a382e656c30d5a268" }, "downloads": -1, "filename": "z3c.formjs-0.4.0.tar.gz", "has_sig": false, "md5_digest": "6edf3f686e5762d1e5be1fa3c4d7e3ac", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 61405, "upload_time": "2008-08-27T04:55:26", "url": "https://files.pythonhosted.org/packages/93/a9/4cb9d25b3c0ec0b2cd5e3357a451cf445217c188bb8d789e8ba41643b423/z3c.formjs-0.4.0.tar.gz" } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "c3c0f2ab5c7a60d596ef763d2c252fca", "sha256": "f8ac151ca67b1029df546c56e62e506019f1dc21d7f4ca3b3d7d8c5ae0d2c7f0" }, "downloads": -1, "filename": "z3c.formjs-0.4.1.tar.gz", "has_sig": false, "md5_digest": "c3c0f2ab5c7a60d596ef763d2c252fca", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 62368, "upload_time": "2008-12-16T09:41:35", "url": "https://files.pythonhosted.org/packages/f8/b2/f60096c3e7174edcb56030a3a35c7680965f065e5286a85c3621289aa25f/z3c.formjs-0.4.1.tar.gz" } ], "0.5.0": [ { "comment_text": "", "digests": { "md5": "b31d9513a3233091bda660c83d45d202", "sha256": "d85e7f5297402991154f3512fe5dee5426f9006e6e8b8d8a8bcf01620c9d707c" }, "downloads": -1, "filename": "z3c.formjs-0.5.0.tar.gz", "has_sig": false, "md5_digest": "b31d9513a3233091bda660c83d45d202", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 61603, "upload_time": "2009-07-23T07:03:59", "url": "https://files.pythonhosted.org/packages/ca/ec/2a9c7a352c4809106e96f4decfaf0b3f3a82c0b76ab67d760523aba6f54d/z3c.formjs-0.5.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "b31d9513a3233091bda660c83d45d202", "sha256": "d85e7f5297402991154f3512fe5dee5426f9006e6e8b8d8a8bcf01620c9d707c" }, "downloads": -1, "filename": "z3c.formjs-0.5.0.tar.gz", "has_sig": false, "md5_digest": "b31d9513a3233091bda660c83d45d202", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 61603, "upload_time": "2009-07-23T07:03:59", "url": "https://files.pythonhosted.org/packages/ca/ec/2a9c7a352c4809106e96f4decfaf0b3f3a82c0b76ab67d760523aba6f54d/z3c.formjs-0.5.0.tar.gz" } ] }