Programmatically defining ADF Messages
ADF leverages much of the JSF framework that it is built on for the hints and error messages you see when invalid data is entered into a field, so traditionally if you wanted to change the error message on an inputDate you would create a messages.properties file and override the default message of "Enter a date in the same format as this example: 11/29/1998".
org.apache.myfaces.trinidad.convert.DateTimeConverter.DATE_HINT=Example: dd/mm/yyyy
This is fine if you want all your date fields to have the same validation message, but if you want to customise each inputDate to use a different message, for example: date of birth field, start dates and end dates then it becomes a bit tricky.
You could add a regex validation to each of the inputDates, this will let you define your hint for each inputDate independently but it shows two hints, the one you defined in the regex plus the default value "Example: 11/29/1998". It also doesn't use the value defined in messageDetailNoMatch and defaults to the defined value in the message.properties or the default resource bundle.
Adding a regex validation to the jspx will be sufficient for 95% of ADF applications, but if your doing something that doesn't have the traditional jspx page, say your writing an application that can be customised by your end users or maybe you're generating your pages at runtime then you have to start digging into the framework.
This is where the ClientConverter interface and DateTimeConverter classes in the JSF world come in handy. When ADF generates a page (spinning Oracle thingy) it builds out quite a lot of javascript so the framework knows what validators and convertors are attached to the component.
For example if you dragged an inputDate field onto the page and added a regex at design time. At runtime it needs to know the full id, and what conversion rules it needs to use. The framework uses the ClientConvertor interfaces getClientConversion method to build out the following :
new AdfRichInputDate('r2:0:pn1:dc_id3',{'styleClass':'inputDate','converter':new TrDateTimeConverter('M/d/yyyy',null,'11/29/1998','DATE'),'validators':[new TrRegExpValidator('[0-9/*]{10}',{'detail':'not a valid date dopey','hint':'im a hint'})]})
The TRDateTimeConverter then takes the inputs and renders that on the screen as such
As you can see from the example image above, it doesn't show the "not a valid date dopey"message and has two hints.
So to override these messages and define your own, you have to create your own class that implements the ClientConverter interface and extend the DateTimeConverter. Technically extending the DateTimeConverter isn't necessary and you could implement the Converter interface. But once you start implementing these methods there are alot of hooks into the org.apache.myfaces.trinidadinternal packages that you don't have access to, the DateTimeConverter does have access into the internal packages so unless you want to replicate the code in your implementation it's easier, and is much likely to survive an upgrade if you don't.
So by implementing the getClientConversion method you are generating your own javascript that will get used at runtime on the clientside.
For example below, I've included some javadoc to make it a little clearer when adding the convertor to your inputDate.
To use the new convertor, bind your inputDate to a bean or if you are generating the component programmatically as well you can use the setter.
inputDateTest.setConverter(new DateConverter("dd/MM/yyyy","Date Of Birth","Please enter a valid date of birth: {2}","18/02/1981"));
Would render the following at runtime :
It's worth noting as well, that the {2} in this example is how you pass parameters to your messages. You don't need to add parameters to your messages if you don't want as it's entirely optional. But here is how the mapping works.
Full code example below
org.apache.myfaces.trinidad.convert.DateTimeConverter.DATE_HINT=Example: dd/mm/yyyy
This is fine if you want all your date fields to have the same validation message, but if you want to customise each inputDate to use a different message, for example: date of birth field, start dates and end dates then it becomes a bit tricky.
You could add a regex validation to each of the inputDates, this will let you define your hint for each inputDate independently but it shows two hints, the one you defined in the regex plus the default value "Example: 11/29/1998". It also doesn't use the value defined in messageDetailNoMatch and defaults to the defined value in the message.properties or the default resource bundle.
Adding a regex validation to the jspx will be sufficient for 95% of ADF applications, but if your doing something that doesn't have the traditional jspx page, say your writing an application that can be customised by your end users or maybe you're generating your pages at runtime then you have to start digging into the framework.
This is where the ClientConverter interface and DateTimeConverter classes in the JSF world come in handy. When ADF generates a page (spinning Oracle thingy) it builds out quite a lot of javascript so the framework knows what validators and convertors are attached to the component.
For example if you dragged an inputDate field onto the page and added a regex at design time. At runtime it needs to know the full id, and what conversion rules it needs to use. The framework uses the ClientConvertor interfaces getClientConversion method to build out the following :
new AdfRichInputDate('r2:0:pn1:dc_id3',{'styleClass':'inputDate','converter':new TrDateTimeConverter('M/d/yyyy',null,'11/29/1998','DATE'),'validators':[new TrRegExpValidator('[0-9/*]{10}',{'detail':'not a valid date dopey','hint':'im a hint'})]})
The TRDateTimeConverter then takes the inputs and renders that on the screen as such
As you can see from the example image above, it doesn't show the "not a valid date dopey"message and has two hints.
So to override these messages and define your own, you have to create your own class that implements the ClientConverter interface and extend the DateTimeConverter. Technically extending the DateTimeConverter isn't necessary and you could implement the Converter interface. But once you start implementing these methods there are alot of hooks into the org.apache.myfaces.trinidadinternal packages that you don't have access to, the DateTimeConverter does have access into the internal packages so unless you want to replicate the code in your implementation it's easier, and is much likely to survive an upgrade if you don't.
So by implementing the getClientConversion method you are generating your own javascript that will get used at runtime on the clientside.
For example below, I've included some javadoc to make it a little clearer when adding the convertor to your inputDate.
/** * getClientConversion - builds up the following javascript for clientside validation. * * new TrDateTimeConverter('dd/MM/yyyy',null,'11/29/1987','DATE',{'hint':'test','detail':'Please enter a valid date : {2}'}) * * @param facesContext - current facesContext * @param uiComponent - the uiComponent where the conversion will be attached */ public String getClientConversion(FacesContext facesContext, UIComponent uiComponent) { StringBuilder outBuffer = new StringBuilder(); try { outBuffer.append("new TrDateTimeConverter("); outBuffer.append(String.format("'%s',null,'%s','DATE',{'hint':'%s','detail':'%s'})", this.dateFormat, this.suggestionValue, this.hint, this.detail) ); } catch (Exception e) { outBuffer.append("null"); } return outBuffer.toString(); }
To use the new convertor, bind your inputDate to a bean or if you are generating the component programmatically as well you can use the setter.
inputDateTest.setConverter(new DateConverter("dd/MM/yyyy","Date Of Birth","Please enter a valid date of birth: {2}","18/02/1981"));
Would render the following at runtime :
It's worth noting as well, that the {2} in this example is how you pass parameters to your messages. You don't need to add parameters to your messages if you don't want as it's entirely optional. But here is how the mapping works.
- {0} = The label of the component, in this example the message would be "Please enter your date of birth : Date Of Birth"
- {1} = The value of the component, in this example the message would be "Please enter your date of birth : sdfs"
- {2} = The suggestionValue you defined yourself this is a static value at runtime, in this example the message would be "Please enter your date of birth : 18/02/1981"
Full code example below
package au.com.wildsoftware.convertor; import java.util.ArrayList; import java.util.Collection; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import org.apache.myfaces.trinidad.convert.ClientConverter; public class DateConverter extends javax.faces.convert.DateTimeConverter implements ClientConverter { private String dateFormat; private String hint; private String detail; private String suggestionValue; /** * DateConverter - Define customised converter along with hints and details. * * @param dateFormat - the conversion format dd/MM/yyyy will convert 10/5/13 to 10/05/2013 * @param hint - the hint message that pops up on focus * @param detail - the detail message when the conversion encounters an error, parameters can be used * {0) = the label attribute of the uiComponent when rendering * {1} = the value attribute of the uiComponent when rendering * {2} = the suggestionValue passed in as part of the constructor * @param suggestionValue - only used if your detail message makes reference to {2} * */ public DateConverter(String dateFormat, String hint, String detail, String suggestionValue) { super(); this.dateFormat = dateFormat; this.hint = hint; this.detail = detail; this.suggestionValue = suggestionValue; } /** * getClientConversion - builds up the following javascript for clientside validation. * * new TrDateTimeConverter('dd/MM/yyyy',null,'11/29/1987','DATE',{'hint':'test','detail':'Please enter a valid date : {2}'}) * * @param facesContext - current facesContext * @param uiComponent - the uiComponent where the conversion will be attached */ public String getClientConversion(FacesContext facesContext, UIComponent uiComponent) { StringBuilder outBuffer = new StringBuilder(); try { outBuffer.append("new TrDateTimeConverter("); outBuffer.append(String.format("'%s',null,'%s','DATE',{'hint':'%s','detail':'%s'})", this.dateFormat, this.suggestionValue, this.hint, this.detail)); } catch (Exception e) { outBuffer.append("null"); } return outBuffer.toString(); } /** * getClientLibrarySource - not sure what this does yet * * @param facesContext - facesContext */ public String getClientLibrarySource(FacesContext facesContext) { return null; } /** * getClientImportNames - imports the javascript library for TrDateTimeConvertor * * @return Collection<String> - a collection of imports */ public Collection<String> getClientImportNames() { ArrayList names = new ArrayList(2); names.add("TrDateTimeConverter()"); names.add(String.format("LocaleInfo_%s", getLocale().toString())); return names; } /** * getClientScript - not sure what this does yet * * @param facesContext - facesContext * @param uiComponent - current component */ public String getClientScript(FacesContext facesContext, UIComponent uiComponent) { return null; } }
No comments:
Post a Comment