[Checkins] SVN: zc.extjs/branches/dev/src/zc/extjs/ Merged changes from .com branch.

Gintautas Miliauskas gintas at pov.lt
Fri Mar 28 19:52:26 EDT 2008


Log message for revision 85007:
  Merged changes from .com branch.
  
      svn merge -r 23365:23418 svn+ssh://svn.zope.com/repos/main/zc.extjs/branches/dev/src/zc/extjs
  
  

Changed:
  U   zc.extjs/branches/dev/src/zc/extjs/application.txt
  U   zc.extjs/branches/dev/src/zc/extjs/form.py
  U   zc.extjs/branches/dev/src/zc/extjs/form.txt
  U   zc.extjs/branches/dev/src/zc/extjs/form_example.py
  U   zc.extjs/branches/dev/src/zc/extjs/resources/util.js
  U   zc.extjs/branches/dev/src/zc/extjs/resources/widgets.js
  U   zc.extjs/branches/dev/src/zc/extjs/session.txt
  U   zc.extjs/branches/dev/src/zc/extjs/widgets.py
  U   zc.extjs/branches/dev/src/zc/extjs/widgets.zcml

-=-
Modified: zc.extjs/branches/dev/src/zc/extjs/application.txt
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/application.txt	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/application.txt	2008-03-28 23:52:25 UTC (rev 85007)
@@ -9,7 +9,7 @@
 
 The basic idea is that an application can be provided using a single
 Zope 3 view plus necessary resource-library definitions.  This view
-has a URL.  It typically provides many ajax method whose URLs have the
+has a URL.  It typically provides many ajax methods whose URLs have the
 view URL as a base.
 
 Many applications can be implemented using a simple class that can be
@@ -98,7 +98,7 @@
 providing a login form variable. 
 
     >>> browser.open('http://localhost/calculator.html?login')
-    >>> print browser.contents # doctest: +NORMALIZE_WHITESPACE
+    >>> print browser.contents # doctest: +NORMALIZE_WHITESPACE, +REPORT_NDIFF
     <html><head>
     <base href="http://localhost/calculator.html/index.html" />
     <BLANKLINE>
@@ -152,7 +152,7 @@
 property indicating whether a form was successfully processed. If
 success is false, it also expects an errors property that is an object
 mapping field names to errors to be displayed with error fields.  The
-zc.extjs packge provides an ajax helper, named call_server that follows a
+zc.extjs packge provides an ajax helper named call_server that follows a
 similar convention.  If a result has a false success propery and an
 error property containing an error message, it displays an alert with
 the error message.  There's also a form helper that displays an alert
@@ -161,31 +161,33 @@
 dictionary is returned that doesn't have one.  The property is true
 unless there are error or errors properties.  
 
+    >>> import simplejson
+
     >>> browser.open('http://localhost/@@calculator.html/value')
-    >>> print browser.contents
-    {"success": true, "value": 0}
+    >>> simplejson.loads(browser.contents)
+    {u'value': 0, u'success': True}
 
     >>> browser.open('http://localhost/@@calculator.html/add?value=hi')
-    >>> print browser.contents
-    {"success": false, "error": "The value must be an integer!"}
+    >>> simplejson.loads(browser.contents)
+    {u'success': False, u'error': u'The value must be an integer!'}
 
 Also, if a method returns None, an object with a true success property
 is returned:
 
     >>> browser.open('http://localhost/@@calculator.html/noop')
-    >>> print browser.contents
-    {"success": true}
+    >>> simplejson.loads(browser.contents)
+    {u'success': True}
 
 If something other than a dictionary is returned from a Python
 method, no success attribute is added:
 
     >>> browser.open('http://localhost/@@calculator.html/about')
-    >>> print browser.contents
-    "Calculator 1.0"
+    >>> simplejson.loads(browser.contents)
+    u'Calculator 1.0'
 
     >>> browser.open('http://localhost/@@calculator.html/operations')
-    >>> print browser.contents
-    ["add", "subtract"]
+    >>> simplejson.loads(browser.contents)
+    [u'add', u'subtract']
 
 If you want to marshal JSON yourself, you can use the
 zc.extjs.application.jsonpage decorator:
@@ -196,8 +198,8 @@
 done by the subtract method in our example:
 
     >>> browser.open('http://localhost/@@calculator.html/subtract?value=hi')
-    >>> print browser.contents
-    {"success": false, "error": "The value must be an integer!"}
+    >>> simplejson.loads(browser.contents)
+    {u'success': False, u'error': u'The value must be an integer!'}
 
 This works because there is a view registered for
 zope.exceptions.interfaces.IUserError, and
