Mimic 'Allow Values Not In List' Functionality
When choosing a value from a keyword list, in traditional Notes development there is a feature called "allow values not in list" where you can have your end users add a new value instead of picking strictly from the list. Here's a screen shot of that setting in Designer: And here's what the options look like in the Notes client:
Creating this same functionality in XPages is not straightforward. One option is to enable type ahead on your field. Users can start typing the option to "choose" the option from the list of choices, but they can also override and type whatever they want. This requires the users to know the options before hand. I didn't like that option. My solution is a bit more complicated, but gets the job done.
Step 1: Add a Hidden Input Control to your XPage.
The hidden input control will be the tie-in to the back-end document. This will be the value that is ultimately stored when the XPage is saved. If you want to make the field required, add your validation to the hidden input control.
<xp:inputHidden id="kwFruitHidden" value="#{document1.kwFruit}" required="true">
<xp:this.validators>
<xp:validateRequired message="You must choose a fruit from the list of type in a new option.">
</xp:validateRequired>
</xp:this.validators>
</xp:inputHidden>
Step 2: Add a division for users who want to choose an option.
This HTML DIV tag will be the default for new documents. It will be shown if the user wants to select the option from the list of options.
<div id="kwFruitComboDiv" style="display:block;">
</div>
Step 3: Add a Combo Box Control to your XPage, inside the "kwFruitComboDiv" division.
This is the control that the user selects their option, if they want one of the standard options. You can choose to have the options come from a keyword profile, or some other formula, or hard-code the options like I did here. It doesn't matter. The important thing is that the Combo Box is inside the division added in Step 2 and gives the user the "standard" options. It is also VERY IMPORTANT that the combo box is NOT tied to any back-end data source. The value here is not actually stored anywhere - the hidden input field is what is ultimately stored.
<xp:comboBox id="kwFruitCombo">
<xp:selectItem itemLabel="Orange"></xp:selectItem>
<xp:selectItem itemLabel="Apple"></xp:selectItem>
<xp:selectItem itemLabel="Strawberry"></xp:selectItem>
<xp:selectItem itemLabel="Grape"></xp:selectItem>
<xp:selectItem itemLabel="Kiwi"></xp:selectItem>
</xp:comboBox>
Step 4: Capture any changes to the Combo Box.
When the user chooses a different option from the Combo Box, the new value needs to be stored in the hidden input field, so it can ultimately be saved to the server. This is done through an Event Handler in the Combo Box. The event is onChange, and it is a client event.
Here's the actual code:
var comboBox = document.getElementById("#{id:kwFruitCombo}");
var hiddenEdit = document.getElementById("#{id:kwFruitHidden}");
var v = comboBox[comboBox.selectedIndex].value;
hiddenEdit.value = v;
Step 5: Add a link to allow the user to switch from "choosing an option" to "typing a new option".
To be able to allow the user to enter a value not in the list, they need an input field to do this. The input field will be in a second HTML division (not created yet). But we need a way to allow the user to switch to that division.
You can use whatever link you want, but I end up using a small image. Since I'm no good at creating images, I just use one of the action button images that comes with Notes. I import action button #30 (actn030.gif) as an image resource, and then I reference that in my XPage. The text of the link is removed so only the image shows.
<xp:link escape="true" id="enterValueNotInList" title="Click to add value not in list">
<xp:image id="image1" url="/actn030.gif"></xp:image>
</xp:link>
Step 6: Add an event handler to the link from Step 5.
When the link is clicked, the process is to hide the Combo Box division and show the Edit Box division (not yet created). In addition, the value that shown in the Edit Box should be put into the Hidden Input field. The expectation is that what the end user sees on the screen is what will be stored on disk. So if they click the link and the Edit Box has a value of "blah" in it, they would expect "blah" to be stored on disk. The event is onClick for the link, and is client-side JavaScript again. Here's the code:
var visibleEdit = document.getElementById("#{id:kwFruitEdit}");
var v = visibleEdit.value;
var hiddenEdit = document.getElementById("#{id:kwFruitHidden}");
hiddenEdit.value = v;
var combo = document.getElementById("kwFruitComboDiv");
combo.style.display="none";
var edit = document.getElementById("kwFruitEditDiv");
edit.style.display="block";
Step 7: Add a division for users who want to type in a new option.
The combo box is the default for new documents, so this HTML DIV is going to be hidden by default. (I'll describe what happens upon editing an existing document later). The DIV will be shown when a user clicks on the link where they want to enter a new value.
<div id="kwFruitEditDiv" style="display:none;">
</div>
Step 8: Add an Edit Box Control to your XPage, inside the "kwFruitEditDiv" division.
This is the control where the user types in a new keyword choice. You don't want any validation on this field (if you want validation, put it on the Hidden Input field). The reason you don't want validation is because this field might be blank if the user selected one of the standard options. Again, this value should NOT be tied to any back-end data source - the Hidden Input is what is saved to disk. This goes inside the division created in step 7.
<xp:inputText id="kwFruitEdit" size="55">
</xp:inputText>
Step 9: Capture any changes to the Edit Box.
As with the Combo Box, we need to save the value from the Edit Box into the Hidden Input field. These changes are captured on the onBlur event for the Edit Box. It's again a client-side JavaScript function. Here's the XPages source:
<xp:eventHandler event="onclick" submit="false">
<xp:this.script>
<![CDATA[var visibleEdit = document.getElementById("#{id:kwFruitEdit}");
var v = visibleEdit.value;
var hiddenEdit = document.getElementById("#{id:kwFruitHidden}");
hiddenEdit.value = v;
var combo = document.getElementById("kwFruitComboDiv");
combo.style.display="none";
var edit = document.getElementById("kwFruitEditDiv");
edit.style.display="block";]]>
</xp:this.script>
</xp:eventHandler>
Step 10: Add a link to allow the user to switch from adding a new value to going back to the Combo Box.
If the user decided they didn't want to enter a new value (or, they clicked the first link accidentally), they need to go back to the Combo Box. I imported action icon #43 (actn043.gif) as the image.
<xp:link escape="true" id="chooseValueNotInList" title="Click to select value from list">
<xp:image id="image2" url="/actn043.gif"></xp:image>
</xp:link>
Step 11: Add an event handler to the link from step 10.
This link is going to hide the Edit Box division, show the Combo Box division, and take the value from the Combo Box and store it in the hidden input. Again, what the user sees is what should be stored on the back-end document. The event is onClick for the link, and is client-side JavaScript again. Here's the code:
var comboBox = document.getElementById("#{id:kwFruitCombo}");
var v = comboBox[comboBox.selectedIndex].value;
var hiddenEdit = document.getElementById("#{id:kwFruitHidden}");
hiddenEdit.value = v;
var edit = document.getElementById("kwFruitEditDiv");
edit.style.display="none";
var combo = document.getElementById("kwFruitComboDiv");
combo.style.display="block";
Step 12: Show the appropriate division and set the appropriate value when editing an existing document.
All the code above is appropriate both to editing an existing document and creating a new document. There's one additional thing needed when editing an existing document. The hidden input field is the one tied to the back-end document. The combo box and the edit box are not tied to any back-end data source, so they need to be set. The logic for this is to choose the appropriate keyword if one of the keywords is what's on disk. If what's on disk isn't a keyword, then hide the combo box division and show the edit box division with the value in the edit box.
This code in client-side JavaScript stored in the onClientLoad event for the XPage
Here's the actual code:
var comboBox = document.getElementById("#{id:kwFruitCombo}");
var hiddenEdit = document.getElementById("#{id:kwFruitHidden}");
var foundIt = false;
if ((comboBox) && (hiddenEdit)) {
for (var i=0; i
comboBox.selectedIndex = i;
foundIt = true;
}
}
}
var visibleEdit = document.getElementById("#{id:kwFruitEdit}");
if ((!foundIt) && (visibleEdit)) {
visibleEdit.value = hiddenEdit.value;
var comboDiv = document.getElementById("kwFruitComboDiv");
comboDiv.style.display="none";
var editDiv = document.getElementById("kwFruitEditDiv");
editDiv.style.display="block";
}
That should be it. 12 steps is a lot, but it's pretty easy to implement.