List Model
Listbox and Grid allow developers to separate the view and the model by implementing ListModel. Once the model is assigned (with Listbox.setModel(ListModel)), the display of the listbox is controlled by the model, and an optional renderer. The model is used to provide data, while the renderer is used to provide the custom look. By default, the data is shown as a single-column grid/listbox. If it is not what you want, please refer to the View section for writing a custom renderer.
Model-driven Display
As shown, the listbox retrieves elements from the specified model[1], and then invokes the renderer, if specified, to compose the listitem for the element.
The retrieval of elements is done by invoking ListModel.getSize() and ListModel.getElementAt(int).
The listbox will register itself as a data listener to the list model by invoking ListModel.addListDataListener(ListDataListener). Thus, if the list model is not mutable, the implementation has to notify all the registered data listeners. It is generally suggested to extend from AbstractListModel, or use any of the default implementations, which provide a set of utilities for handling data listeners transparently. We will talk about it later in #Notify for Data Updates.
- ↑ The listbox is smart enough to read the elements that are visible at the client, such the elements for the active page. It is called Live Data or Render on Demand'.'
Small Amount of Data
If your data can be represented as a list, map, set or array (java.util.List, java.util.Map, etc.), you could use one of the default implementations, such as ListModelList, ListModelMap, ListModelSet and ListModelArray. For example,
void setModel(List data) {
listbox.setModel(new ListModelList(data));
}
If the amount of your data is small, you could load them all into a list, map, set or array. Then, you could use one of the default implementations as described above.
Alternatively, you could load all data when ListModel.getSize() is called. For example,
public class FooModel extends AbstractListModel {
private List _data;
public int getSize() {
//load all data into _data
return _data.size();
}
public Object getElementAt(int index) {
return _data.get(index);
}
}
Huge Amount of Data
If the data amount is huge, it is not a good idea to load them all at once. Rather, you shall load only the required subset. On the other hand, it is generally not a good idea to load single element when ListModel.getElementAt(int) is called, since the overhead of loading from the database is significant.
Thus, it is suggested to use SQL LIMIT or similar feature to load only a subset of the data. For example, if the total number of visible elements is about 30, you could load 30 (or more, say 60, depending on performance or memory is more important to you) if an element is not loaded, and discard the previous loaded data, if any. If the next invocation of ListModel.getElementAt(int) is in the subset, we could return it immediately. Here is the pseudo code:
public class FooModel extends AbstractListModel {
public List _subset;
public int _startAt;
public Object getElementAt(int index) {
if (index >= _startAt && _subset != null && index - _startAt < _subset.size())
return _subset.get(index - _startAt); //cache hit
_subset = new LinkedList(); //drop _subset, and load a subset of data, say, 60, to _subset
...
For more realist example, please refer to Small Talks: Handling huge data using ZK.
Notify for Data Updates
If the data in the model is changed, the implementation must notify all data listeners that are registered by ListModel.addListDataListener(ListDataListener). It can be done by invoking AbstractListModel.fireEvent(int, int, int) if your implementation is extended from AbstractListModel or derived.
Notice that if you use one of the default implementations, such as ListModelList, you don't need to worry about it. The notification is handled transparently.
For example, (pseudo code)
public void removeRange(int fromIndex, int toIndex) {
removeElements(fromIndex, toIndex); //remove elements from fromIndex (inclusive) to toIndex (exclusive)
fireEvent(ListDataEvent.INTERVAL_REMOVED, fromIndex, index - 1);
}
public void add(int index, Object element){
addElements(index, element); //add an element at index
fireEvent(ListDataEvent.INTERVAL_ADDED, index, index);
}
public void set(int index, Object element) {
setElement(index, element); //change the element at index
fireEvent(ListDataEvent.CONTENTS_CHANGED, index, index);
}
Once a model is assigned to a component, the component will register itself as a data listener such that any change can be updated to UI.
Notice that you shall not update the component (such as listbox) directly. Rather, you shall update to the modal and then the model shall fire the event to notify the component to update accordingly.
Selection
If your data model also provides the collection of the seletected elements, you shall also implement Selectable. When using with a component supporting the selection (such as Listbox), the component will invoke Selectable.getSelection() to display the selected elements correctly. Then, if the end user selects or deselects an element, Selectable.addSelection(Object) and Selectable.removeSelection(Object) will be called by the component to notify the model that the selection is changed. Then, you might update the selection into the persistent layer (such as database).
If the model (or your application rather than the user) wants to change the selection at run time, it has to fire the event, such as ListDataEvent.CONTENTS_CHANGED as described above. It will cause the component to reload the data (ListModel.getElementAt(int)) and check if it is selected (Selectable.getSelection()).
All default implementations, including AbstractListModel, implement Selectable. However, notice that the implementations of AbstractListModel.addSelection(Object) and AbstractListModel.removeSelection(Object) do not fire any event, since they are designed to be called by the component. Thus, if you want to invoke them directly (i.e., you want to change the selection from the model directly), you have to invoke AbstractListModel.fireEvent(int, int, int) explicitly.
In additions, the default implementations don't store the data to anywhere. If you want to store them, you have to override Selectable.addSelection(Object) and Selectable.removeSelection(Object), and store the selection when any of them is invoked.
Sorting
To support the sorting, the model must implement ListModelExt too. Thus, when the end user clicks the header to request the sorting, ListModelExt.sort(Comparator, boolean) will be called.
For example, (pseudo code)
public class FooModel extends AbstractListModel implements ListModelExt {
public void sort(Comparator cmpr, final boolean ascending) {
sortData(cmpr); //sort your data here
fireEvent(ListDataEvent.CONTENTS_CHANGED, -1, -1); //ask component to reload all
}
...
Notice that the ascending parameter is used only for reference, and you usually don't need it, since the cmpr is already a comparator capable to sort in the order specified in the ascending parameter.
Version History
Version | Date | Content |
---|---|---|