You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2019/05/15 08:21:38 UTC

[struts-site] branch json-validation created (now fbbef5a)

This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a change to branch json-validation
in repository https://gitbox.apache.org/repos/asf/struts-site.git.


      at fbbef5a  Adds proper back link

This branch includes the following new commits:

     new e20f5dd  Adds missing JSON validation page
     new fbbef5a  Adds proper back link

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[struts-site] 02/02: Adds proper back link

Posted by lu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch json-validation
in repository https://gitbox.apache.org/repos/asf/struts-site.git

commit fbbef5a5cb759de0cbdb710e00f96226b5912b57
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Wed May 15 10:21:18 2019 +0200

    Adds proper back link
---
 source/plugins/json/json-validation.md | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/source/plugins/json/json-validation.md b/source/plugins/json/json-validation.md
index 0ab0ca4..aecfa86 100644
--- a/source/plugins/json/json-validation.md
+++ b/source/plugins/json/json-validation.md
@@ -1,6 +1,9 @@
 ---
 layout: plugin
-title: JSON plugin
+title: JSON Ajax validation
+parent:
+    title: JSON plugin
+    url: index.html
 ---
 
 # JSON Ajax Validation


[struts-site] 01/02: Adds missing JSON validation page

Posted by lu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch json-validation
in repository https://gitbox.apache.org/repos/asf/struts-site.git

commit e20f5ddd4cdc1a54acbb057d31eaff51c7eb7afe
Author: Lukasz Lenart <lu...@apache.org>
AuthorDate: Wed May 15 10:15:58 2019 +0200

    Adds missing JSON validation page
---
 pom.xml                                        |   2 +-
 source/plugins/json/index.md                   |   2 +-
 source/plugins/json/json-validation.md         | 507 +++++++++++++++++++++++++
 source/plugins/json/struts2-ajax-vali-flow.png | Bin 0 -> 54364 bytes
 4 files changed, 509 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 1984c3a..caf6162 100644
--- a/pom.xml
+++ b/pom.xml
@@ -101,7 +101,7 @@
                                         <argument>https://cwiki.apache.org/confluence</argument>
                                         <argument>+gfm</argument>
                                         <argument>true</argument>
-                                        <argument>13859</argument>
+                                        <argument>13850</argument>
                                     </arguments>
                                 </configuration>
                             </execution>
diff --git a/source/plugins/json/index.md b/source/plugins/json/index.md
index 43e016d..14d158d 100644
--- a/source/plugins/json/index.md
+++ b/source/plugins/json/index.md
@@ -46,7 +46,7 @@ The `setArray` method can take as parameter either a `List`, or any numeric arra
 
 `root` attribute must be set on the `JSONInterceptor` when dealing with JSON array.
 
-This plugin also provides _AJAX Validation_.
+This plugin also provides [JSON Validation](json-validation).
 
 ## Installation
 