@@ -207,7 +209,7 @@
 ===============
 
 zc.extjs.testing has some helper functions to make it easier to test
-ajax calls.  
+ajax calls.
 
 The call_form function makes an ajax call, marshaling input data and
 de-marshaling output data. It's called call_form because it marshals
@@ -307,7 +309,7 @@
 help with creating sub-applications:
 
 SubApplication
-    This class, which Application subclasses provides traversal to
+    This class, which Application subclasses, provides traversal to
     attributes that provide IBrowserPublisher.  It also stamps
     IAjaxRequest on the request object when an object is traversed.
 

Modified: zc.extjs/branches/dev/src/zc/extjs/form.py
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/form.py	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/form.py	2008-03-28 23:52:25 UTC (rev 85007)
@@ -37,11 +37,12 @@
     __Security_checker__ = zope.security.checker.NamesChecker((
         '__call__', 'browserDefault', 'publishTraverse'))
 
-    def __init__(self, context, request=None):
-        self.context = context
+    def __init__(self, app, request=None):
+        self.app = app
         if request is None:
-            request = context.request
+            request = app.request
         self.request = request
+        self.context = app.context
 
     @zope.cachedescriptors.property.Lazy
     def prefix(self):
@@ -49,7 +50,7 @@
 
     @zope.cachedescriptors.property.Lazy
     def base_href(self):
-        base_href = getattr(self.context, 'base_href', None)
+        base_href = getattr(self.app, 'base_href', None)
         if base_href is not None:
             base_href += '/'
         else:
@@ -58,23 +59,25 @@
 
     def get_definition(self):
         widgets = zope.formlib.form.setUpWidgets(
-            self.form_fields, self.prefix, self.context.context, self.request,
+            self.form_fields, self.prefix, self.context, self.request,
             ignore_request=True)
 
+        for widget in widgets:
+            # A programmer's check to make sure that we have the right
+            # type of widget.
+            assert hasattr(widget, 'js_config'), widget.name
+
         return dict(
-            widgets = [widget.js_config() for widget in widgets],
+            widgets=[widget.js_config() for widget in widgets],
             widget_names = dict((widget.name, i)
-                                for (i, widget) in enumerate(widgets)
-                                ),
-            actions = [dict(label=action.label,
-                            url="%s/%s" % (self.base_href,
-                                           action.__name__.split('.')[-1],
-                                           ),
-                            name=action.__name__,
-                            )
-                       for action in self.actions],
+                                for (i, widget) in enumerate(widgets)),
+            actions=[dict(label=action.label,
+                          url="%s/%s" % (self.base_href,
+                                         action.__name__.split('.')[-1]),
+                          name=action.__name__)
+                     for action in self.actions]
             )
-        
+
     def __call__(self):
         """Return rendered js widget configs
         """
@@ -93,7 +96,7 @@
 
     def getObjectData(self, ob, extra=()):
         widgets = zope.formlib.form.setUpWidgets(
-            self.form_fields, self.prefix, self.context.context, self.request,
+            self.form_fields, self.prefix, self.context, self.request,
             ignore_request=True)
 
         result = {}
@@ -107,6 +110,7 @@
 
         return result
 
+
 class Action(object):
 
     zope.interface.implementsOnly(
@@ -148,13 +152,12 @@
                             IWidgetInputErrorView,
                             )
                         error = view.snippet()
-                    
+
                     field_errors[widget.name] = error
 
         if field_errors:
             return zc.extjs.application.result(dict(errors=field_errors))
 
-
         # XXX invariants and action conditions
         # XXX action validator and failure handlers
 
@@ -162,3 +165,12 @@
 
     def browserDefault(self, request):
         return self, ()
+
+
+class EditForm(Form):
+
+    @zope.formlib.form.action("Apply")
+    def apply(self, action, data):
+        if zope.formlib.form.applyChanges(self.context, self.form_fields, data):
+            zope.event.notify(ObjectModifiedEvent(self.context))
+

Modified: zc.extjs/branches/dev/src/zc/extjs/form.txt
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/form.txt	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/form.txt	2008-03-28 23:52:25 UTC (rev 85007)
@@ -12,7 +12,7 @@
 To create a form, just create a form class as a subclass of
 zc.extjs.form.Form. This base class provides:
 
