Fun with Declarative Components
- by [email protected]
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