Tuesday Jun 04, 2013

How-to highlight input field content when accessed from auto-tab

A question on the OTN forum has been how to highlight the content of an input field when navigation to it occurs using the auto tab functionality. As a reminder, the "autotab" property of an input field, if set to "true", tabs out of the field that you currently edit when the maximum length of the field entry is reached.

The problem reported on OTN for JDeveloper 11.1.2.4 (and I am sure its the same on other versions of JDeveloper) is that manual tabbing into a next field will highlight the fields content (value) whereas autotab doesn't. To enforce consistent highlighting behavior, you can use JavaScript as shown in the page sample below:

<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <af:document title="AutoTab.jsf" id="d1">
    <af:resource type="javascript">
      function onFocusHandler(inputEvent){
         var textfield = 
             AdfAgent.AGENT.getElementById(inputEvent.getSource().getId()+'::content');
         textfield.select();
     }
    </af:resource>
      <af:form id="f1">
        <af:panelFormLayout id="pfl1">
            <f:facet name="footer"/>
             <af:inputText label="First Name" id="it1" 
                           value="#{viewScope.fieldValues.firstName}" 
                           maximumLength="5" autoTab="true"/>
             <af:inputText label="Last Name" id="it2" 
                           value="#{viewScope.fieldValues.lastname}">
                <af:clientListener method="onFocusHandler" type="focus"/>
             </af:inputText>
          </af:panelFormLayout>
       </af:form>
   </af:document>
</f:view>

The af:clientListener is attached to the input field for which you want to show content highlighting on autotab. Attach the same client listener definition to  all field that should have the content highlighting. Instead of adding the JavaScript to the page, you want to create an external JS library file and link it from the page as the JS code above is reusable.

Note that the use of "'::content" in the JavaScript uses knowledge about the way component IDs are rendered at runtime in ADF Faces. If component rendering changes in a future release (you never know) then your code will break at this point and needs to be corrected. So heads-up on this risk (though should be a small one). To the time of writing this lines there is no other API (option) available to get to the HTML field handle in ADF Faces.

Also note the use of AdfAgent.AGENT.getElementById, which is a performant wrapper API in ADF Faces around the document.findElementById function of the Browser DOM. As a rule of thumb you should always work with ADF Faces JS APIs and don't directly reach out to the DOM yourself.

Friday Jan 04, 2013

JavaScript function to intercept or listen for tab remove event

The af:panelTabbed component allows you to remove tabs from display using the tabRemoval property

tabRemoval Valid Values: none, all, allExceptLast

determines if tab removal is enabled.

This attribute supports these tabRemoval types:

  • none - tab removal is not enabled.
  • all - tab removal is enabled on all tabs, regardless if they are disclosed. The last tab in the panelTabbed can be removed.
  • allExceptLast - tab removal is enabled on all tabs, regardless if they are disclosed. The last tab in the panelTabbed cannot be removed.

 src: http://docs.oracle.com/cd/E35521_01/apirefs.111230/e17491/tagdoc/af_panelTabbed.html

Because tab removal is not automatic in ADF Faces, developers configure an item listener on the af:showDetailItem component that make the tabs within a panel tab

 

itemListener javax.el.MethodExpression Only EL a method reference to an item listener

src: http://docs.oracle.com/cd/E35521_01/apirefs.111230/e17491/tagdoc/af_showDetailItem.html

"Choosing to remove a tab will cause an ItemEvent of type 'remove' to be launched. This event can be listened for on a showDetailItem using the itemListener attribute. It is up to the developer to handle this event and code the actual removal of the tab. This is because of the dynamic nature of the panelTabbed component, which may have dynamic children. This also allows a finer level of control by the developer, who can then choose a custom implementation that may (for example) include warning dialogs, and control of which tab gains focus after a tab is removed."

src: http://docs.oracle.com/cd/E35521_01/apirefs.111230/e17491/tagdoc/af_panelTabbed.html

This apparently works with logic written in Java saved on the server. A question on OTN was how to handle or intercept this remove event on the client side using JavaScript. This information - unfortunately - is a bit hidden in our documentation and you have to look into the JavaScript documentation for this.

src: http://docs.oracle.com/cd/E35521_01/apirefs.111230/e17489/oracle/adf/view/js/event/AdfItemEvent.html

If there is an AdfItemEvent that fires on the ADF Faces client side, then there also must be a configuration available on the af:clientListener to listen for such an event. And there is (just not obvious)

 <af:panelTabbed id="pt1" tabRemoval="all">  
    <af:showDetailItem text="TAB 1" id="sdi1" stretchChildren="first" clientComponent="false">
         ...
         <af:clientListener method="alertTabClose" type="item"/>
     </af:showDetailItem>