diff --git a/source/plugins/json/json-validation.md b/source/plugins/json/json-validation.md
new file mode 100644
index 0000000..0ab0ca4
--- /dev/null
+++ b/source/plugins/json/json-validation.md
@@ -0,0 +1,507 @@
+---
+layout: plugin
+title: JSON plugin
+---
+
+# JSON Ajax Validation
+{:.no_toc}
+
+* Will be replaced with the ToC, excluding a header
+{:toc}
+
+## Description
+
+Struts provides [client side validation](../../core-developers/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](index) (an interceptor and a result). Client 
+side must be handled by applications 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
+public class AjaxFormSubmitAction extends ActionSupport {
+    private String requiredValidatorField = null;
+    private String requiredStringValidatorField = null;
+    private Integer integerValidatorField = null;
+    private Date dateValidatorField = null;
+    private String emailValidatorField = null;
+    private String urlValidatorField = null;
+    private String stringLengthValidatorField = null;
+    private String regexValidatorField = null;
+    private String fieldExpressionValidatorField = null;
+    @Override
+    public void validate() {
+        if (hasFieldErrors()) {
+            addActionError("Errors present!");
+        }
+    }
+    public Date getDateValidatorField() {
+        return dateValidatorField;
+    }
+    @DateRangeFieldValidator(
+        min="01/01/1990", 
+        max="01/01/2000", 
+        message="must be a min 01-01-1990 max 01-01-2000 if supplied")
+    public void setDateValidatorField(Date dateValidatorField) {
+        this.dateValidatorField = dateValidatorField;
+    }
+    public String getEmailValidatorField() {
+        return emailValidatorField;
+    }
+    @EmailValidator(message="must be a valid email if supplied")
+    public void setEmailValidatorField(String emailValidatorField) {
+        this.emailValidatorField = emailValidatorField;
+    }
+    public Integer getIntegerValidatorField() {
+        return integerValidatorField;
+    }
+    @IntRangeFieldValidator(min="1", max="10", message="must be integer min 1 max 10 if supplied")
+    public void setIntegerValidatorField(Integer integerValidatorField) {
+        this.integerValidatorField = integerValidatorField;
+    }
+    public String getRegexValidatorField() {
+        return regexValidatorField;
+    }
+    @RegexFieldValidator(
+        regex="[^<>]+", 
+        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](../convention/)
+
+```xml
+<!DOCTYPE struts PUBLIC 
+  "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
+  "http://struts.apache.org/dtds/struts-2.5.dtd">
+
+<struts>
+    <package>
+         <action name="ajaxFormSubmit" class="org.apache.struts2.showcase.validation.AjaxFormSubmitAction">
+             <interceptor-ref name="jsonValidationWorkflowStack"/>
+             <result name="input">/WEB-INF/validation/ajaxFormSubmit.jsp</result>
+             <result type="jsonActionRedirect">ajaxFormSubmitSuccess</result>
+         </action>
+    </package>
+</struts>
+```
+
+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
+<%@taglib prefix="s" uri="/struts-tags" %>
+<html>
+<head>
+    <title>Struts2 Showcase - Validation - AJAX Form Submit</title>
+    <s:head theme="xhtml"/>
+</head>
+<body>
+<div class="page-header">
+    <h1>AJAX Form Submit</h1>
+</div>
+    <h3>Action Errors Will Appear Here</h3>
+    <s:actionerror theme="ajaxErrorContainers"/>
+    <hr/>
+    <s:form method="POST" theme="xhtml">
+        <s:textfield label="Required Validator Field" name="requiredValidatorField" theme="ajaxErrorContainers"/>
+        <s:textfield label="Required String Validator Field" name="requiredStringValidatorField" theme="ajaxErrorContainers"/>
+        <s:textfield label="Integer Validator Field" name="integerValidatorField" theme="ajaxErrorContainers"/>
+        <s:textfield label="Date Validator Field" name="dateValidatorField" theme="ajaxErrorContainers"/>
+        <s:textfield label="Email Validator Field" name="emailValidatorField" theme="ajaxErrorContainers"/>
+        <s:textfield label="URL Validator Field" name="urlValidatorField" theme="ajaxErrorContainers"/>
+        <s:textfield label="String Length Validator Field" name="stringLengthValidatorField" theme="ajaxErrorContainers"/>
+        <s:textfield label="Regex Validator Field" name="regexValidatorField" theme="ajaxErrorContainers"/>
+        <s:textfield label="Field Expression Validator Field" name="fieldExpressionValidatorField" theme="ajaxErrorContainers"/>
+        <s:submit label="Submit" cssClass="btn btn-primary"/>
+    </s:form>
+</body>
+</html> 
+```
+
+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.
+
+> 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
+
+```
+parent = xhtml
+```
+
+#### actionerror.ftl
+
+```html
+<#--
+    Make sure element is always present. To be filled later via JS.
+-->
+<ul<#rt/>
+<#if parameters.id??>
+ id="${parameters.id?html}"<#rt/>
+</#if>            
+<#if parameters.cssClass??>
+ class="${parameters.cssClass?html}"<#rt/>
+<#else>
+ class="errorMessage"<#rt/>
+</#if>
+<#if parameters.cssStyle??>
+ style="${parameters.cssStyle?html}"<#rt/>
+</#if>
+>
+<#if (actionErrors?? && actionErrors?size > 0)>
+    <#list actionErrors as error>
+        <#if error??>
+            <li><span><#if parameters.escape>${error!?html}<#else>${error!}</#if></span><#rt/></li><#rt/>
+        </#if>
+    </#list>
+</#if>
+</ul>
+```
+
+#### controlfooter.ftl
+
+```html
+${parameters.after!}<#t/>
+    </td><#lt/>
+</tr>
+<#if (parameters.errorposition!"top") == 'bottom'>
+<#assign hasFieldErrors = parameters.name?? && fieldErrors?? && fieldErrors[parameters.name]??/>
+<#if hasFieldErrors>
+<tr errorFor="${parameters.id}">
+    <td class="tdErrorMessage" colspan="2"><#rt/>
+        <#if hasFieldErrors>
+            <#list fieldErrors[parameters.name] as error>
+                <div class="errorMessage">${error?html}</div><#t/>
+            </#list>
+        </#if>
+    </td><#lt/>
+</tr>
+</#if>
+</#if>
+```
+
+#### controlheader-core.ftl
+
+```html
+ <#--
+    Always include elements to show errors. They may be filled later via AJAX.
+-->
+<#assign hasFieldErrors = parameters.name?? && fieldErrors?? && fieldErrors[parameters.name]??/>
+<#if (parameters.errorposition!"top") == 'top'>
+<tr errorFor="${parameters.id}">
+    <td class="tdErrorMessage" colspan="2" data-error-for-fieldname="${parameters.name}"><#rt/>
+        <#if hasFieldErrors>
+            <#list fieldErrors[parameters.name] as error>
+                <div class="errorMessage">${error?html}</div><#t/>
+            </#list>
+        </#if>
+    </td><#lt/>
+</tr>
+</#if>
+<#if !parameters.labelposition?? && (parameters.form.labelposition)??>
+<#assign labelpos = parameters.form.labelposition/>
+<#elseif parameters.labelposition??>
+<#assign labelpos = parameters.labelposition/>
+</#if>
+<#--
+    if the label position is top,
+    then give the label it's own row in the table
+-->
+<tr>
+<#if (labelpos!"") == 'top'>
+    <td class="tdLabelTop" colspan="2"><#rt/>
+<#else>
+    <td class="tdLabel"><#rt/>
+</#if>
+<#if parameters.label??>
+    <label <#t/>
+<#if parameters.id??>
+        for="${parameters.id?html}" <#t/>
+</#if>
+<#if hasFieldErrors>
+        class="errorLabel"<#t/>
+<#else>
+        class="label"<#t/>
+</#if>
+    ><#t/>
+<#if parameters.required!false && parameters.requiredPosition!"right" != 'right'>
+        <span class="required">*</span><#t/>
+</#if>
+${parameters.label?html}<#t/>
+<#if parameters.required!false && parameters.requiredPosition!"right" == 'right'>
+ <span class="required">*</span><#t/>
+</#if>
+${parameters.labelseparator!":"?html}<#t/>
+<#include "/${parameters.templateDir}/${parameters.expandTheme}/tooltip.ftl" />
+</label><#t/>
+</#if>
+    </td><#lt/>
+<#-- add the extra row -->
+<#if (labelpos!"") == 'top'>
+</tr>
+<tr>
+</#if>
+```
+
+### 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_ .
+
+```js
+.ajaxVisualFeedback {
+    width: 16px;
+    height: 16px;
+    background-image: url('../images/indicator.gif');
+    background-repeat: no-repeat;
+    float: right;
+}
+```
+
+### 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
+ /**
+  * Validates form per AJAX. To be called as onSubmit handler.
+  *
+  * @param event onSubmit event
+  */
+function ajaxFormValidation(event) {
+    event.preventDefault();
+    _removeValidationErrors();
+    var _form = $(event.target);
+    var _formData = _form.serialize(true);
+    // prepare visual feedback
+    // you may want to use other elements here
+    var originalButton = _form.find('.btn-primary');
+    // note: jQuery returns an array-like object
+    if (originalButton && originalButton.length && originalButton.length > 0) {
+        originalButton.hide();
+        var feedbackElement = $('<div class="ajaxVisualFeedback"></div>').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><span></span></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 class="errorMessage"></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:
+
+```json
+{
+    "errors": ["Global Error 1", "Global Error 2"],
+    "fieldErrors": {
+        "field1": ["Field 1 Error 1", "Field 1 Error 2"],
+        "field1": ["Field 2 Error 1", "Field 2 Error 2"]  
+    }
+}
+```
+
+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:
+
+```json
+{}
+```
+
+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:
+
+```json
+{"location": "<url to be loaded next>"}
+```
+
+> 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
+
+Some details are omitted, like results used.
+
+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.
+
+![Flow chart ](struts2-ajax-vali-flow.png) 
+ 
\ No newline at end of file
diff --git a/source/plugins/json/struts2-ajax-vali-flow.png b/source/plugins/json/struts2-ajax-vali-flow.png
new file mode 100755
index 0000000..8da0331
Binary files /dev/null and b/source/plugins/json/struts2-ajax-vali-flow.png differ