In the previous part I created a ListPicker class that extends IValuePickerData and Serializable. The hasCapability() and getSourceLabels() methods that the IValuePickerData interface requires have been implemented and just return false. In addition to the default constructor public ListPicker(), I’ve created two more: public ListPicker(List<String>) and public ListPicker(Set<String>). Because I’ve just used the generic classes as parameters of the methods, List will take any type of list like an ArrayList and Set will take any kind of set like a TreeSet. Both methods store the collection passed into them into a private List variable called options_.

Now we’re ready to start on the two pivotal methods that do the work – readEntries() and loadEntries().

readEntries takes an IPickerOptions object as its parameter. These are the options passed in via the underlying AJAX call to the dataProvider. There are four properties used:

  • startKey: a full or partial value that is passed from the Value Picker’s search box, if search is enabled by setting dojoType=”extlib.dijit.PickerListSearch” on the Value Picker.
  • key: a full or partial value that is passed from an Edit Box that uses typeahead, where the typeahead is bound to the Value Picker.
  • start: the starting position in the collection. It doesn’t look like there’s any current functionality in the Value Picker to take advantage of this.
  • count: the number of entries to return. It looks like the Value Picker doesn’t support this property, but the Name Picker does by providing the maxRowCount dojoAttribute in the PickerName JavaScript Library, as Sean Cull blogged.

Looking at the functionality provided, it should be possible to extend the Value Picker or Name Picker to add paging which would pass back start and count options via an AJAX request. Those who saw my session at IBM Connect or the video on YouTube of the final demo from it will know that I’ve had some experience doing that. Though that’s beyond the scope of this tutorial.

So the code for the readEntries method is:

public IPickerResult readEntries(IPickerOptions options) {
String startKey = options.getStartKey();
String key = options.getKey();
int start = options.getStart();
int count = options.getCount();
int searchIndex = 0;
List opts = filteredOptions(key, startKey, start, searchIndex);
List entries = new ArrayList();
for (String opt : opts) {
entries.add(new SimplePickerResult.Entry(opt, opt));
}
return new SimplePickerResult(entries, -1);
}

So first we extract the options passed in. We then call a method filteredOptions(), passing in the various criteria to return a list of just the options we want to display. I’ll come back to that shortly, but initially it will be the same as the options_ variable we initialised the ListPicker with. We then crerate a new ArrayList, loop through the filtered options and create a SimplePickerResult.Entry object for each. Entry is a class within the SimplePickerResult class, and the constructor takes two parameters – a label and a value. For these collections the value and the label are the same, so both parameters are the same. Finally we return a new SimplePickerResult object that holds the values. In the examples I found in the Extension Library the second parameter is always -1, so that’s what is passed in here.

So now to the filteredOptions method. This just takes the options_ variable and filters it depending on those IPickerOptions that were passed in:

private List filteredOptions(String key, String startKey, int start, int searchIndex) {
List retVal = new ArrayList();
int first = -1;
if (StringUtil.isNotEmpty(key)) {
// We've got a typeahead key passed in, jump to first entry beginning with that key
for (int i = 0; i < options_.size(); i++) {
if (StringUtil.startsWithIgnoreCase(options_.get(i), key)) {
first = i;
break;
}
}
if (first >= 0) {
// And add entries that start with the key
for (int i = first; i < options_.size(); i++) {
if (StringUtil.startsWithIgnoreCase(options_.get(i), key)) {
retVal.add(options_.get(i));
}
}
}
} else if (StringUtil.isNotEmpty(startKey)) {
// We've got a search key passed in, jump to that entry
for (int i = 0; i < options_.size(); i++) {
if (options_.get(i).compareToIgnoreCase(startKey) >= 0) {
first = i;
break;
}
}
if (first >= 0) {
// And add all remaining entries
for (int i = first; i < options_.size(); i++) {
retVal.add(options_.get(i));
}
}
} else {
retVal.addAll(options_);
}
return retVal;
}

First we create a return variable retVal, an ArrayList to put our filtered options into. Then we have three checks: if key is not empty, is startKey is not empty, or if they’re both empty. The last option is what’s going to be true the first time the user clicks on the Value Picker icon – they haven’t initiated typeahead (key) or done a search in the Value Picker (startKey). In that case (right near the bottom) we just add all to our return variable.

The first if statement runs if key is not empty, so the user is typing into an Edit Box and triggering typeahead. In this case we only want to return any entries that match exactly the letters typed in. If the letters are found, we set first to that entry’s index. If first is greater than or equal to zero (it’s been initialised to -1), we then add that and any subsequent entries starting with the letters typed in by the user.

The second if statement run if startKey is not empty, so the user is entering a search into the Value Picker dialog itself. First it compares lexicographically each option to the startKey, that means that in a dictionary and ignoring case sensitivity, does it come before (less than zero), at the same place (zero) or after (greater than zero). So we set first to the entry that matches the search criteria, begins with the letters typed, or is the closest after the letters typed. We then get all subsequent entries.

Let me just give an example for the startKey scenario. Say we have a List with entries “apple”, “banana”, “grape” and “plum”. When the Value Picker is first shown, the user sees all four. If the user types “grape” or “GRAPE” or “Grape” in the search box and searches, they get a list comprising “grape” and “plum”. If the user types “ba” and searches, they get “banana”, “grape” and “plum” because banana comes after ba in a dictionary. The fact that banana starts with the letters the user is searching on is irrelevant. Equally if they type “nectarine” and search, they get “plum”, because nectarine is not in the list and plum is the next entry alphabetically that is in the list.

So the readEntries() method gets entries for the user to select from. Now onto the loadEntries() method. This is used when the Value Picker dialog is launched to identify which options in the dialog have already been selected. That’s so they can be highlighted. Here is the code we use:

public List loadEntries(Object[] values, String[] attributes) {
List entries = new ArrayList();
if (null != values) {
for (int i = 0; i < values.length; i++) {
String checkStr = values[i].toString();
if (StringUtil.isNotEmpty(checkStr)) {
for (String option : options_) {
if (checkStr.equals(option)) {
break;
}
}
entries.add(new SimplePickerResult.Entry(checkStr, checkStr));
}
}
}
return entries;
}

This takes two parameters, an array of objects called values and a String array attributes. Attributes is not used in the examples I’ve seen and not used here. The array of objects is the one or more elements from the list that were previously selected. If it’s null, we don’t have to do anything, because nothing has ever been selected before. Otherwise, for each entry in the values array we get it as a String, then loop through the options_ List and try to find each previously selected value. If we can, we add it to the entries ArrayList as a new SimplePickerResult.Entry. As before, the label and value are the same, so both parameters for the SimplePickerResult.Entry are the same.

Put this all together and we have a generic class that can be used as a dataProvider for any List or Set for a Value Picker on any page of the database. But how do we call it. First we need the List or Set. That could be provided in a number of ways. Explicitly in the code for the dataProvider; in a viewScope variable; in a dataContext; or in an xe:objectData datasource. We then just need to compute the dataProvider property of the Value Picker, creating a new ListPicker object and passing in the List or Set using the variable name from the viewScope variable, dataContext or datasource, like so:

${javascript:new org.openntf.pickerdata.ListPicker(myValuesSet)}

Now we have a fully working dataProvider. You can find the full code on XSnippets.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.