-- an ajax __call__ method that retuirns a form definition,
+- an ajax __call__ method that returns a form definition,
 
 - traversal to form actions, in much the same way that
   zc.extjs.application.Application [#application]_ provides traversal
@@ -33,9 +33,10 @@
 define the form class elsewhere and use it, but if a form is only used
 in an application, then it's often easiest to define it within an
 application class.  Forms are instantiated by calling them with a
-single argument.  This argument becomes the form's page attribute.
-Form classes are automatically instantiated when a form class is
-assigned to an attribute in a class and accessed through an instance
+single argument.  This argument, the application, becomes the form's `app`
+attribute.  The application's context becomes the form's context.  Form
+classes are automatically instantiated when a form class is assigned to
+an attribute in a class and accessed through an instance
 [#form_classes_are_descriptors]_.
 
 Let's try accessing our form:
@@ -95,9 +96,9 @@
 the zc.extjs resource library that provides additional information,
 like Javascript validators, that can't be expressed in JSON.
 
-The're an action definition for each action defined in the form.  The
-action information includes the url to post the result to, reletive to
-the application.  
+There is an action definition for each action defined in the form.  The
+action information includes the url to post the result to, relative to
+the application.
 
 Note that the name of the form class is used as the form prefix and
 that the form prefix is used as the prefix for widget and action names
@@ -142,29 +143,31 @@
                u'favorite_color': u'',
                u'first_name': u'Bob',
                u'last_name': u'Zope'},
+     u'self_app_class_name': u'FormExample',
      u'self_class_name': u'ExampleForm',
-     u'self_context_class_name': u'FormExample',
-     u'self_context_context_class_name': u'Folder',
+     u'self_context_class_name': u'Folder',
      u'success': True}
 
 Here we get a successful result.  Our contrived action in the example
 simply echoed back the data it was passed,  Note, in particular that:
 
-- the data keys have the form profix removed, and that
+- the data keys have the form prefix removed, and
 
 - the value of the age key is an integer, since the field was an
   integer field.
 
-The action also prints out the classes of it's self argument, and it's
-contexts.  Actions are methods of forms so their self argument is the
-form. The form context is the page it's accessed through.
+The action also prints out the classes of its self argument, its app
+and its context. Actions are methods of forms so their `self` argument is the
+form. The form's `app` is the app through which it is accessed and
+`context` is the app's context.
 
+
 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 from
-calling get_definition.  
+Sometimes we want to get form definitions from Python.  The form
+__call__ method returns a JSON string.  We can get Python data by
+calling get_definition.
 
     >>> import zc.extjs.form_example
     >>> import zope.publisher.browser
@@ -213,7 +216,7 @@
                   'widget_constructor': 'zc.extjs.widgets.InputInt'}]}
 
 Note that we had to stamp the request with IAjaxRequest.  This is done
-during application traversal.  We need it som widgets can get looked
+during application traversal.  We need it so widgets can get looked
 up.
 
 
@@ -221,10 +224,10 @@
 ---------------
 
 Forms have base_href and prefix variables.  The base_href is used to compute
-URLs for form actions.  A form's base_href defaults to it's class name.
-The form's base_href also includes the base_href of it's page, if it's page has
-a base_href. This is useful for sub-application. Let's give our sample
-application a base_href attribute as if it was a sub-application:
+URLs 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 has
+a base_href. This is useful for sub-applications. Let's give our sample
+application a base_href attribute as if it were a sub-application:
 
     >>> ex = zc.extjs.form_example.FormExample(None, request)
     >>> ex.base_href = 'sample'
@@ -270,18 +273,19 @@
 
 Note that the action URL now includes "sample/" as a prefix.  Also
 note that the widget and action names have "sample." as a prefix.  The
-form prefix is simply it's base with "/"s converted to "."s.
+form 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 data
 and the form definition can be fetched separately.  For example, we
 may use the same form to edit multiple objects.  Form objects have a
-getObjectData method that returns adta suitable for etting form field
+getObjectData method that returns data suitable for editing form field
 values.  Let's create a person and use out form to get data for them:
 
     >>> bob = zc.extjs.form_example.Person('bob', 'smith', None, 11)
@@ -293,8 +297,7 @@
 We didn't set the favorite_color for the person, so it is ommitted
 from the data.
 