</af:panelTabbed
 

The JavaScript function to this looks similar to

function alertTabClose (closureEvent){
     var tab = closureEvent.getSource();  
     ...

This allows you to handle (and suppress) the tab remove event on the client using JavaScript in ADF Faces

Wednesday Mar 28, 2012

Gotcha when using JavaScript in ADF Regions

You use the ADF Faces af:resource tag to add or reference JavaScript on a page. However, adding the af:resource tag to a page fragment my not produce the desired result if the script is added as shown below

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
 <af:resource type="javascript">
  function yourMethod(evt){ ... }
</af:resource>

Adding scripts to a page fragment like this will see the script added for the first page fragment loaded by an ADF region but not for any subsequent fragment navigated to within the context of task flow navigation. The cause of this problem is caching as the af:resource tag is a JSP element and not a lazy loaded JSF component, which makes it a candidate for caching.

To solve the problem, move the af:resource tag into a container component like af:panelFormLayout so the script is added when the component is instantiated and added to the page. 

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<af:panelFormLayout>
 <af:resource type="javascript">
    function yourMethod(evt){ ... }
 </af:resource>
</af:panelFormLayout> 

Magically this then works and prevents browser caching of the script when using page fragments.

Thursday Mar 08, 2012

Using af:serverListener as a JS client-server proxy

Despite of ADF Faces having a client side JavaScript architecture, JavaScript rule #1 in ADF Faces is to use JavaScript as a fallback option only for development use cases in which there is no native solution to a problem. A built-in feature of the ADF Faces JavaScript client architecture is security that disallows certain component properties like disabled and readOnly to be changed from JavaScript.

To quote the JavaScript doc for the AdfRichInputText object:

http://docs.oracle.com/cd/E12839_01/apirefs.1111/e12046/oracle/adf/view/js/component/rich/input/AdfRichInputText.html#getReadOnly__

public Boolean getReadOnly()

Get function for attribute for 'readOnly'. This attribute is secured. You may get it, but you may not set it. Any changes to this attribute will not be transmitted to the server.

Note: The disabled property can be enabled for JavaScript modification by setting the component unsecure property as explained in the component documentation, which however I don't recommend you to do. If you need to open protected features for modification, do so in a way that you control, for example in that you can check a user permission to do so.

There might be use cases in which you need to change a protected property from JavaScript, and here is how this can and should be done:

The ADF Faces af:serverListener component allows developers to invoke server side Java from JavaScript and thus can be used as a proxy to bypass applied client side security.

For example, to be able to change the readOnly property on an af:inputText component, you define the text field as shown below

<af:inputText label="Label 2"
              id="it2" 
              clientComponent="true"                      
              value="#{SetInputTextFieldReadOnly.textValue}">
              <af:serverListener type="serverAction"
                                 method="#{SetInputTextFieldReadOnly.actionEvent}"/>
</af:inputText>

The clientComponent property need to be set to true on the component to ensure a JavaScript object is created on the client architecture. With no clientComponent set to true and no af:clientListener attached, the text field component is not accessible from JavaScript at all.

The af:serverListener needs to be configured on the component that is passed as a component reference to the server. In the sample, the component reference that is sent to the server is the input text field that you want to switch from updateable to read-only. If the integration is from a non ADF Faces component, like an Applet, you would configure the af:serverListener on the af:document component. If this is the case, you may not make use of the component reference on the server and instead pass additional payload arguments.

In the Oracle JDeveloper 11.1.1.6 sample to this article the switch between read-only and updateable is simulated by a command button you can press. The sample can be dowloaded from here.

In a realistic use case, this switch would be triggered from a non ADF Faces component like Applet or another technology you integrate in your ADF Faces page.

Note: The use of a command button in the sample is only for demonstration purpose. If the trigger was a command button then no JavaScript would be needed at all and JavaScript rule #1 would apply

On the server, a managed bean method is invoked by the client JavaScript call shown below:

<af:resource type="javascript">
  function setUpdateable(actionEvent){
     actionEvent.cancel();
     var commandButton = actionEvent.getSource();
     var textFieldId = commandButton.getProperty('componentId');
     invokeServerAction(textFieldId,actionEvent,'true');
  }
       
  function setReadOnly(actionEvent){      
    actionEvent.cancel();
    var commandButton = actionEvent.getSource();
    var textFieldId = commandButton.getProperty('componentId');
    invokeServerAction(textFieldId,actionEvent,'false');
  }   
  function invokeServerAction(compId,evt,isUpdateable){
    var textField = evt.getSource().findComponent(compId);            
    AdfCustomEvent.queue(
         textField,"serverAction",
         {updateable: isUpdateable},false);                              
  }
</af:resource>

Note: payload arguments are surrounded by curly braces. If you want to pass multiple arguments then you use a comma separated list of key1:value1, key2:value2 …pairs.

The JavaScript is taken from the sample provided for this tip and – as mentioned – is invoked from a command button. The command button has a custom property (af:clientAttribute) added that tells the script about the text component Id to modify. The component and the desired switch state "readOnly" or "updateable" is passed to the managed bean method:

 /**
  * Proxy the client request
  * @param clientEvent The client event gives you access to the 
  * component that raises the event as well as to the paraneters 
  * passed from the client to the server 
 */
 public void actionEvent(ClientEvent clientEvent) {
  /*
   * This would be a good place to check an ADF Security resource
   * permission if the user is allowed to perform the change on the 
   * attribute. There is no security in JavaScript, but using server 
   * side JAAS protects you from unauthorized changes
   */
   RichInputText inputText =  (RichInputText) clientEvent.getComponent();
   String updateable = (String) clientEvent.getParameters().get("updateable");
   inputText.setReadOnly(!Boolean.parseBoolean(updateable));
   AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
   adfFacesContext.addPartialTarget(inputText);
 }

Sample Download

https://blogs.oracle.com/jdevotnharvest/resource/SetInputTextToReadOnlyJS.zip

Thursday Feb 23, 2012

How-to define a default action for page fragments

The af:form component has a DefaultCommand property that, when set to the component Id of a command component like af:commandButton invokes the associated command action when the enter key is pressed anywhere in this form. However, if the form fields are contained in a page fragment exposed in a region then using the DefaultCommand property may not be an option as it is difficult to predict the command button id and its surrounding naming containers.

A solution to this is to use JavaScript on the UI input components that, when the Enter key is pressed virtually press a button within the page fragment (note that the af:form element belongs to the parent page and that you can only have a single af:form component per browser page)

Let's assume a page fragment with a single input text component and a command button to press:

<af:panelFormLayout id="pfl1">
  <f:facet name="footer">
     <af:commandButton text="Print Field Value" id="cb1" partialSubmit="true"                                            
                       clientComponent="true" action="#{View1Bean.onButtonPressed}"/>
   </f:facet>
   <af:inputText label="Enter Field" id="it1" binding="#{View1Bean.inputTextField}">
      <af:clientListener method="onFieldEnterKey" type="keyUp"/>
   </af:inputText>  
</af:panelFormLayout>

The command button is bound to a managed bean action method. By default, the action method is invoked when users press the command button. However, with the JavaScript shown next, this can be simulated and mapped to the Enter key press in the text field.

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:af=http://xmlns.oracle.com/adf/faces/rich
          xmlns:f="http://java.sun.com/jsf/core">

<af:resource type="javaScript">
   //function called by the client listener

  function onFieldEnterKey(inputEvent){
    if (event.getKeyCode() == AdfKeyStroke.ENTER_KEY) {
      //get the input text component from the event            
      var inputTextField = inputEvent.getSource();
      //the button is relative to the input text field so
      //relative search will do with no worrying about naming
      //containers
      var defaultButton = inputTextField.findComponent('cb1');
      //perform a partial submot
      var partialSubmit = true;
      AdfActionEvent.queue(defaultButton,partialSubmit);
      //Enter key does not need to go to server as we
      //queued a new event
       event.cancel();
   }
}
</af:resource>

For JavaScript to work, note the use of the af:clientListener on the input text field and the use of the clientComponent="true" configuration on the button.

Monday Jan 30, 2012

Disclose panelBox with a double-click on the header

No long post today, just some sample code showing the bit of JavaScript you need to add to a panel box to toggle it open/close on a double click.

<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <af:document title="PanelBoxTester.jsf" id="d1">
    <af:resource type="javascript">
       function togglePanelBox(mouseEvent){
          var panelBox = mouseEvent.getSource();
          panelBox.broadcast(new AdfDisclosureEvent(panelBox, !panelBox.getDisclosed()));
       }
     </af:resource>
        <af:form id="f1">
          <af:panelBox text="PanelBox1" id="pb1">
              <f:facet name="toolbar"/>
                <af:clientListener method="togglePanelBox" type="dblClick"/>
            </af:panelBox>
        </af:form>
    </af:document>
</f:view>

        
    

Friday Oct 14, 2011

How to programmatically set focus on an input component

The af:document component has a property InitialFocusId that allows you to define the Id of an input component that should take focus when a page initially loads. But how do you set the component focus for pages that are already rendered? A use case for this is a partial submit that executes logic on the server, after which the cursor focus in the UI needs to be set to a specific input component of a page or page fragment. The solution to this use case is JavaScript that is generated on the server and executed on the client.

The simplified sample I wrote contains of three input text fields and three command buttons. Initially, using the af:document InitialFocusId property, the cursor focus is set to the first input text field.

The command button send a partial submit to a managed bean on the server. The managed bean reads the component Id of the input text field to put the focus to from a client attribute that is defined on each command button. Using the MyFaces Trinidad ExtendedRenderKitService class, the managed bean sends a JavaScript call to the client to change the current component focus.

Important: By default, ADF Faces does not render all its user interface components as JavaScript objects. It only renders those components as JavaScript objects that have behavior, like a table that can have its columns moved. Input text components, for example, are rendered in HTML only, which means that the ADF client side JavaScript framework will not find a handle to the component unless you tell ADF Faces to create one. For this, you set the ClientComponent property of the input text fields to true. This also is required for the af:document InitialFocusId property to work.

Below is the managed bean code that handles the command button action and that composes and executes the JavaScript to set the client side UI focus.

import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import oracle.adf.view.rich.component.rich.input.RichInputText;
import oracle.adf.view.rich.component.rich.nav.RichCommandButton;
import org.apache.myfaces.trinidad.render.ExtendedRenderKitService;
import org.apache.myfaces.trinidad.util.Service;
public
class FocusBean {
  public FocusBean() {
  }

 public String onSetFocus(ActionEvent event) {
   RichCommandButton rcb = (RichCommandButton)event.getSource();
   String focusOn = (String)rcb.getAttributes().get("focusField");
   FacesContext fctx = FacesContext.getCurrentInstance();
   UIViewRoot viewRoot = fctx.getViewRoot();
  //search can be improved to include naming containers
  RichInputText rit = RichInputText)viewRoot.findComponent(focusOn);
 if (rit != null) {
   String clientId = rit.getClientId(fctx); 
   //compose JavaScript to be executed on the client 
   StringBuilder script = new StringBuilder();
   //use client id to ensure component is found if located in
   //naming container  
   script.append("var textInput = ");
   script.append("AdfPage.PAGE.findComponentByAbsoluteId");
   script.append ("('"+clientId+"');");
 
   script.append("if(textInput != null){"); 
   script.append("textInput.focus();");
   script.append("}");
   //invoke JavaScript
   writeJavaScriptToClient(script.toString());
  }  
 }
  //generic, reusable helper method to call JavaScript on a client
  private void writeJavaScriptToClient(String script) {
    FacesContext fctx = FacesContext.getCurrentInstance();
    ExtendedRenderKitService erks = null;
    erks = Service.getRenderKitService(
                 fctx, ExtendedRenderKitService.class);
    erks.addScript(fctx, script);
  }
}

 As mentioned, the command buttons in the sample have a client attribute attached that I use to determine which input component should receive focus. The managed bean calls the client attribute here:

RichCommandButton rcb = (RichCommandButton)event.getSource();
String focusOn = (String)rcb.getAttributes().get("focusField");

 In the page definition, the client attribute is defined as

<af:commandButton text="Focus Field 2" id="commandButton1"
                   partialSubmit="true"
                   actionListener="#{FocusBean.onSetFocus}">
  <af:clientAttribute name="focusField" value="it2"/>
</af:commandButton>
<af:commandButton text="Focus Field 3" id="cb1"
                  partialSubmit="true"
 actionListener="#{FocusBean.onSetFocus}">
  <af:clientAttribute name="focusField" value="it3"/>
</af:commandButton>

In your custom implementation of this use case, you for sure will have your own way of determining the next focus target. The server side code however doesn't change much.

You can DOWNLOAD the sample workspace from here: http://blogs.oracle.com/jdevotnharvest/resource/SetFocusToField.zip


Saturday Feb 26, 2011

How to ensure custom serverListener events fires before action events

[Read More]

Tuesday Nov 30, 2010

Get social security numbers right

A common development use case is to guide users when working with input fields that require a specific input format. For example, credit card and social security number fields use character delimiters that you may want to enforce on a field. The following sample uses JavaScript to add a defined delimiter character according to a defined character.

The American social security pattern is defined as xxx-xx-xxxx. Users that type 123456789 should have the input automatically corrected to 123-45-6789 while they type. Also, the field should be protected from character input and input length larger than the provided pattern.

