List box typeahead: Intuitive list searching

by guest author Chris Belanger, a 4D developer from Canada

List box typeahead (a method for progressively searching) is not a native feature of list boxes. However, you can easily implement this feature with some creative coding. In this blog post, I’ll demonstrate the technique along with a database example and an exhaustive document explaining the details step by step.

The final result is summarized in the GIF below, It illustrates two “searches”. One for E-L-L-I-O-T, then another (after a column sort) for H-A-N-N-A-H:

HDI: Listbox typeahead

What do we want to do?

When the list box has focus (but not in “input mode”), we want to be able to type some characters and have the list box scroll to and select the closest match according to the “sort column”. For example, if it’s sorted by company name, we want to start to type a company’s name and have it located and highlighted in the list box. However, when a list box is the focused object, it cannot respond to keystrokes. 

So how can we solve this problem since there doesn’t appear to be a way to collect the keystrokes? 

   Place the List box in a subform

The first step is to put the list box into a subform (in the sample database, this form is called LB). Ok … but how do we collect the keystrokes? By using invisible buttons with “shortcuts” assigned to them.

In turn, all the shortcuts call a method to determine the search text, locate it in the list box, and highlight it for the user. You can follow this explanation if you open the LB form in the Listbox Clairvoyance database.

The list box has the object name, LB_Browser. Its properties include:

blank

The entity selection is called Form.es (within the context of the LB Subform).

At the bottom of the LB form, there’s a collection of buttons. Each has a shortcut key assigned to it. The bottom row may seem unclear, but they handle the SPACE, -, and _ keys. You can add more if needed.

blank

The properties for each of these buttons are illustrated by using the “A” key as an example.

The Object Name ends in the LETTER, NUMBER, or SYMBOL it generates. SPACE is an exception; it uses the ␣ symbol and is “translated” to SPACE.

Title is only for clarification, Horizontal Sizing is Move, and the shortcut is the keystroke. The only event that should be checked is On Clicked.

When a user presses a shortcut key, the script for that button is triggered with an On Clicked Form event code.

blank

The script for each button is:

LB_SEARCH_DO (Form; OBJECT Get name)

   Interpreting the keystroke

The  LB_SEARCH_DO project method processes the keystrokes. It uses the final character of the Object Name to know the keystroke to process.

$char:=$2[[Length($2)]]  // get the last character in the object’s name.

This character will either be appended to the search string or replace the search string, depending on the length of time since the last keystroke. In the sample database, any “wait” longer than 1.5 seconds will start a new search.

$now:=Milliseconds
If (($now-$obj_Search.lastMillisecond)>1500)  //allow up to 1.50 seconds between keystrokes.       
 $obj_Search.searchFor:=$char  //reset .searchFor
Else
 $obj_Search.searchFor:=$obj_Search.searchFor+$char  //add to .searchFor
End if
$obj_Search.lastMillisecond:=$now  

The search on the “search column” is performed by determining which record in the entity selection would be the first match, and then locating this record in the entity selection (“.es“).

C_OBJECT($es_GreaterEqual// the entity selection of the >= group that match the searchFor string.
$es_GreaterEqual:=$1.es.query($attrName+" >= :1";$obj_Search.searchFor).orderBy($attrName+" ASC")

The $itemPosition within the entity selection is determined with:

If ($es_GreaterEqual.length>0)  // if there was at least one match
 $match:=$es_GreaterEqual[0]  // $match = the FIRST RECORD in the collection 
 $itemPosition:=$es_GreaterEqual[0].indexOf($1.es)+1  // location of the matching entity within the selection
Else
 $itemPosition:=$1.es.length
End if

Now update the list box row selection and scroll so that the user can see the result. We show it on line 5 in the list box to give some context.

C_LONGINT($show// used to cause the ITEM POSITION to show in the 5th line (or higher, if it is < 5).
LISTBOX SELECT ROW(*;$lbName;$itemPosition;lk replace selection)
$show:=Choose($itemPosition<4;1;$itemPosition-4) // it will be line 5, or closer to the top
OBJECT SET SCROLL POSITION(*;$lbName;$show;*)

Lastly, for the sake of the demo, we load the selected entity into an object that the parent form can access.

Form.en_edit:=Form.es[$itemPosition-1]  // for the parent form’s use

And more

Download the HDI above to get a closer look at the concept. Additionally, an exhaustive document is included explaining the configuration of the parent form to operate the list box, as well as tips to design the user interface and enhance the user experience.

I hope this helps with your next 4D project!

 

Chris Belanger
Chris Belanger is a 4D developer who started writing 4D solutions in about 1991 — starting with 4D v2.0 — while residing in Yellowknife, NWT, Canada. He wrote several systems for the Territorial Government, including one to manage projects and a Terminology Database that correlated terminology in eight languages. Chris has also written 4D databases to manage the operations of a wholesale company, a cleaning, and restoration company, and several oilfield services companies.