You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2012/08/14 03:33:15 UTC

[2/11] git commit: Remove FormEventManager, replaced with top-level event handlers in the core/forms module Remove the error icon image next to fields' Rebuild some of the logic linking forms, fields, validators, and the FieldEventManager

Remove FormEventManager, replaced with top-level event handlers in the core/forms module
Remove the error icon image next to fields'
Rebuild some of the logic linking forms, fields, validators, and the FieldEventManager


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/b1c01eb8
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/b1c01eb8
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/b1c01eb8

Branch: refs/heads/5.4-js-rewrite
Commit: b1c01eb8a99ce25710caeda807ad1f5ae083648d
Parents: ce08b5a
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Mon Aug 13 18:32:39 2012 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Mon Aug 13 18:32:39 2012 -0700

----------------------------------------------------------------------
 .../META-INF/modules/core/events.coffee            |   34 ++-
 .../META-INF/modules/core/forms.coffee             |  100 +++++
 .../coffeescript/META-INF/modules/core/spi.coffee  |   45 +++-
 .../apache/tapestry5/corelib/components/Form.java  |   26 +-
 .../internal/DefaultValidationDecorator.java       |   33 +--
 .../services/ValidationDecoratorFactoryImpl.java   |   10 +-
 .../resources/org/apache/tapestry5/tapestry.js     |  285 +++------------
 .../internal/DefaultValidationDecoratorTest.java   |   12 +-
 8 files changed, 247 insertions(+), 298 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b1c01eb8/tapestry-core/src/main/coffeescript/META-INF/modules/core/events.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/events.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/events.coffee
index 9832c02..b754fc8 100644
--- a/tapestry-core/src/main/coffeescript/META-INF/modules/core/events.coffee
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/events.coffee
@@ -18,6 +18,34 @@
 # trigger or listener for. Prototype requires that all custom events have a namespace prefix; jQuery appears to
 # allow it without issue.
 define
+  # Defines events related to the validation and submission of forms. See module `core/forms` for further details.
+  # All events are triggered on a specific HTML `<form>` element, and top-level handlers take it from there.
+  form:
+
+    # Triggered after `events.field.validate`, when there are no field validation exceptions, to allow for cross-form
+    # validation.
+    validateForm: "t5:form:validate"
+
+    # Triggered after `validateForm` (when there are no prior validation exceptions), to allow certain elements
+    # to configure themselves immediately before the form is submitted. This exists primarily for components such
+    # as FormFragment, which will update a enable or disable a hidden field to match the visibility of the fragment.
+    prepareForSubmit: "t5:form:prepare-for-submit"
+
+    # Triggered last, when the form is configured to not submit normally (as a standard POST). Under 5.3, this
+    # configuration was achieved by adding the `t-prevent-submission` CSS class; under 5.4 it is preferred to
+    # set the `data-t5-prevent-submission` attribute. In either case, the submit event is stopped, and this
+    # event fired to replace it; in most cases, a handler will then handle submitting the form's data as part
+    # of an Ajax request.
+    processSubmit: "t5:form:process-submit"
+
+  field:
+    # Triggered by the Form on all enclosed elements with the `data-t5-validation` attribute (indicating they are
+    # interested in participating with user input validation). The memo object passed to the event has an error property
+    # that can be set to true to indicate a validation error. Individual fields should determine if the field is in
+    # error and remove or add/update decorations for the validation error (decorations will transition from 5.3 style
+    # popups to Twitter Bootstrap in the near future).
+    validate: "t5:field:validate"
+
   # Defines a number of event names specific to Tapestry Zones. Zones are Tapestry components that are structured
   # to correctly support dynamic updates from the server via an Ajax request, and a standard response
   # (the partial page render reponse). More details are available in the `core/zone` module.
@@ -26,11 +54,11 @@ define
     # `core/spi:ElementWrapper`, or a string containing HTML markup). A standard top-level handler is defined by module
     # `core/zone`, and is responsible for the actual update; it triggers the `events.zone.willUpdate` and
     # `events.zone.didUpdate` events just before and just after changing the element's content.
-    update: "t5:zone-update"
+    update: "t5:zone:update"
 
     # Triggered (by the standard handler) just before the content in a Zone will be updated.
-    willUpdate: "t5:zone-will-update"
+    willUpdate: "t5:zone:will-update"
 
     # Triggered (by the standard hanndler) just after the content in a Zone has updated. If the zone was not visible, it
     # is made visible after its content is changed, and before this event is triggered.
