Separate Data from UI with Model

You can separate the underlying data structure containing the data from the UI that displays the data. It is generally suggested, since you can construct and even change complex UI easier.

To do so, you first implement a model and then assign the model to a view that supports it. For example, to display a list of options, you have to implement ListModel. Then, you can assign it to a view that supports ListModel, such as DropDownList depending on your need.

Use Default Implementation

Though ListModel is simple, you can use the default implementation, DefaultListModel directly. For example,

new DropDownList(model: new DefaultListModel(["apple", "orange", "lemon", "juice"]));


Some views allow the user to select one or multiple data in the list. To work with them, your implementation of ListModel shall also implement SelectionModel too. Alternatively, you can use DefaultListModel since it implements SelectionModel. For example,

final DefaultListModel<String> model
  = new DefaultListModel(["apple", "orange", "lemon", "juice"]);
model.addToSelection("orange"); //select one of them
model.addToDisables("juice"); //you can disable some items
model.on.select.add((event) {
  //do something when the user selects an item
new DropDownList(model: model);

As shown, DefaultListModel also implements DisablesModel, so you can disable items that the user can't select.

Sharing the Selection

You can have two or more views to share the same model.

final dd1 = new DropDownList(model: model);
final dd2 = new DropDownList(model: model);

Since the model is shared, the selection is shared too. If the user selects an item in one of views, all other views will reflect it.

Not to Share the Selection

If you prefer to share the data but not to share the selection, you can implement a list model that maintains the selection but delegate ListModel.operator[ ] to another list model holding the data.

class ProxyListModel<E> extends AbstractListModel<E> {
  final ListModel<E> origin;
  PorxyListModel(ListModel<E> this.origin);

  E operation[](int index) => origin[index];
  int get length => origin.length;

Then, you can split the selection as you want it.

new DropDownModel(model: model);
new DropDownModel(model: new ProxyListModel(model)); //shared data but not selection

Data Altering

If you have to change the data after assigning to UI, you have to invoke one of the alerting method in DefaultListModel, such as DefaultListModel.operator[ ]= and DefaultListModel.add(). For example,

model.add("flower"); //UI will be updated automatically to show "flower"
model.addToSelection("flower"); //UI will be updated to select "flower"

You shall not modify the original passed to the list model's constructor. Otherwise, UI won't be changed to reflect the data.


You can implement TreeModel to represent a hierarchy data structure. Similarly, there is a default implementation called DefaultTreeModel, which assumes each node is an instance of TreeNode. Here is an example:

DefaultTreeModel<String> model = new DefaultTreeModel(nodes: [
  new DefaultTreeNode("Australia",
    ["Sydney", "Melbourne", "Port Hedland"]),
  new DefaultTreeNode("New Zealand",
    ["Cromwell", "Queenstown"])]);
new DropDownList(model: model); //DropDownList supports TreeModel too

DefaultTreeModel also implements SelectionModel, so you can handle as well.

model.on.select.add((event) {
  //do something when some of items is selected

Notice that the selected item in DefaultTreeModel is an instance of TreeNode. So you have to use the instance of TreeNode to select it manually if you want. For example, the following select the second child's third child.



By default, views that support model will convert the data to a string and display it. If you want to customize it, you have to implement a renderer to generate the content to display. For example, to customize the renderer of DropDownList, you can implement Renderer and then assign an instance to DropDownList as follows:

new DropDownList(model: model,
  renderer: (RenderContext context) => "..${context.data}...");