-We can pass in a dictionary of values that take precedence over object
-data: 
+We can pass in a dictionary of values that take precedence over object data:
 
     >>> pprint(ex.ExampleForm.getObjectData(
     ...            bob, {'sample.ExampleForm.age': u'1'}),
@@ -302,9 +305,8 @@
     {'sample.ExampleForm.age': u'1',
      'sample.ExampleForm.first_name': u'bob',
      'sample.ExampleForm.last_name': u'smith'}
-   
-    
 
+
 To-do (maybe)
 -------------
 
@@ -322,8 +324,6 @@
 
 
 
-
-
 .. [#application] See application.txt
 
 .. [#form_classes_are_descriptors] Form classes are also

Modified: zc.extjs/branches/dev/src/zc/extjs/form_example.py
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/form_example.py	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/form_example.py	2008-03-28 23:52:25 UTC (rev 85007)
@@ -42,7 +42,7 @@
     resource_library_name = None
 
     class ExampleForm(zc.extjs.form.Form):
-        
+
         form_fields = zope.formlib.form.Fields(IPerson)
 
         @zope.formlib.form.action("Register")
@@ -50,7 +50,6 @@
             return dict(
                 data = data,
                 self_class_name = self.__class__.__name__,
-                self_context_class_name = self.context.__class__.__name__,
-                self_context_context_class_name =
-                self.context.context.__class__.__name__,
+                self_app_class_name = self.app.__class__.__name__,
+                self_context_class_name = self.context.__class__.__name__
                 )

Modified: zc.extjs/branches/dev/src/zc/extjs/resources/util.js
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/resources/util.js	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/resources/util.js	2008-03-28 23:52:25 UTC (rev 85007)
@@ -50,7 +50,7 @@
                 callback: callback});
     }
 