-    didUpdate: "t5:zone-did-update"
\ No newline at end of file
+    didUpdate: "t5:zone:did-update"
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b1c01eb8/tapestry-core/src/main/coffeescript/META-INF/modules/core/forms.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/forms.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/forms.coffee
new file mode 100644
index 0000000..452a4b0
--- /dev/null
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/forms.coffee
@@ -0,0 +1,100 @@
+# Copyright 2012 The Apache Software Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ## core/forms
+#
+# Defines handlers for HTML forms and HTML field elements, specifically to control input validation.
+
+define ["core/events", "core/spi", "core/builder", "core/compat/tapestry"],
+  (events, spi, builder) ->
+
+    SKIP_VALIDATION = "data-t5-skip-validation"
+
+    isPreventSubmission = (element) ->
+      (element.hasClass Tapestry.PREVENT_SUBMISSION) or
+      (element.getAttribute "data-t5-prevent-submission")
+
+    clearSubmittingHidden = (form) ->
+      hidden = form.find "[name=t:submit]"
+
+      hidden.setValue null if hidden
+
+      return
+
+    setSubmittingHidden = (form, wrapper) ->
+      hidden = form.find "[name=t:submit]"
+
+      unless hidden
+        firstHidden = form.find "input[type=hidden]"
+        hidden = builder "input", type:"hidden", name:"t:submit"
+        firstHidden.insertBefore hidden
+
+      # TODO: Research why we need id and name and get rid of one if possible.
+      value = Object.toJSON [ wrapper.element.id, wrapper.element.name ]
+
+      hidden.setValue value
+
+    defaultValidateAndSubmit = (event) ->
+
+      if ((this.getAttribute "data-t5-validate") is "submit") and
+         (not this.getAttribute SKIP_VALIDATION)
+
+        this.removeAttribute SKIP_VALIDATION
+
+        memo = error: false
+
+        for field in this.findAll "[data-t5-validation]"
+           field.trigger events.field.validate, memo
+
+        # Only do form validation if all individual field validation
+        # was successful.
+        this.trigger events.form.validateForm, memo unless memo.error
+
+        if memo.error
+          clearSubmittingHidden this
+          event.stop()
+          return
+
+      # Allow certaint types of elements to do last-moment set up. Basically, this is for
+      # FormFragment, or similar, to make their t:hidden field enabled or disabled to match
+      # their UI's visible/hidden status. This is assumed to work.
+      this.trigger events.form.prepareForSubmit
+
+      # Sometimes we want to submit the form normally, for a full-page render.
+      # Othertimes we want to stop here and let the `events.form.processSubmit`
+      # handler take it from here.
+      if isPreventSubmission this
+        event.stop()
+        this.trigger events.form.processSubmit
+
+      # Otherwise, the event is good, there are no validation problems, let the normal processing commence.
+      return
+
+    spi.domReady ->
+      # TODO: May want to define a data attribute to control whether Tapestry gets
+      # involved at all?
+      spi.body().on "submit", "form", defaultValidateAndSubmit
+
+      # On any click on a submit or image, update the containing form to indicate that the element
+      # was responsible for the eventual submit; this is very important to Ajax updates, otherwise the
+      # information about which control triggered the submit gets lost.
+      spi.body().on "click", "input[type=submit], input[type=image]", (event) ->
+        setSubmittingHidden (spi.wrap this.element.form), this
+
+    exports =
+      setSubmittingElement: (form, element) ->
+        setSubmittingHidden form, element
+
+      skipValidation: (formWrapper) ->
+        form.wrapper setAttribute SKIP_VALIDATION, true
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b1c01eb8/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
index e85723d..9a52bad 100644
--- a/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
@@ -20,9 +20,10 @@
 # Prototype ... but does it in a way that makes it relatively easy to swap in jQuery instead.
 define ["_", "prototype"], (_) ->
 
+  domLoaded = false
   # When the document has loaded, convert `domReady` to just execute the callback immediately.
   $(document).observe "dom:loaded", ->
-    exports.domReady = (callback) -> callback()
+    domLoaded = true
 
   # _internal_: splits the string into words separated by whitespace
   split = (str) ->
@@ -198,6 +199,15 @@ define ["_", "prototype"], (_) ->
       @element.writeAttribute name, value
       this
 
+    # Removes the named attribute, if present.
+    #
+    # Returns this ElementWrapper
+    removeAttribute: (name) ->
+
+      @element.writeAttribute name, null
+      this
+
+
     # Returns true if the element has the indicated class name, false otherwise.
     hasClass: (name) ->
       @element.hasClassName name