<af:inputText label="Social Security Number" id="it1"
rendered="true">
<af:clientListener
method="handleNumberFormatConversion('xxx-xx-xxxx','-')"
type="keyDown"/>
</af:inputText>

With the above configuration, the handleNumberFormatConversion method is called for each key stroke in the input field. Additional arguments provided to the function are the input pattern and the character delimiter.

The JavaScript code that is addressed by the clientListener on the InputText is shown below:

// JavaScript function that applies a specific format
// to numeric input. The pattern argument defines the
// input mask, e.g. xxx-xx-xxxx. The delimiter defines
// the delimiter character to add to the user input based
// on the pattern

function handleNumberFormatConversion(pattern, delimiter){
return function(evt){
var inputField = evt.getCurrentTarget();
var keyPressed = evt.getKeyCode();
var oldValue = inputField.getSubmittedValue();
//keycode 48-57 are keys 0-9
//keycode 96-105 are numbpad keys 0-9

var validKeys = new Array(48,49,50,51,52,53,54,55,
56,57,96,97,98,99,100,
                                 101,102,103,104,105,
AdfKeyStroke.ARROWRIGHT_KEY,
AdfKeyStroke.ARROWLEFT_KEY,
AdfKeyStroke.BACKSPACE_KEY,
AdfKeyStroke.DELETE_KEY,
AdfKeyStroke.END_KEY,
AdfKeyStroke.ESC_KEY,
AdfKeyStroke.TAB_KEY);

var numberKeys = new Array(48,49,50,51,52,53,54,55,
56,57,96,97,98,99,100,
101,102,103,104,105);

var isValidKey = false;
for (var i=0; i < validKeys.length; ++i){
if (validKeys[i] == keyPressed) {
isValidKey = true;
break;
}
}
if(isValidKey){
//key is valid, ensure formatting is correct
var isNumberKey = false;
for (var n=0; n < numberKeys.length; ++n){
if(numberKeys[n] == keyPressed){
isNumberKey = true;
break;
}
}
if(isNumberKey){
//if the user provided enough data, cancel
//the input
var formatLength = pattern.length;
if(formatLength == oldValue.length){
inputField.setValue(oldValue);
evt.cancel();
}
//more values allowed. Check if delimiter needs to
           //be set
else{
//if the date format has a delimiter as the next
//character, add it
if(pattern.charAt(oldValue.length)== delimiter){
oldValue = oldValue+delimiter;
inputField.setValue(oldValue);
}
}
}
}
else{
//key is not valid, so undo entry
inputField.setValue(oldValue);
evt.cancel();
}
}
}