-    function new_form(args)
+    function get_form_config(args)
     {
         var config = Ext.apply({}, args.config);
 
@@ -64,14 +64,13 @@
             config.items.push(zc.extjs.widgets.Field(
                 args.definition.widgets[i]));
 
-        for (var i=0; i < args.definition.actions.length; i++)
-        {
+        for (var i=0; i < args.definition.actions.length; i++) {
             var url = args.definition.actions[i].url;
             config.buttons.push({
                 text: args.definition.actions[i].label,
                 id: args.definition.actions[i].name,
-                handler: function () 
-                {
+                handler: function () {
+                    var form = config.form;
                     if (! form_valid(form))
                         return;
                     form.getForm().submit({
@@ -83,12 +82,70 @@
                 }
             });
         }
+        return config;
+    }
+
+    function new_form(args)
+    {
+        var config = get_form_config(args);
         var form = new Ext.form.FormPanel(config);
+        config.form = form; // Used for action handlers (see get_form_config)
         return form;
     }
 
-    function form_dialog (args)
+    function form_panel(args, callback)
     {
+        // Create a form panel by loading a description of the fields.
+        // `args` is a dict of options; make sure to specify `url`.
+        //
+        // This function is asynchonous!  It returns immediately, and
+        // calls `callback` with the newly constructed form panel only
+        // when it is ready.  Normally the callback would add the panel
+        // to some container.  Remember to call container.doLayout()
+        // after adding the component.
+        call_server({
+            url: args.url,
+            params: args.params,
+            task: "Loading form definition",
+            success: function (result) {
+                var form_config = {
+                    autoHeight: true,
+                    buttons: [{
+                        text: 'Cancel',
+                        handler: function ()
+                        {
+                            form_reset(form_config.form, result.data);
+                        }
+                    }]
+                };
+
+                if (args.form_config) {
+                    if (args.form_config.buttons)
+                        args.form_config.buttons = (
+                            args.form_config.buttons.concat(
+                                form_config.buttons));
+                    form_config = Ext.apply(form_config, args.form_config);
+                }
+
+                var formpanel = zc.extjs.util.new_form({
+                    definition: result.definition,
+                    config: form_config,
+                    after: function (form, action)
+                    {
+                        dialog.hide();
+                        if (args.after)
+                            args.after(form, action);
+                    }
+                });
+
+                form_reset(formpanel, result.data);
+                callback(formpanel);
+            }
+        });
+    }
+
+    function form_dialog(args)
+    {
         var dialog;
         return function (data) {
             if (dialog)
@@ -168,15 +225,15 @@
             system_error("Submitting this form");
     }
 
-    function form_reset (form_panel, data)
+    function form_reset(formpanel, data)
     {
-        form_panel.getForm().reset();
+        formpanel.getForm().reset();
         if (data)
             for (var field_name in data)
-                form_panel.find('name', field_name)[0].setValue(
+                formpanel.find('name', field_name)[0].setValue(
                     data[field_name]);
-     }
-   
+    }
+
     function form_valid(form) 
     {
         if (form.getForm().isValid())
@@ -185,12 +242,12 @@
         return false;
     }
 
-    function init ()
+    function init()
     {
         Ext.QuickTips.init();
     }
 
-    function map (func, sequence)
+    function map(func, sequence)
     {
         var result = [];
         for (var i=0; i < sequence.length; i++)
@@ -198,7 +255,7 @@
         return result;
     }
 
-    function session_expired () 
+    function session_expired()
     {
         Ext.MessageBox.alert(
             'Session Expired',
@@ -206,16 +263,18 @@
             window.location.reload.createDelegate(window.location)
         );
     }
-        
-    function system_error (task)
+
+    function system_error(task)
     {
         Ext.MessageBox.alert("Failed",task+" failed for an unknown reason");
     }
 
     return {
         call_server: call_server,
-        form: new_form,
+        new_form: new_form,
+        get_form_config: get_form_config,
         form_dialog: form_dialog,
+        form_panel: form_panel,
         form_failure: form_failure,
         form_reset: form_reset,
         form_valid: form_valid,

Modified: zc.extjs/branches/dev/src/zc/extjs/resources/widgets.js
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/resources/widgets.js	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/resources/widgets.js	2008-03-28 23:52:25 UTC (rev 85007)
@@ -50,6 +50,20 @@
             return config;
         },
 
+        InputDecimal: function (widget)
+        {
+            var config = Ext.apply({xtype: 'textfield'}, widget);
+
+            config.validator = function (value) {
+                value = Number(value);
+                return !isNaN(value);
+            };
+            config.maskRe = /[-0-9.]/;
+            config.regex = /^-?[0-9.]+$/;
+            config.regexText = 'The input must be a decimal number.';
+            return config;
+        },
+
         InputChoice: function (widget)
         {
             var config = Ext.apply({xtype: 'combo'}, widget);

Modified: zc.extjs/branches/dev/src/zc/extjs/session.txt
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/session.txt	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/session.txt	2008-03-28 23:52:25 UTC (rev 85007)
@@ -36,8 +36,9 @@
 
     >>> browser.addHeader('X-Requested-With', 'XMLHTTPRequest')
     >>> browser.open('http://localhost/@@index.html/about')
-    >>> browser.contents
-    '{"session_expired": true, "success": false}'
+    >>> import simplejson
+    >>> simplejson.loads(browser.contents)
+    {u'session_expired': True, u'success': False}
 
 
 .. [#application] See application.txt

Modified: zc.extjs/branches/dev/src/zc/extjs/widgets.py
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/widgets.py	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/widgets.py	2008-03-28 23:52:25 UTC (rev 85007)
@@ -12,15 +12,17 @@
 #
 ##############################################################################
 
-import zc.extjs.interfaces
-import zope.app.form
-import zope.app.form.interfaces
-import zope.app.form.browser.interfaces
+import decimal
 import zope.interface
 import zope.cachedescriptors.property
 import zope.component
 import zope.schema.interfaces
+import zope.app.form
+import zope.app.form.interfaces
+import zope.app.form.browser.interfaces
+import zc.extjs.interfaces
 
+
 class Base(zope.app.form.InputWidget):
 
     zope.interface.implements(zc.extjs.interfaces.IInputWidget)
@@ -121,7 +123,6 @@
         return bool(v)
 
 
-    
 class InputChoiceIterable(Base):
 
     zope.component.adapts(
@@ -242,6 +243,29 @@
                 u"Invalid integer: %r" % v
                 )
 
+
+class InputDecimal(Base):
+
+    zope.component.adapts(
+        zope.schema.interfaces.IDecimal,
+        zc.extjs.interfaces.IAjaxRequest)
+
+    widget_constructor = 'zc.extjs.widgets.InputDecimal'
+
+    def _is_missing(self, raw):
+        return not raw
+
+    def _toForm(self, v):
+        return str(v)
+
+    def _toValue(self, v):
+        try:
+            return decimal.Decimal(v)
+        except decimal.InvalidOperation:
+            raise zope.app.form.interfaces.ConversionError(
+                u"Invalid decimal: %r" % v)
+
+
 class InputTextLine(Base):
 
     zope.component.adapts(

Modified: zc.extjs/branches/dev/src/zc/extjs/widgets.zcml
===================================================================
--- zc.extjs/branches/dev/src/zc/extjs/widgets.zcml	2008-03-28 23:39:18 UTC (rev 85006)
+++ zc.extjs/branches/dev/src/zc/extjs/widgets.zcml	2008-03-28 23:52:25 UTC (rev 85007)
@@ -21,6 +21,11 @@
      />
 
   <adapter
+     factory=".widgets.InputDecimal"
+     provides="zope.app.form.interfaces.IInputWidget"
+     />
+
+  <adapter
      factory=".widgets.InputTextLine"
      provides="zope.app.form.interfaces.IInputWidget"
      />



More information about the Checkins mailing list