@@ -234,6 +244,14 @@ define ["_", "prototype"], (_) ->
       @element.insert top: (convertContent content)
       this
 
+    # Inserts new content (Element, ElementWrapper, or HTML markup string) into the DOM immediately before
+    # this ElementWrapper's element.
+    #
+    # Returns this ElementWrapper
+    insertBefore: (content) ->
+      @element.insert before: (convertContent content)
+      this
+
     # Runs an animation to fade-in the element over the specified duration. The element may be hidden (via `hide()`)
     # initially, and will be made visible (with initial opacity 0, which will increase over time) when the animation
     # starts.
@@ -319,6 +337,21 @@ define ["_", "prototype"], (_) ->
 
       this
 
+    # Returns the current value of the element (which must be a form control element, such as `<input>` or
+    # `<textarea>`).
+    # TODO: Define behavior for multi-named elements, such as `<select>`.
+
+    getValue: ->
+      @element.getValue()
+
+    # Updates the value for the element (whichmust be a form control element).
+    #
+    # Returns this ElementWrapper
+    setValue: (newValue) ->
+      @element.setValue newValue
+
+      this
+
     # Adds an event handler for one or more events.
     #
     # events - one or more event names, separated by spaces
@@ -410,13 +443,18 @@ define ["_", "prototype"], (_) ->
 
     # Invokes the callback only once the DOM has finished loading all elements (other resources, such as images, may
     # still be in-transit). This is a safe time to search the DOM, modify attributes, and attach event handlers.
-    # Returns this modules exports, for chained calls.
+    # Returns this modules exports, for chained calls. If the DOM has already loaded, the callback is invoked
+    # immediately.
     domReady: (callback) ->
-      $(document).observe "dom:loaded", callback
+      if domLoaded
+        callback()
+      else
+        $(document).observe "dom:loaded", callback
 
       exports
 
     # on() is used to add an event handler
+    #
     # * selector - CSS selector used to select elements to attach handler to; alternately,
     #   a single DOM element, or an array of DOM elements
     # * events - one or more event names, separated by spaces
@@ -424,6 +462,7 @@ define ["_", "prototype"], (_) ->
     # * up to a selected element from an originating element that matches the CSS expression
     #   will invoke the handler.
     # * handler - function invoked; the function is passed an Event object.
+    #
     # Returns an EventHandler object, making it possible to turn event notifications on or off.
     on: (selector, events, match, handler) ->
       unless handler?

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b1c01eb8/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
index 3d179dc..d8e0f4d 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Form.java
@@ -34,11 +34,9 @@ import org.apache.tapestry5.ioc.services.PropertyAccess;
 import org.apache.tapestry5.ioc.util.ExceptionUtils;
 import org.apache.tapestry5.ioc.util.IdAllocator;
 import org.apache.tapestry5.json.JSONArray;
-import org.apache.tapestry5.json.JSONObject;
 import org.apache.tapestry5.runtime.Component;
 import org.apache.tapestry5.services.*;
 import org.apache.tapestry5.services.compatibility.DeprecationWarning;
-import org.apache.tapestry5.services.javascript.InitializationPriority;
 import org.apache.tapestry5.services.javascript.JavaScriptSupport;
 import org.slf4j.Logger;
 
@@ -204,9 +202,6 @@ public class Form implements ClientElement, FormValidationControl
     @Environmental
     private JavaScriptSupport javascriptSupport;
 
-    @Environmental
-    private JavaScriptSupport jsSupport;
-
     @Inject
     private Request request;
 
@@ -329,10 +324,10 @@ public class Form implements ClientElement, FormValidationControl
 
         formSupport = createRenderTimeFormSupport(clientId, actionSink, allocator);
 
-        addJavaScriptInitialization();
-
         if (zone != null)
+        {
             linkFormToZone(link);
+        }
 
         environment.push(FormSupport.class, formSupport);
         environment.push(ValidationTracker.class, tracker);
@@ -340,7 +335,7 @@ public class Form implements ClientElement, FormValidationControl
         if (autofocus)
         {
             ValidationDecorator autofocusDecorator = new AutofocusValidationDecorator(
-                    environment.peek(ValidationDecorator.class), tracker, jsSupport);
+                    environment.peek(ValidationDecorator.class), tracker, javascriptSupport);
             environment.push(ValidationDecorator.class, autofocusDecorator);
         }
 
