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 <[email protected]> Authored: Mon Aug 13 18:32:39 2012 -0700 Committer: Howard M. Lewis Ship <[email protected]> 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 <form> 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);
