miercuri, 20 ianuarie 2016

Using PrimeFaces and OmniFaces to customize the PrimeFaces client-side bean validation error messages

As you probably know, PrimeFaces support Bean Validation. Actually, PrimeFaces comes with a client side validation framework integrated with Bean Validation Specification.  Basically, PrimeFaces obtains via a set of classes (located in org.primefaces.validate.bean) the server side validation constraints for each type of supported validators. These constrains will be used in a JavaScript file named beanvalidation.js to perform the validation on client side and generate the corresponding messages. For example, let's suppose that we have the following bean:

@Named
@RequestScoped
public class DataBean {

 @Size(min = 2, max = 25)
 private String name;
 @Size(min = 2, max = 10)
 private String surname;

 // getters and setters
}

PrimeFaces extracts the constraints for @Size via org.primefaces.validate.bean.SizeClientValidationConstraint, like below:

package org.primefaces.validate.bean;

import java.util.HashMap;
import java.util.Map;
import javax.validation.constraints.Size;
import javax.validation.metadata.ConstraintDescriptor;
import org.primefaces.util.HTML;

public class SizeClientValidationConstraint implements ClientValidationConstraint {

 private static final String MESSAGE_METADATA = "data-p-size-msg";
 private static final String MESSAGE_ID = "{javax.validation.constraints.Size.message}";

 public Map<String, Object> getMetadata(ConstraintDescriptor constraintDescriptor) {
       
  Map<String, Object> metadata = new HashMap<String, Object>();
  Map attrs = constraintDescriptor.getAttributes();
  Object message = attrs.get("message");

  metadata.put(HTML.VALIDATION_METADATA.MIN_LENGTH, attrs.get("min"));
  metadata.put(HTML.VALIDATION_METADATA.MAX_LENGTH, attrs.get("max"));

  if (!message.equals(MESSAGE_ID)) {
      metadata.put(MESSAGE_METADATA, message);
  }

  return metadata;
 }

 public String getValidatorId() {
  return Size.class.getSimpleName();
 }
}

Now, the extracted constrains are used in beanvalidation.js, as below:

if (window.PrimeFaces) {
    ...
    PrimeFaces.locales.en_US.messages["javax.validation.constraints.Size.message"] = "size must be between {0} and {1}";

    PrimeFaces.validator.Size = {
    MESSAGE_ID: "javax.validation.constraints.Size.message",
      validate: function(d, f) {
       if (f !== null) {
           var e = d.val().length,
           c = d.data("p-minlength"),
           a = d.data("p-maxlength"),
           b = PrimeFaces.util.ValidationContext;
           if (e < c || e > a) {
               throw b.getMessageBV(d, this.MESSAGE_ID, d.data("p-size-msg"), c, a)
           }
       }
      }
    };
    ...  
};

So, this will produce the error messages from figure below:


Now, let's suppose that we want to change this message and involve more information in it. For example, let's suppose that we want to provide in the error messages the labels of the invalid components. So, instead of the above image, we want to obtain something like this:


Note In case of server-side validation via Bean Validation, you can accomplish this task via OmniFaces JsfLabelMessageInterpolator.

In order to accomplish this task we have several approaches. One of these approaches consist in overriding the SizeClientValidationConstraint for extracting the validated input label and overriding the client-side implementation to take this label into account.

So, first we will write a new SizeClientValidationConstraint like below:

import java.util.HashMap;
import java.util.Map;
import javax.validation.constraints.Size;
import javax.validation.metadata.ConstraintDescriptor;
import static org.omnifaces.util.Components.getCurrentComponent;
import static org.omnifaces.util.Components.getLabel;
import org.primefaces.util.HTML;

public class SizeClientValidationConstraint implements ClientValidationConstraint {

 private static final String MESSAGE_METADATA = "data-p-size-msg";
 private static final String MESSAGE_ID = "{javax.validation.constraints.Size.message}";

 public Map<String, Object> getMetadata(ConstraintDescriptor constraintDescriptor) {
       
  Map<String, Object> metadata = new HashMap<String, Object>();
  Map attrs = constraintDescriptor.getAttributes();
  Object message = attrs.get("message");

  metadata.put(HTML.VALIDATION_METADATA.MIN_LENGTH, attrs.get("min"));
  metadata.put(HTML.VALIDATION_METADATA.MAX_LENGTH, attrs.get("max"));
  metadata.put(HTML.VALIDATION_METADATA.LABEL, getLabel(getCurrentComponent()));       

  if (!message.equals(MESSAGE_ID)) {
      metadata.put(MESSAGE_METADATA, message);
  }

  return metadata;
 }

 public String getValidatorId() {
  return Size.class.getSimpleName();
 }
}

Thanks to OmniFaces utilities, we can obtain the label of the current validated input component via Components#getCurrentComponent() and Components#getLabel(). This save us for writing a good chunk of code! Further, we simply add the label in the metadata under HTML.VALIDATION_METADATA.LABEL.

Now, on client-side, we adjust the JavaScript code to use this label in error validation messages for @Size:

<script type="text/javascript">
 //<![CDATA[
 PrimeFaces.locales.en_US.messages["javax.validation.constraints.Size.message"] = "The size of {0} must be between {1} and {2} characters";
 PrimeFaces.validator.Size = {
  MESSAGE_ID: "javax.validation.constraints.Size.message",
  validate: function (d, f) {
  if (f !== null) {
      var e = d.val().length,
          c = d.data("p-minlength"),
          a = d.data("p-maxlength"),
          l = d.data("p-label");
          b = PrimeFaces.util.ValidationContext;
          if (e < c || e > a) {
              throw b.getMessageBV(d, this.MESSAGE_ID, d.data("p-size-msg"), l, c, a)
          }
   }
  }
 };
 //]]>  
</script>

Done! The complete application in available here.

Read more such goodies in:

PrimeFaces & OmniFaces - Powers Combined

Niciun comentariu:

Trimiteți un comentariu