Fun with Declarative Components
Posted
by [email protected]
on Oracle Blogs
See other posts from Oracle Blogs
or by [email protected]
Published on Tue, 25 May 2010 16:00:27 +0000
Indexed on
2010/05/25
16:42 UTC
Read the original article
Hit count: 462
ADF Faces
|jdeveloper
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
© Oracle Blogs or respective owner