Fun with Declarative Components

Use case background

I have been asked on a number of occasions if our selectOneChoice component could allow random text to be entered, as well as having a list of selections available. Unfortunately, the selectOneChoice component only allows entry via the dropdown selection list and doesn't allow text entry. I was thinking of possible solutions and thought that this might make a good example for using a declarative component.


My initial idea

My first thought was to use an af:inputText to allow the text entry, and an af:selectOneChoice with mode="compact" for the selections. To get it to layout horizontally, we would want to use an af:panelGroupLayout with layout="horizontal". To get the label for this to line up correctly, we'll need to wrap the af:panelGroupLayout with an af:panelLabelAndMessage. This is the basic structure:

<af:panelLabelAndMessage>
<af:panelGroupLayout layout="horizontal">
<af:inputText/>
<af:selectOneChoice mode="compact"/>
</af:panelgroupLayout>
</af:panelLabelAndMessage>


Make it into a declarative component

One of the steps to making a declarative component is deciding what attributes we want to be able to specify. To keep this example simple, let's just have:

  • 'label' (the label of our declarative component)
  • 'value' (what we want to bind to the value of the input text)
  • 'items' (the select items in our dropdown)

Here is the initial declarative component code (saved as file "inputTextWithChoice.jsff"):

<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (c) 2008, Oracle and/or its affiliates. All rights reserved. -->
<jsp:root
xmlns:jsp="http://java.sun.com/JSP/Page"
version="2.1"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<jsp:directive.page contentType="text/html;charset=utf-8"/>
<af:componentDef
var=
"attrs"
componentVar="comp">
<af:xmlContent>
<component xmlns="http://xmlns.oracle.com/adf/faces/rich/component">
<description>Input text with choice component.</description>
<attribute>
<description>Label</description>
<attribute-name>label</attribute-name>
<attribute-class>java.lang.String</attribute-class>
</attribute>
<attribute>
<description>Value</description>
<attribute-name>value</attribute-name>
<attribute-class>java.lang.Object</attribute-class>
</attribute>
<attribute>
<description>Choice Select Items Value</description>
<attribute-name>items</attribute-name>
<attribute-class>[[Ljavax.faces.model.SelectItem;</attribute-class>
</attribute>
</component>
</af:xmlContent>

<af:panelLabelAndMessage id="myPlm" label="#{attrs.label}" for="myIt">
<af:panelGroupLayout id="myPgl" layout="horizontal">
<af:inputText
id=
"myIt"
value="#{attrs.value}"
partialTriggers="mySoc"
label="myIt" simple="true" />
<af:selectOneChoice
id=
"mySoc"
label="mySoc" simple="true" mode="compact"
value="#{attrs.value}"
autoSubmit="true">
<f:selectItems id="mySIs" value="#{attrs.items}" />
</af:selectOneChoice>
</af:panelGroupLayout>
</af:panelLabelAndMessage>
</af:componentDef>
</jsp:root>

By having af:inputText and af:selectOneChoice both have the same value, then (assuming that this passed in as an EL expression) selecting something in the selectOneChoice will update the value in the af:inputText.

To use this declarative component in a jspx page:

<af:declarativeComponent
id=
"myItwc"
viewId="inputTextWithChoice.jsff"
label="InputText with Choice"
value="#{demoInput.choiceValue}"
items="#{demoInput.selectItems}" />


Some problems arise

At first glace, this seems to be functioning like we want it to. However, there is a side effect to having the af:inputText and af:selectOneChoice share a value, if one changes, so does the other. The problem here is that when we update the af:inputText to something that doesn't match one of the selections in the af:selectOneChoice, the af:selectOneChoice will set itself to null (since the value doesn't match one of the selections) and the next time the page is submitted, it will submit the null value and the af:inputText will be empty. Oops, we don't want that. Hmm, what to do.

Okay, how about if we make sure that the current value is always available in the selection list. But, lets not render it if the value is empty. We also need to add a partialTriggers attribute so that this gets updated when the af:inputText is changed. Plus, we really don't want to select this item so let's disable it.

<af:selectOneChoice 
id=
"mySoc"
partialTriggers="myIt"
label="mySoc" simple="true" mode="compact"
value="#{attrs.value}"
autoSubmit="true">
<af:selectItem id="mySI" label="Selected:#{attrs.value}"
value="#{attrs.value}" disabled="true"
rendered="#{!empty attrs.value}"/>

<af:separator id="mySp" />
<f:selectItems id="mySIs" value="#{attrs.items}" />
</af:selectOneChoice>

That seems to be working pretty good. One minor issue that we probably can't do anything about is that when you enter something in the inputText and then click on the selectOneChoice, the popup is displayed, but then goes away because it has been replaced via PPR because we told it to with the partialTriggers="myIt". This is not that big a deal, since if you are entering something manually, you probably don't want to select something from the list right afterwards.


Making it look like a single component.

Now, let's play around a bit with the contentStyle of the af:inputText and the af:selectOneChoice so that the compact icon will layout inside the af:inputText, making it look more like an af:selectManyChoice. We need to add some padding-right to the af;inputText so there is space for the icon. These adjustments were for the Fusion FX skin.

<af:inputText
id=
"myIt"
partialTriggers="mySoc"
autoSubmit="true"
contentStyle="padding-right: 15px;"
value="#{attrs.value}"
label="myIt" simple="true" />
<af:selectOneChoice
id=
"mySoc"
partialTriggers="myIt"
contentStyle="position: relative; top: -2px; left: -19px;"
label="mySoc" simple="true" mode="compact"
value="#{attrs.value}"
autoSubmit="true">
<af:selectItem id="mySI" label="Selected:#{attrs.value}"
value="#{attrs.value}" disabled="true"
rendered="#{!empty attrs.value}"/>

<af:separator id="mySp" />
<f:selectItems id="mySIs" value="#{attrs.items}" />
</af:selectOneChoice>

There you have it, a declarative component that allows for suggested selections, but also allows arbitrary text to be entered. This could be used for search field, where the 'items' attribute could be populated with popular searches. Lines of java code written: 0

Comments:

Hi, This works perfect on page. But when using showPrintablePageBehavior, the popup page shows two times (inputtext and selectOneChoice) showing text. How we can avoid it? Regards, Santhosh

Posted by Santhosh C Jacob on May 22, 2011 at 12:55 AM BST #

Looks like you would need to add this to the compact selectOneChoice: rendered="#{adfFacesContext.outputMode != 'printable'}" so that the compact selectOneChoice doesn't render for printable outputMode. It's not surprising that the compact selectOneChoice renders as text in printable outputMode, since printable is a non-interactive mode and you'd want to be able to see the selected value.

Posted by Marky on May 23, 2011 at 08:10 AM BST #

I am trying to create a custom components and have few attributes in it. some of them are labels. I need to provided functionality like when someone consumes this component he/she gets the option of 'Select Text Resource' as you get when you try to edit a label kind of property for default ADF components. [using small down arrow edit option available next to each property field in property inspector.]

Can you help me know what changes would I need to do ?

Posted by guest on February 23, 2012 at 07:01 PM GMT #

Hi Marky,
I was trying to use af:declarativeComponent instead of taglibdeclarative Component.If I want to use actionListener or any listeners in the declarative component, the control was not going . So could you please help me out how to invoke listeners from declarativeComp.

Posted by vishnu on March 22, 2013 at 10:13 AM GMT #

Vishnu, for this example lets say I wanted to set the valueChangeListener of the af:inputText component, I would add a valueChangeListener attribute to the declarative component which would be passed to the af:inputText.
<af:componentDef
var="attrs"
componentVar="comp">
<af:xmlContent>
...
<attribute>
<description>Value Change Listener</description>
<attribute-name>valueChangeListener</attribute-name>
<attribute-class>javax.faces.el.MethodBinding</attribute-class>
</attribute>
...
<af:inputText
id="myIt"
value="#{attrs.value}"
valueChangeListener="#{attrs.valueChangeListener}"
partialTriggers="mySoc"
label="myIt" simple="true" />
...

Posted by Marky on March 22, 2013 at 02:49 PM GMT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

An exploration in the lighter side of ADF development.

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