The sample is for number only input. However, changing it for character or mixed input is not difficult to do. Note however that you can't use this with af:inputDate component because this component doesn't work well when setting String formatted values as the value property. (At least my debugging showed this)

Wednesday Nov 24, 2010

Using JavaScript to clear validation error messages

ADF Faces provides client validation for component constraints like required fields. If input fields fail validation, then form data is not submitted to the server and instead an error message is displayed for the fields causing the error.

The error messages are displayed until the user corrects the input field values and re-submits the form, which could be irritating to users. To get rid of error messages of a failed form submit, application developers can use JavaScript as shown below

<af:resource type="javascript">
function clearMessagesForComponent(evt){
AdfPage.PAGE.clearAllMessages();
evt.cancel();
}
</af:resource>

The JavaScript client function is called from the focus event of a client listener added to the input fields in a form

<af:panelFormLayout id="pfl1">
<af:inputText value="#{bindings.EmployeeId.inputValue}" ...>
...
<af:clientListener method="clearMessagesForComponent"
type="focus"/>
</af:inputText>
<af:inputText value="#{bindings.FirstName.inputValue}" ... >
...
<af:clientListener method="clearMessagesForComponent"
type="focus"/>
</af:inputText>
...
</af:panelFormLayout>

Note: The AdfPage.js class provides APIs to access the component messages and the changed components in list objects to individually handle component error message instead of clearing them all.

About

The Oracle JDeveloper forum ranks in the Top 5 of the most active forums on the Oracle Technology Network (OTN).



The OTN Harvest blog is a summary of selected topics posted on the OTN Oracle JDeveloper forum.



It is an effort to turn knowledge exchange into an interesting read for developers who enjoy little nuggets of wisdom





Frank Nimphius

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today