Numeric keypad decimal point |
We came up with a solution based on a custom ADF Behavior tag. I've recently posted how to create your own ADF (client side) behavior. The first post was a simple version without properties, while the second post expanded this example with properties. Now it is time for the sequel showing how you can handle key presses in an input item and replace any decimal point keystroke with a decimal separator (possibly comma) keystroke. The user can simply use the decimal point on the numeric keypad and when using a European locale it will simply type a comma.
Read these previous posts to learn how to create custom ADF behavior tags. This post will only focus on the parts that are specific for this solution: the BehaviorTag class, the javascript implementation and a sample page. Let's start with the DecimalCommaBehaviorTag class. This represents the redheap:decimalCommaBehavior JSP tag and constructs the client side snippet of javascript:
package com.redheap.decimalcomma; import java.text.DecimalFormatSymbols; import java.util.Locale; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import org.apache.commons.lang.StringEscapeUtils; import org.apache.myfaces.trinidad.context.RequestContext; /** * Implementation of redheap:decimalCommaBehavior JSP tag. * @author Wilfred van der Deijl, www.redheap.com */ public class DecimalCommaBehaviorTag extends oracle.adfinternal.view.faces.taglib.behaviors.BehaviorTag { /** * Returns the snippet of javascript code that needs to be executed * on the client to construct the client behavior object. * @param component JSF component this behavior tag is attached to * @return snippet of javascript that instantiates an object */ @Override protected String getBehavior(UIComponent component) { return "new RedHeapDecimalCommaBehavior('" + StringEscapeUtils.escapeJavaScript(getDecimalSeparator()) + "')"; } /** * Returns the name of a JavaScript library feature that provides * the client-side implementation of this listener/behavior. * This feature will be added to the set of features required for * this page/request, ensuring that corresponding JavaScript * library partition is available. * @return a javascript library feature name */ @Override protected String getFeatureDependency() { return "RedHeapDecimalCommaBehavior"; } protected String getDecimalSeparator() { DecimalFormatSymbols dfs = new DecimalFormatSymbols(getLocale()); return String.valueOf(dfs.getDecimalSeparator()); } protected Locale getLocale() { Locale locale = RequestContext.getCurrentInstance().getFormattingLocale(); return locale != null ? locale : FacesContext.getCurrentInstance().getViewRoot().getLocale(); } }Line 25-27 returns the javascript script to instantiate the client-side implementation object. This gets the decimal separator character as argument to the constructor. This decimal separator character is determined in the getDecimalSeparator() methods which uses getLocale() to determine the locale for the current request/page.
Next is the most important part of this solution: the client-side javascript implementation:
/** * javascript constructor that invokes the Init function */ function RedHeapDecimalCommaBehavior(decimalSeparator) { this.Init(decimalSeparator); } // register as a subclass of AdfClientBehavior AdfObject.createSubclass(RedHeapDecimalCommaBehavior, AdfClientBehavior); /** * initialize this behavior * @override */ RedHeapDecimalCommaBehavior.prototype.Init = function(decimalSeparator) { // be sure to invoke initialization by superclass RedHeapDecimalCommaBehavior.superclass.Init.call(this); this._decimalSeparator = decimalSeparator; } /** * as part of the AdfClientBehavior behavior contract, initialize is * called when the component is created to give the behavior a chance * to register event listeners. * @param {AdfUIComponent} component */ RedHeapDecimalCommaBehavior.prototype.initialize = function(component) { AdfAssert.assertPrototype(component, AdfUIComponent); component.addEventListener(AdfUIInputEvent.KEY_PRESS_EVENT_TYPE, this._handleKeyPress, this); } /** * handle invoking a command component with this behavior. * @param {AdfUIInputEvent} event */ RedHeapDecimalCommaBehavior.prototype._handleKeyPress = function(event) { AdfAssert.assertPrototype(event, AdfUIInputEvent); if (event.getKeyCode() == '.'.charCodeAt(0)) { // this looks like the '.' is pressed and we might want to replace it if (event.getNativeEvent && event.getNativeEvent().charCode==0) { // Firefox wrongfully raises this keyPress event on the DELETE // button as well. Look at the native event to distinguish and // abort. Also see "Special keys" at // http://www.quirksmode.org/dom/events/keys.html return; } if ('.' == this._decimalSeparator) { // no need to do anything if decimal-separator of current locale // uses decimal dot return; } // replace keystroke of dot on numeric keypad with decimal separator // of current locale var source = event.getSource(); AdfLogger.LOGGER.logMessage(AdfLogger.FINE, "replacing . keystroke with " + this._decimalSeparator + " for " + source.getClientId()); var input = AdfDhtmlEditableValuePeer.GetContentNode(source); if (input) { event.cancel(); // cancel normal event to suppress '.' character if (typeof(input.selectionStart) !== 'undefined') { // gecko, webkit or IE9+ support selection properties AdfLogger.LOGGER.logMessage(AdfLogger.FINE, "using selection properties for modern browsers"); // determine future caret position before we start messing with // selection and content var newPos = input.selectionStart + this._decimalSeparator.length; // inject decimal separator while keeping text before and after // selection (thus removing selection) var oldtext = input.value; input.value = oldtext.substring(0, input.selectionStart) + this._decimalSeparator + oldtext.substring(input.selectionEnd, oldtext.length); // set caret position after the decimal separator input.selectionStart = newPos; input.selectionEnd = newPos; } else if (document.selection && document.selection.createRange) { AdfLogger.LOGGER.logMessage(AdfLogger.FINE, "using document.selection for older IE versions"); var sel = document.selection.createRange(); // replace current selection with decimal-sep or insert at caret sel.text = this._decimalSeparator; // collapse selection so caret is after decimal-separator sel.setEndPoint("StartToEnd", sel); sel.select(); } } } }
The code should be well documented so reading a couple of times should make clear what it does. Basically we are canceling the key-press event for "." and injecting the decimal separator in the value of the current item at the cursor position or replacing any existing selected text within this item.
Finally the sample page:
<?xml version='1.0' encoding='UTF-8'?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:af="http://xmlns.oracle.com/adf/faces/rich" xmlns:redheap="http://redheap.com/taglib"> <jsp:directive.page contentType="text/html;charset=UTF-8"/> <f:view> <af:document id="d1"> <af:form id="f1"> <af:panelFormLayout> <af:inputText label="Browser locale (should use comma decimal separator)" value="#{facesContext.viewRoot.locale}" readOnly="true"/> <af:inputText label="Try typing point on decimal keyboard" id="it1"> <redheap:decimalCommaBehvior/> </af:inputText> </af:panelFormLayout> </af:form> </af:document> </f:view> </jsp:root>
As always you can download the full sample application or browse the subversion repository to look at the source code.
No comments:
Post a Comment
Comments might be held for moderation. Be sure to enable the "Notify me" checkbox so you get an email when the comment is accepted and I reply.