Saturday, June 7, 2008

Lightweight Text Input in LWUIT (TextField) & AutoComplete

TextField has landed! LWUIT now features a real lightweight text field component that can accept arbitrary input and can be configured for additional languages/features.
This is the first version so bugs are to be expected but the basics seem to be working and the ability to "jump" into the native text box allow us to support everything including T9.

What is the use case for TextField and why should you care?

First: for username/password sort of input fields this is far more intuitive than the alternative of jumping to the native text box...

Second: You can listen to change events and do "cool stuff"!

What sort of stuff?

Hows about an address book where typing in the name of the recipient narrows down the list as you type?

As you can see in the pictures bellow typing c followed by k (its really typing 2 three times and 5 twice which caused the list to "jump" a bit) narrowed down the list to the only two names with "ck" in their names (case insensitive). All of that while the fish keeps swimming ;-)


This functionality requires a few very cool LWUIT tricks including both text field manipulation and a proxy filtering model. To narrow down the list as seen in the pictures we install a special list model that can reduce the element count on the fly, this is one way of pulling this trick off without too much effort!

This is the code for the text field and as you can see its trivial:
Form test = new Form("Address Book");
test.setLayout(new BorderLayout());
final TextField field = new TextField();
final ListModel underlyingModel = new DefaultListModel(new String[] {"Jack", "Kate", "Sawyer",
"Sayid", "Hurley", "Jin", "Sun", "Charlie", "Claire", "Aaron",
"Michael", "Walt", "Boone", "Shannon", "Locke", "Mr. Eko",
"Ana-Lucia", "Libby", "Desmond", "Benjamin Linus", "Juliet Burke" });

// this is a list model that can narrow down the underlying model
final FilterProxyListModel proxyModel = new FilterProxyListModel(underlyingModel);

List people = new List(proxyModel);
test.addComponent(BorderLayout.NORTH, field);
test.addComponent(BorderLayout.CENTER, people);
field.addDataChangeListener(new DataChangedListener() {
public void dataChanged(int type, int index) {
proxyModel.filter(field.getText());
}
});

test.show();
The filter proxy essentially implements the proxy pattern (or decorator if you are the type whose finicky about patterns) and exposes the list items that fall into the defined constraint, notice that it delegates all the heavy lifting to the underlying model.
This code really highlights the importance and power of MVC in the long run... For a small application MVC might cost in size, but once we start building large applications the ability to reuse programming concepts actually reduces code size and development time making MVC really appropriate for embedded devices!
class FilterProxyListModel implements ListModel, DataChangedListener {
private ListModel underlying;
private Vector filter;
private Vector listeners = new Vector();
public FilterProxyListModel(ListModel underlying) {
this.underlying = underlying;
underlying.addDataChangedListener(this);
}

private int getFilterOffset(int index) {
if(filter == null) {
return index;
}
if(filter.size() > index) {
return ((Integer)filter.elementAt(index)).intValue();
}
return -1;
}

private int getUnderlyingOffset(int index) {
if(filter == null) {
return index;
}
return filter.indexOf(new Integer(index));
}

public void filter(String str) {
filter = new Vector();
str = str.toUpperCase();
for(int iter = 0 ; iter < underlying.getSize() ; iter++) {
String element = (String)underlying.getItemAt(iter);
if(element.toUpperCase().indexOf(str) > -1) {
filter.addElement(new Integer(iter));
}
}
dataChanged(DataChangedListener.CHANGED, -1);
}

public Object getItemAt(int index) {
return underlying.getItemAt(getFilterOffset(index));
}

public int getSize() {
if(filter == null) {
return underlying.getSize();
}
return filter.size();
}

public int getSelectedIndex() {
return Math.max(0, getUnderlyingOffset(underlying.getSelectedIndex()));
}

public void setSelectedIndex(int index) {
underlying.setSelectedIndex(getFilterOffset(index));
}

public void addDataChangedListener(DataChangedListener l) {
listeners.addElement(l);
}

public void removeDataChangedListener(DataChangedListener l) {
listeners.removeElement(l);
}

public void addSelectionListener(SelectionListener l) {
underlying.addSelectionListener(l);
}

public void removeSelectionListener(SelectionListener l) {
underlying.removeSelectionListener(l);
}

public void addItem(Object item) {
underlying.addItem(item);
}

public void removeItem(int index) {
underlying.removeItem(index);
}

public void dataChanged(int type, int index) {
if(index > -1) {
index = getUnderlyingOffset(index);
if(index < 0) {
return;
}
}
for(int iter = 0 ; iter < listeners.size() ; iter++) {

((DataChangedListener)listeners.elementAt(iter)).dataChanged(type, index);
}
}
}

0 comments:

Post a Comment