Child pages
  • AJAX Validation
Skip to end of metadata
Go to start of metadata

Description

Struts provides client side validation(using JavaScript) for a few validators. Using AJAX validation, all validators available to the application on the server side can be used without forcing the page to reload, just to show validation errors. AJAX validation has a server side, which is in included in JSON Plugin (an interceptor and a result). Client side must be handled by applictions themself. One reason for that is there are too many JavaScript frameworks and libraries. Struts has no preference which of them you use. Previous versions of Struts included a client side which was relying on the Dojo JS framework and was located in Struts Dojo plugin. That has been deprecated for a long time and was eventually removed.

Example

This example is taken from the Struts showcase application.

Create the action class

java]+", message="regexValidatorField must match a regexp (.*\\.txt) if specified") public void setRegexValidatorField(String regexValidatorField) { this.regexValidatorField = regexValidatorField; } public String getRequiredStringValidatorField() { return requiredStringValidatorField; } @RequiredStringValidator(trim=true, message="required and must be string") public void setRequiredStringValidatorField(String requiredStringValidatorField) { this.requiredStringValidatorField = requiredStringValidatorField; } public String getRequiredValidatorField() { return requiredValidatorField; } @RequiredFieldValidator(message="required") public void setRequiredValidatorField(String requiredValidatorField) { this.requiredValidatorField = requiredValidatorField; } public String getStringLengthValidatorField() { return stringLengthValidatorField; } @StringLengthFieldValidator( minLength="2", maxLength="4", trim=true, message="must be a String of a specific greater than 1 less than 5 if specified") public void setStringLengthValidatorField(String stringLengthValidatorField) { this.stringLengthValidatorField = stringLengthValidatorField; } public String getFieldExpressionValidatorField() { return fieldExpressionValidatorField; } @FieldExpressionValidator( expression = "(fieldExpressionValidatorField == requiredValidatorField)", message = "must be the same as the Required Validator Field if specified") public void setFieldExpressionValidatorField( String fieldExpressionValidatorField) { this.fieldExpressionValidatorField = fieldExpressionValidatorField; } public String getUrlValidatorField() { return urlValidatorField; } @UrlValidator(message="must be a valid url if supplied") public void setUrlValidatorField(String urlValidatorField) { this.urlValidatorField = urlValidatorField; } }]]>

 

Map the Action

Note that is is not necessary when using Convention Plugin.

XML /WEB-INF/validation/ajaxFormSubmit.jsp ajaxFormSubmitSuccess ]]>

AJAX validation is performed by the jsonValidation interceptor. This interceptor is included in the jsonValidationWorkflowStack, and is required in order to perform AJAX validation. Normal results(input, success, etc) should be provided for the action in the case that someone tries to access the action directly, in which case normal validation will be triggered. So, how does the jsonValidation know that it must perform AJAX validation vs regular validation? We will see that in a minute, but you don't need to know that in order to use AJAX validation. Same applies for specialized Redirect Result Type jsonActionRedirect.

Create the JSP

HTML Struts2 Showcase - Validation - AJAX Form Submit

Action Errors Will Appear Here


 ]]>

Things to note on this JSP:

  • The form tag does not have validate set to true, which would perform client validation before the AJAX validation.
  • It uses a customized theme ajaxErrorContainers. The default Struts themes generate HTML-Elements to show validation errors only if errors are present when page is created on server side. But in order to show validation errors that arrive later via AJAX it is necessary to have error-container elements in DOM always.

What happens if validation succeeds? That depends on your request parameters and action configuration. If you are using jsonActionRedirect result mentioned above the action will be executed while AJAX request is active and respond with JSON providing a new URL to load. Otherwise the AJAX response will be empty and the form must be submitted a 2nd time but as usual request, not AJAX.

Client Validation

Setting validate to true in the form tag will enable client side, JavaScript validation, which can be used along with AJAX validation (runs before the AJAX validation).

Custom Theme

In this sample the custom theme is based on xhtml theme. It is required to override 3 FTL files.

theme.properties
text

 