@@ -365,6 +360,11 @@ public class Form implements ClientElement, FormValidationControl
             writer.attributes("onsubmit", MarkupConstants.WAIT_FOR_PAGE);
         }
 
+        if (clientValidation != ClientValidation.NONE)
+        {
+            writer.attributes("data-t5-validate", "submit");
+        }
+
         resources.renderInformalParameters(writer);
 
         div = writer.element("div", "class", CSSClassConstants.INVISIBLE);
@@ -382,16 +382,6 @@ public class Form implements ClientElement, FormValidationControl
         environment.peek(Heartbeat.class).begin();
     }
 
-    private void addJavaScriptInitialization()
-    {
-        JSONObject validateSpec = new JSONObject()
-                .put("submit", clientValidation != ClientValidation.NONE);
-
-        JSONObject spec = new JSONObject("formId", clientId).put("validate", validateSpec);
-
-        javascriptSupport.addInitializerCall(InitializationPriority.EARLY, "formEventManager", spec);
-    }
-
     @HeartbeatDeferred
     private void linkFormToZone(Link link)
     {

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b1c01eb8/tapestry-core/src/main/java/org/apache/tapestry5/internal/DefaultValidationDecorator.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/DefaultValidationDecorator.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/DefaultValidationDecorator.java
index 6262334..5e2ce23 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/DefaultValidationDecorator.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/DefaultValidationDecorator.java
@@ -26,21 +26,16 @@ public final class DefaultValidationDecorator extends BaseValidationDecorator
 {
     private final Environment environment;
 
-    private final Asset spacerAsset;
-
     private final MarkupWriter markupWriter;
 
     /**
      * @param environment
-     *            used to locate objects and services during the render
-     * @param spacerAsset
-     *            asset for a one-pixel spacer image used as a placeholder for the error marker icon
+     *         used to locate objects and services during the render
      * @param markupWriter
      */
-    public DefaultValidationDecorator(Environment environment, Asset spacerAsset, MarkupWriter markupWriter)
+    public DefaultValidationDecorator(Environment environment, MarkupWriter markupWriter)
     {
         this.environment = environment;
-        this.spacerAsset = spacerAsset;
         this.markupWriter = markupWriter;
     }
 
@@ -62,32 +57,14 @@ public final class DefaultValidationDecorator extends BaseValidationDecorator
     }
 
     /**
-     * Writes an icon for field after the field. The icon has the same id as the field, with ":icon" appended. This is
-     * expected by the default client-side JavaScript. The icon's src is a blank spacer image (this is to allow the
-     * image displayed to be overridden via CSS). The icon's CSS class is "t-error-icon", with "t-invisible" added
-     * if the field is not in error when rendered. If client validation is not enabled for the form containing the
-     * field and the field is not in error, then the error icon itself is not rendered.
-     * 
+     * Does nothing; prior releases would write an error icon.
+     *
      * @param field
-     *            which just completed rendering itself
+     *         which just completed rendering itself
      */
     @Override
     public void afterField(Field field)
     {
-        boolean inError = inError(field);
-
-        boolean clientValidationEnabled = getFormSupport().isClientValidationEnabled();
-
-        if (inError || clientValidationEnabled)
-        {
-            String iconId = field.getClientId() + "_icon";
-
-            String cssClass = inError ? "t-error-icon" : "t-error-icon t-invisible";
-
-            markupWriter.element("img", "src", spacerAsset.toClientURL(), "alt", "", "class", cssClass, "id", iconId);
-            markupWriter.end();
-        }
-
     }
 
     private FormSupport getFormSupport()

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b1c01eb8/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ValidationDecoratorFactoryImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ValidationDecoratorFactoryImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ValidationDecoratorFactoryImpl.java
index b7d0476..4385734 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ValidationDecoratorFactoryImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ValidationDecoratorFactoryImpl.java
@@ -14,10 +14,8 @@
 
 package org.apache.tapestry5.internal.services;
 
-import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.ValidationDecorator;
-import org.apache.tapestry5.annotations.Path;
 import org.apache.tapestry5.internal.DefaultValidationDecorator;
 import org.apache.tapestry5.services.Environment;
 import org.apache.tapestry5.services.ValidationDecoratorFactory;
@@ -26,17 +24,13 @@ public class ValidationDecoratorFactoryImpl implements ValidationDecoratorFactor
 {
     private final Environment environment;
 
-    private final Asset spacerImage;
-
-    public ValidationDecoratorFactoryImpl(Environment environment, @Path("${tapestry.spacer-image}")
-    Asset spacerImage)
+    public ValidationDecoratorFactoryImpl(Environment environment)
     {
         this.environment = environment;
-        this.spacerImage = spacerImage;
     }
 
     public ValidationDecorator newInstance(MarkupWriter writer)
     {
-        return new DefaultValidationDecorator(environment, spacerImage, writer);
+        return new DefaultValidationDecorator(environment, writer);
     }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b1c01eb8/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js b/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
index af0a91a..92ee51c 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
@@ -18,10 +18,10 @@ define("core/compat/tapestry", [
     "core/spi",
     "core/events",
     "core/ajax",
-    "core/zone",
+    "core/forms",
     "core/compat/t5-dom",
     "core/compat/t5-console",
-    "core/compat/t5-init"], function (_, spi, events, ajax) {
+    "core/compat/t5-init"], function (_, spi, events, ajax, forms) {
 
     window.Tapestry = {
 
@@ -34,19 +34,19 @@ define("core/compat/tapestry", [
          * of the Form's Tapestry object to true (which will prevent form
          * submission).
          */
-        FORM_VALIDATE_EVENT: "tapestry:formvalidate",
+        FORM_VALIDATE_EVENT: events.form.validate,
 
         /**
          * Event fired just before the form submits, to allow observers to make
          * final preparations for the submission, such as updating hidden form
          * fields. The form element is passed as the event memo.
          */
-        FORM_PREPARE_FOR_SUBMIT_EVENT: "tapestry:formprepareforsubmit",
+        FORM_PREPARE_FOR_SUBMIT_EVENT: events.form.prepareForSubmit,
 
         /**
          * Form event fired after prepare.
          */
-        FORM_PROCESS_SUBMIT_EVENT: "tapestry:formprocesssubmit",
+        FORM_PROCESS_SUBMIT_EVENT: events.form.processSubmit,
 
         /**
          * Event, fired on a field element, to cause observers to validate the
@@ -63,7 +63,7 @@ define("core/compat/tapestry", [
          * on all fields within the form (observed by each field's
          * Tapestry.FieldEventManager).
          */
-        FORM_VALIDATE_FIELDS_EVENT: "tapestry:validatefields",
+        FORM_VALIDATE_FIELDS_EVENT: events.field.validate,
 
         /**
          * Event, fired on the document object, which identifies the current focus
@@ -105,7 +105,10 @@ define("core/compat/tapestry", [
         /**
          * CSS Class added to a &lt;form&gt; element that directs Tapestry to
          * prevent normal (HTTP POST) form submission, in favor of Ajax
-         * (XmlHttpRequest) submission.
+         * (XmlHttpRequest) submission.   This is still supported in Tapestry 5.4, but
+         * replaced with the data-t5-prevent-submission attribute.
+         *
+         * @deprecated Use data-t5-prevent-submission="true" instead
          */
         PREVENT_SUBMISSION: "t-prevent-submission",
 
@@ -179,9 +182,7 @@ define("core/compat/tapestry", [
          * such to load first. This simply observes the dom:loaded event on the
          * document object (support for which is provided by Prototype).
          */
-        onDOMLoaded: function (callback) {
-            spi.domReady(callback);
-        },
+        onDOMLoaded: spi.domReady,
 
         /**
          * Find all elements marked with the "t-invisible" CSS class and hide()s
@@ -225,26 +226,6 @@ define("core/compat/tapestry", [
                     t.observingFocusChange = true;
                 }
             });
-
-            /*
-             * When a submit element is clicked, record the name of the element into
-             * the associated form. This is necessary for some Ajax processing, see
-             * TAPESTRY-2324.
-             *
-             * TAP5-1418: Added "type=image" so that they set the submitting element
-             * correctly.
-             */
-            $$("INPUT[type=submit]", "INPUT[type=image]").each(function (element) {
-                var t = $T(element);
-
-                if (!t.trackingClicks) {
-                    element.observe("click", function () {
-                        $(element.form).setSubmittingElement(element);
-                    });
-
-                    t.trackingClicks = true;
-                }
-            });
         },
 
         /*
@@ -602,26 +583,6 @@ define("core/compat/tapestry", [
             'FORM',
             {
                 /**
-                 * Gets the Tapestry.FormEventManager for the form.
-                 *
-                 * @param form
-                 *            form element
-                 */
-                getFormEventManager: function (form) {
-                    form = $(form);
-
-                    var manager = $T(form).formEventManager;
-
-                    if (manager == undefined) {
-
-                        throw "No Tapestry.FormEventManager object has been created for form '#{id}'."
-                                .interpolate(form);
-                    }
-
-                    return manager;
-                },
-
-                /**
                  * Identifies in the form what is the cause of the
                  * submission. The element's id is stored into the t:submit
                  * hidden field (created as needed).
@@ -633,8 +594,7 @@ define("core/compat/tapestry", [
                  *            (a Submit or LinkSubmit)
                  */
                 setSubmittingElement: function (form, element) {
-                    form.getFormEventManager()
-                            .setSubmittingElement(element);
+                    forms.setSubmittingControl(spi.wrap(form), spi.wrap(element));
                 },
 
                 /**
@@ -642,7 +602,7 @@ define("core/compat/tapestry", [
                  * the form.
                  */
                 skipValidation: function (form) {
-                    $T(form).skipValidation = true;
+                    forms.skipValidation(spi.wrap(form));
                 },
 
                 /**
@@ -768,6 +728,7 @@ define("core/compat/tapestry", [
          *            function to be passed the field value
          */
         addValidator: function (element, validator) {
+
             element.observe(Tapestry.FIELD_VALIDATE_EVENT, function (event) {
                 try {
                     validator.call(this, event.memo.translated);
@@ -989,20 +950,6 @@ define("core/compat/tapestry", [
         },
 
         /**
-         * Sets up a Tapestry.FormEventManager for the form, and enables
-         * events for validations. This is executed with
-         * InitializationPriority.EARLY, to ensure that the FormEventManager
-         * exists vefore any validations are added for fields within the
-         * Form.
-         *
-         * @since 5.2.2
-         */
-        formEventManager: function (spec) {
-            $T(spec.formId).formEventManager = new Tapestry.FormEventManager(
-                    spec);
-        },
-
-        /**
          * Keys in the masterSpec are ids of field control elements. Value
          * is a list of validation specs. Each validation spec is a 2 or 3
          * element array.
@@ -1073,7 +1020,7 @@ define("core/compat/tapestry", [
              */
             $(clientId).observeAction("click", function (event) {
                 $(this.form).skipValidation();
-                $(this.form).setSubmittingElement(clientId);
+                $(this.form).setSubmittingElement($(clientId));
                 $(this.form).performSubmit(event);
             });
         }
@@ -1091,7 +1038,7 @@ define("core/compat/tapestry", [
 
         required: function (field, message) {
             $(field).getFieldEventManager().requiredCheck = function (value) {
-                if ((T5._.isString(value) && value.strip() == '')
+                if ((_.isString(value) && value.strip() == '')
                         || value == null)
                     $(field).showValidationMessage(message);
             };
@@ -1265,7 +1212,6 @@ define("core/compat/tapestry", [
                 return;
 
             if (Prototype.Browser.IE) {
-                var _ = T5._;
 
                 this.outerDiv.show();
 
@@ -1311,7 +1257,7 @@ define("core/compat/tapestry", [
 
                 var div = this.outerDiv;
 
-                T5._.delay(function () {
+                _.delay(function () {
                     div.hide();
                 }, this.IE_FADE_TIME);
 
@@ -1335,138 +1281,31 @@ define("core/compat/tapestry", [
         }
     });
 
-    Tapestry.FormEventManager = Class.create({
-
-        initialize: function (spec) {
-            this.form = $(spec.formId);
-            this.validateOnSubmit = spec.validate.submit;
-
-            this.form.onsubmit = this.handleSubmit.bindAsEventListener(this);
-        },
-
-        /**
-         * Identifies in the form what is the cause of the submission. The element's
-         * id is stored into the t:submit hidden field (created as needed).
-         *
-         * @param element
-         *            id or element that is the cause of the submit (a Submit or
-         *            LinkSubmit)
-         */
-        setSubmittingElement: function (element) {
-
-            if (!this.submitHidden) {
-                // skip if this is not a tapestry controlled form
-                if (this.form.getInputs("hidden", "t:formdata").size() == 0)
-                    return;
-
-                var hiddens = this.form.getInputs("hidden", "t:submit");
-
-                if (hiddens.size() == 0) {
-
-                    /**
-                     * Create a new hidden field directly after the first hidden
-                     * field in the form.
-                     */
-                    var firstHidden = this.form.getInputs("hidden").first();
-
-                    this.submitHidden = new Element("input", {
-                        type: "hidden",
-                        name: "t:submit"
-                    });
-
-                    firstHidden.insert({
-                        after: this.submitHidden
-                    });
-                } else
-                    this.submitHidden = hiddens.first();
-            }
-
-            this.submitHidden.value = element == null ? null : Object.toJSON([$(element).id, $(element).name]);
-        },
-
-        handleSubmit: function (domevent) {
-
-            /*
-             * Necessary because we set the onsubmit property of the form, rather
-             * than observing the event. But that's because we want to specfically
-             * overwrite any other handlers.
-             */
-            Event.extend(domevent);
-
-            var t = $T(this.form);
-
-            t.validationError = false;
-
-            if (!t.skipValidation) {
-
-                t.skipValidation = false;
-
-                /* Let all the fields do their validations first. */
-
-                this.form.fire(Tapestry.FORM_VALIDATE_FIELDS_EVENT, this.form);
-
-                /*
-                 * Allow observers to validate the form as a whole. The FormEvent
-                 * will be visible as event.memo. The Form will not be submitted if
-                 * event.result is set to false (it defaults to true). Still trying
-                 * to figure out what should get focus from this kind of event.
-                 */
-                if (!t.validationError)
-                    this.form.fire(Tapestry.FORM_VALIDATE_EVENT, this.form);
-
-                if (t.validationError) {
-                    domevent.stop();
-
-                    /*
-                     * Because the submission failed, the last submit element is
-                     * cleared, since the form may be submitted for some other
-                     * reason later.
-                     */
-                    this.setSubmittingElement(null);
-
-                    return false;
-                }
-            }
-
-            this.form.fire(Tapestry.FORM_PREPARE_FOR_SUBMIT_EVENT, this.form);
-
-            /*
-             * This flag can be set to prevent the form from submitting normally.
-             * This is used for some Ajax cases where the form submission must run
-             * via Ajax.Request.
-             */
-
-            if (this.form.hasClassName(Tapestry.PREVENT_SUBMISSION)) {
-                domevent.stop();
+    Tapestry.FieldEventManager = Class.create({
 
-                /*
-                 * Instead fire the event (a listener will then trigger the Ajax
-                 * submission). This is really a hook for the ZoneManager.
-                 */
-                this.form.fire(Tapestry.FORM_PROCESS_SUBMIT_EVENT);
+        initialize: function (field) {
 
-                return false;
-            }
+            this.field = $(field);
 
-            /* Validation is OK, not doing Ajax, continue as planned. */
+            this.translator = Prototype.K;
 
-            return true;
-        }
-    });
+            // This marker clues in the Form that validation should be triggered on this
+            // element.
+            this.field.writeAttribute("data-t5-validation", true);
 
-    Tapestry.FieldEventManager = Class.create({
+            var _this = this;
 
-        initialize: function (field) {
-            this.field = $(field);
+            $(this.field).observe(Tapestry.FORM_VALIDATE_FIELDS_EVENT,
+                    function (event) {
 
-            this.translator = Prototype.K;
+                        _this.validateInput();
 
-            var fem = $(this.field.form).getFormEventManager();
+                        if (_this.inError()) {
+                            event.memo.error = true;
 
-            if (fem.validateOnSubmit) {
-                $(this.field.form).observe(Tapestry.FORM_VALIDATE_FIELDS_EVENT,
-                        this.validateInput.bindAsEventListener(this));
-            }
+                        }
+                    }
+            );
         },
 
         getLabel: function () {
@@ -1479,11 +1318,7 @@ define("core/compat/tapestry", [
         },
 
         getIcon: function () {
-            if (!this.icon) {
-                this.icon = $(this.field.id + "_icon");
-            }
-
-            return this.icon;
+            return null;
         },
 
         /**
@@ -1495,8 +1330,6 @@ define("core/compat/tapestry", [
 
             this.getLabel() && this.getLabel().removeClassName("t-error");
 
-            this.getIcon() && this.getIcon().hide();
-
             if (this.errorPopup)
                 this.errorPopup.hide();
         },
@@ -1510,75 +1343,63 @@ define("core/compat/tapestry", [
          *            validation message to display
          */
         showValidationMessage: function (message) {
-            $T(this.field).validationError = true;
-            $T(this.field.form).validationError = true;
 
             this.field.addClassName("t-error");
 
             this.getLabel() && this.getLabel().addClassName("t-error");
 
-            var icon = this.getIcon();
-
-            if (icon && !icon.visible()) {
-                new Effect.Appear(this.icon);
-            }
-
             if (this.errorPopup == undefined)
                 this.errorPopup = new Tapestry.ErrorPopup(this.field);
 
             this.errorPopup.showMessage(message);
         },
 
+        inError: function () {
+            return this.field.hasClassName("t-error");
+        },
+
         /**
-         * Invoked when a form is submitted, or when leaving a field, to perform
-         * field validations. Field validations are skipped for disabled fields. If
-         * all validations are succesful, any decorations are removed. If any
-         * validation fails, an error popup is raised for the field, to display the
+         * Invoked when a form is submitted to perform
+         * field validations. Field validations are skipped for disabled fields or fields that are not visible.
+         * If any validation fails, an error popup is raised for the field, to display the
          * validation error message.
          *
-         * @return true if the field has a validation error
          */
         validateInput: function () {
+            this.removeDecorations();
+
             if (this.field.disabled)
-                return false;
+                return;
 
             if (!this.field.isDeepVisible())
-                return false;
-
-            var t = $T(this.field);
+                return;
 
             var value = $F(this.field);
 
-            t.validationError = false;
-
-            if (this.requiredCheck)
+            if (this.requiredCheck) {
                 this.requiredCheck.call(this, value);
 
+                if (this.inError()) { return; }
+            }
+
             /*
              * Don't try to validate blank values; if the field is required, that
              * error is already noted and presented to the user.
              */
-
-            if (!t.validationError && !(T5._.isString(value) && value.blank())) {
+            if (!(_.isString(value) && value.blank())) {
                 var translated = this.translator(value);
 
                 /*
                  * If Format went ok, perhaps do the other validations.
                  */
-                if (!t.validationError) {
+                if (!this.inError()) {
                     this.field.fire(Tapestry.FIELD_VALIDATE_EVENT, {
                         value: value,
-                        translated: translated
+                        translated: translated,
                     });
                 }
-            }
 
-            /* Lastly, if no validation errors were found, remove the decorations. */
-
-            if (!t.validationError)
-                this.field.removeDecorations();
-
-            return t.validationError;
+            }
         }
     });
 

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b1c01eb8/tapestry-core/src/test/java/org/apache/tapestry5/internal/DefaultValidationDecoratorTest.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/DefaultValidationDecoratorTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/DefaultValidationDecoratorTest.java
index aa10245..c3cf7cf 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/DefaultValidationDecoratorTest.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/DefaultValidationDecoratorTest.java
@@ -34,7 +34,7 @@ public class DefaultValidationDecoratorTest extends TapestryTestCase
 
         replay();
 
-        ValidationDecorator decorator = new DefaultValidationDecorator(env, null, null);
+        ValidationDecorator decorator = new DefaultValidationDecorator(env, null);
 
         decorator.insideLabel(null, null);
 
@@ -56,7 +56,7 @@ public class DefaultValidationDecoratorTest extends TapestryTestCase
 
         Element e = writer.element("label", "accesskey", "f");
 
-        ValidationDecorator decorator = new DefaultValidationDecorator(env, null, null);
+        ValidationDecorator decorator = new DefaultValidationDecorator(env, null);
 
         decorator.insideLabel(field, e);
 
@@ -82,7 +82,7 @@ public class DefaultValidationDecoratorTest extends TapestryTestCase
 
         Element e = writer.element("label", "accesskey", "f", "class", "foo");
 
-        ValidationDecorator decorator = new DefaultValidationDecorator(env, null, null);
+        ValidationDecorator decorator = new DefaultValidationDecorator(env, null);
 
         decorator.insideLabel(field, e);
 
@@ -107,7 +107,7 @@ public class DefaultValidationDecoratorTest extends TapestryTestCase
 
         writer.element("input", "type", "text", "name", "ex", "class", "foo", "value", "freddy", "size", "30");
 
-        ValidationDecorator decorator = new DefaultValidationDecorator(env, null, writer);
+        ValidationDecorator decorator = new DefaultValidationDecorator(env, writer);
 
         decorator.insideField(field);
 
@@ -129,7 +129,7 @@ public class DefaultValidationDecoratorTest extends TapestryTestCase
 
         replay();
 
-        ValidationDecorator decorator = new DefaultValidationDecorator(env, null, null);
+        ValidationDecorator decorator = new DefaultValidationDecorator(env, null);
 
         decorator.insideField(field);
 
@@ -148,7 +148,7 @@ public class DefaultValidationDecoratorTest extends TapestryTestCase
 
         replay();
 
-        ValidationDecorator decorator = new DefaultValidationDecorator(env, null, null);
+        ValidationDecorator decorator = new DefaultValidationDecorator(env, null);
 
         decorator.insideLabel(field, null);