What is the best way to implement an apex:selectList add-on or equivalent, so that when the user selects "Other" a free-form text field appears to allow the user to enter any value (s)he wants?
2 Answers
I ended up implementing the picklist as three components, using jQuery for convenience. The idea is basically that an apex:inputHidden
is actually used to store the value, with an apex:selectList
used to render the picklist and an apex:inputText
to render the dependent text box.
The three elements are "grouped" together by adopting an id
attribute convention:
apex:selectList id="select"
apex:inputText id="selectText"
apex:inputHidden id="selectHidden"
Furthermore, the picklist is flagged for JavaScript initialization with by adding setting styleClass="with-other"
.
Below is an example of a component which uses this setup.
<apex:component >
<!-- Attribute definitions -->
<apex:attribute name="value" type="String"
description="The value to write to"/>
<!-- UI components -->
<apex:outputPanel id="selectPanel">
<apex:selectList id="select" size="1" styleClass="with-other">
<apex:selectOption itemLabel="--Select One--" itemValue=""></apex:selectOption>
<apex:selectOption itemLabel="$10,000" itemValue="10000.00"></apex:selectOption>
<apex:selectOption itemLabel="$100,000" itemValue="100000.00"></apex:selectOption>
<apex:selectOption itemLabel="$1,000,000" itemValue="1000000.00"></apex:selectOption>
<apex:selectOption itemLabel="Other" itemValue="Other"></apex:selectOption>
</apex:selectList>
<apex:inputText id="selectText" />
<apex:inputHidden id="selectHidden" value="{!value}"/>
</apex:outputPanel>
</apex:component>
Below is a page containing a proof of concept, demonstrating the use of the picklist and the dependent text input.
<apex:page docType="html-5.0" standardController="Account">
<apex:includeScript value="{!URLFOR($Resource.jQueryScript)}"/>
<apex:pageMessages/>
<apex:form >
Account Name: <apex:inputField value="{!Account.Name}"/><br/>
Revenue: <c:selectListOther value="{!Account.AnnualRevenue}"/>
<apex:commandButton action="{!save}" value="Save"/>
</apex:form>
<script>
// Establish the Vfe namespace
var Vfe = Vfe || {};
Vfe.OTHER_OPTION_VALUE = 'Other';
Vfe.SELECT_HIDDEN_ID_SUFFIX = 'Hidden';
// The expected id attribute suffix for a
// free-form input field that is used when a user
// selects "Other" in a picklist
Vfe.SELECT_TEXT_ID_SUFFIX = 'Text';
Vfe.getSelectHiddenBySelectId = function(selectId) {
var selectHiddenId = selectId + Vfe.SELECT_HIDDEN_ID_SUFFIX;
return document.getElementById(selectHiddenId);
};
Vfe.getSelectTextBySelectId = function(selectId) {
var selectTextId = selectId + Vfe.SELECT_TEXT_ID_SUFFIX;
return document.getElementById(selectTextId);
};
// Define the utility function to set visibility of
// a related "Other" element, given the element and the
// condition
Vfe.showSelectOther = function(select, condition) {
var selectOther = Vfe.getSelectOther(select.id);
$(selectOther).toggle(condition);
};
// Initialize the select.with-other elements
$("select.with-other").each(function(index, element) {
// Get references to the text input and the hidden input
var selectText = Vfe.getSelectTextBySelectId(element.id);
var selectHidden = Vfe.getSelectHiddenBySelectId(element.id);
// Assume that the actual value is bound to the hidden input.
// Look to see whether the hidden input value matches
// anything in the select list.
var isSelectable = false;
for (var i = 0; i < element.options.length; i++) {
var option = element.options[i];
if (option.value == selectHidden.value)
isSelectable = true;
}
// If the bound value is selectable, mark that value as selected.
// Otherwise, assume the value was entered as an "Other".
if (isSelectable) {
element.value = selectHidden.value;
selectText.value = null;
} else {
element.value = Vfe.OTHER_OPTION_VALUE;
selectText.value = selectHidden.value;
}
// Bind event handlers to the picklist and the text input
// so that values are written appropriately to the hidden input
$(element).change(function(event) {
if (event.target.value == Vfe.OTHER_OPTION_VALUE) {
selectHidden.value = selectText.value;
$(selectText).show();
} else {
selectHidden.value = event.target.value;
$(selectText).hide();
}
});
$(selectText).change(function(event) {
selectHidden.value = event.target.value;
});
// Set the initial state for the text input
$(selectText).toggle(element.value == Vfe.OTHER_OPTION_VALUE);
});
</script>
</apex:page>
Utilize an <apex:actionSupport event="onchange" rerender="thisPanel"/>
inside your <apex:selectList>
. Then, have in <apex:inputText rendered="{!SelectListValue = 'Other'}"/>
also inside the rerendered panel so it would appear with the "Other" selection.
-
Thanks, Brad, I'm trying this now. By the way, do you mean
apex:actionSupport
and notapex:actionFunction
?– Marty C.Commented Dec 10, 2014 at 20:11 -
Yes! way ahead of me. At one point I was thinking of using the action function in conjunction with onChange... but actionSupport should do the trick.– BradByteCommented Dec 10, 2014 at 20:14
-
Actually, I don't think this will work in my case, because I'm trying to write to a Currency field on an object through my custom component. When I bind my Currency field to the component, the Visualforce expression used in the
rendered
attribute causes an error, "Incorrect parameter type for operator '='. Expected Number, received Text". Here's my component: pastie.org/9772945– Marty C.Commented Dec 10, 2014 at 20:20 -
Could you try the
<apex:input type="number"/>
? I think it would require html-5 though.– BradByteCommented Dec 10, 2014 at 20:25 -