actionerror.ftl
xml <#if parameters.id??> id="${parameters.id?html}"<#rt/> <#if parameters.cssClass??> class="${parameters.cssClass?html}"<#rt/> <#else> class="errorMessage"<#rt/> <#if parameters.cssStyle??> style="${parameters.cssStyle?html}"<#rt/> > <#if (actionErrors?? && actionErrors?size > 0)> <#list actionErrors as error> <#if error??>
  • <#if parameters.escape>${error!?html}<#else>${error!}<#rt/>
  • <#rt/> ]]>

     

    controlfooter.ftl
    xml <#lt/> <#if (parameters.errorposition!"top") == 'bottom'> <#assign hasFieldErrors = parameters.name?? && fieldErrors?? && fieldErrors[parameters.name]??/> <#if hasFieldErrors> <#rt/> <#if hasFieldErrors> <#list fieldErrors[parameters.name] as error>
    ${error?html}
    <#t/> <#lt/> ]]>

     

    controlheader-core.ftl
    xml <#assign hasFieldErrors = parameters.name?? && fieldErrors?? && fieldErrors[parameters.name]??/> <#if (parameters.errorposition!"top") == 'top'> <#rt/> <#if hasFieldErrors> <#list fieldErrors[parameters.name] as error>
    ${error?html}
    <#t/> <#lt/> <#if !parameters.labelposition?? && (parameters.form.labelposition)??> <#assign labelpos = parameters.form.labelposition/> <#elseif parameters.labelposition??> <#assign labelpos = parameters.labelposition/> <#-- if the label position is top, then give the label it's own row in the table --> <#if (labelpos!"") == 'top'> <#rt/> <#else> <#rt/> <#if parameters.label??> <#t/> <#lt/> <#-- add the extra row --> <#if (labelpos!"") == 'top'> ]]>

    CSS

    To show users some nice visual feedback while waiting for AJAX response you can use a little CSS. Remember to include the referenced indicator.gif.

    css

     

    JavaScript

    Now this is where the magic happens. Here jQuery is used to register an eventhandler which intercepts form submits. It takes care of hiding validation errors that might be present, submit the form via AJAX and handle JSON responses.

    js 0) { originalButton.hide(); var feedbackElement = $('
    ').insertAfter(originalButton); var restoreFunction = function() { originalButton.show(); feedbackElement.remove(); } } var options = { data: 'struts.enableJSONValidation=true&struts.validateOnly=false&' + _formData, async: true, processData: false, type: 'POST', success: function (response, statusText, xhr) { if (response.location) { // no validation errors // action has been executed and sent a redirect URL wrapped as JSON // cannot use a normal http-redirect (status-code 3xx) as this would be followed by browsers and would not be available here // follow JSON-redirect window.location.href = response.location; } else { if (restoreFunction) { restoreFunction(); } _handleValidationResult(_form, response); } }, error: function(xhr, textStatus, errorThrown) { if (restoreFunction) { restoreFunction(); } // struts sends status code 400 when validation errors are present if (xhr.status === 400) { _handleValidationResult(_form, JSON.parse(xhr.responseText)) } else { // a real error occurred -> show user an error message _handleValidationResult(_form, {errors: ['Network or server error!']}) } } } // send request, after delay to make sure everybody notices the visual feedback :) window.setTimeout(function() { var url = _form[0].action; jQuery.ajax(url, options); }, 1000); } /** * Removes validation errors from HTML DOM. */ function _removeValidationErrors() { // action errors // you might want to use a custom ID here $('ul.errorMessage li').remove(); // field errors $('div.errorMessage').remove(); } /** * Incorporates validation errors in HTML DOM. * * @param form Form containing errors. * @param errors Errors from server. */ function _handleValidationResult(form, errors) { // action errors if (errors.errors) { // you might want to use a custom ID here var errorContainer = $('ul.errorMessage'); $.each(errors.errors, function(index, errorMsg) { var li = $('
  • '); li.text(errorMsg); // use text() for security reasons errorContainer.append(li); }); } // field errors if (errors.fieldErrors) { $.each(errors.fieldErrors, function(fieldName, errorMsg) { var td = $('td[data-error-for-fieldname="' + fieldName + '"]'); if (td) { var div = $('
    '); div.text(errorMsg); // use text() for security reasons td.append(div); } }); } } // register onSubmit handler $(window).bind('load', function() { $('form').bind('submit', ajaxFormValidation); });]]>

     

    How it works

    jsonValidation interceptor must be placed on a stack, following the validation interceptor. The interceptor itself won't perform any validation, but will check for validation errors on the action being invoked (assuming that the action is ValidationAware).

    If you just want to use AJAX validation, without knowing the implementation details, you can skip this section.

    When the jsonValidation interceptor is invoked, it will look for a parameter named struts.enableJSONValidation, this parameter must be set to true, otherwise the interceptor won't do anything. Then the interceptor will look for a parameter named struts.validateOnly, if this parameter exists, is set to true, and there are validation errors (o action errors) they will be serialized into JSON in the form:

    JavaScript

    If the action implements the ModelDrive interface, "model." will be stripped from the field names in the returned JSON. If validation succeeds (and struts.validateOnly is true), an empty JSON string will be returned:

    JavaScript

    If struts.validateOnly is false the action and result are executed. In this case jsonActionRedirect result is very useful. It creates a JSON response in the form:

    JavaScript"} ]]>

    Remember to set struts.enableJSONValidation=true in the request to enable AJAX validation

    JSONValidationInterceptor parameters

    The following request parameters can be used to enable exposing validation errors:

    • struts.enableJSONValidation - a request parameter must be set to true to use this interceptor
    • struts.validateOnly - If the request has this parameter, execution will return after validation (action won't be executed). If struts.validateOnly is set to false you may want to use JSONActionRedirectResult
    • struts.JSONValidation.no.encoding - If the request has this parameter set to true, the character encoding will NOT be set on the response - is needed in portlet environment

    You can override names of these parameters by specifying the following parameters when setting up a stack:

    • validateJsonParam - to override name of struts.enableJSONValidation
    • validateOnlyParam - to override name of struts.validateOnly
    • noEncodingSetParam - to override name of struts.JSONValidation.no.encoding
    • validationFailedStatus - status to be set on response when there are validation errors, by default 400


     Parameters overriding is available since Struts 2.5.9

    Flow chart of AJAX validation

    Client Validation

    Some details are omitted, like results used.

    Client Validation

    As explained above: there is a case where form is submitted twice, one time as AJAX with validation only and another time as usual submit.

     

    1 Comment

    1. For applications using maven to manage artifacts, you'll need to add to pom.xml a dependency node for the struts2-dojo-plugin jar file:
      <dependency>
      <groupId>org.apache.struts</groupId>
      <artifactId>struts2-dojo-plugin</artifactId>
      <version>X.X.X.X</version>
      </dependency>