SPRING-SOURCE.RU

Использование пользовательских событий в GWT 2

Для того чтобы понимать как строятся пользовательские события мы будем использовать стиль Model-Binder-View.

Model (модель)

Модель - это то место, где мы сосредоточим основные свои усилия, так как это компонент, который генерирует пользовательские события. Модель будет выглядеть следующим образом:

      
public class ListModel {
 final private List<ListItem> items = new ArrayList<ListItem>();

 public void addItem(ListItem item) {
  items.add(item);
 }
 
 public void removeItem(ListItem item) {
  items.remove(item);
 }
 
 public void removeAll() {
  items.clear();
 }
 
 public List getItems() {
  return Collections.unmodifiableList(items);
 }
}
		

JAVA совет: Заметьте, что, когда мы возвращаем лист предметов, мы обертываем его в UnmodifiableList. Это делает лист неизменным и таким образом останавливает любого клиента изменять состояние модели.

Класс ListItem выглядит так:

      
public class ListItem {
 private final String text;
  
 public ListItem(String text) {
  this.text = text;
 }
 
 public String getText() {
  return text;
 }
  
        // Equals and HashCode hidden
}
		

В этом примере, ListItem - это простая обертка для текста. Мы могли бы использовать строку для ListItem, но этот метод позволяет пользователю модели расширять ListItem для включения любой дополнительной информации (например, добавить map значений (map of values)).

Добавление HandlerManager

К этому моменту наша модель не особо реагирует на какие-либо действия, клиент должен будет опрашивать модель для того чтобы увидеть изменения. Мы сделаем так, чтобы при добавлении предмета выбрасывалось событие. Добавим HandlerManager в наш ListModel:

      
public class ListModel {
 final private HandlerManager handlerManager = new HandlerManager(this);
 final private List items = new ArrayList();
 
 // Other code hidden
} 
		

Заметьте, что HandlerManager берет в конструктор, в качестве параметра, объект. Объект используется, как источник всех событий, которые HandlerManager будет выбрасывать.

Событие ItemAdded

События в GWT 2 состоят из двух частей, из класса расширяющего GWTEvent (объект-событие) и интерфейса, расширяющего EventHandler (интерфейс-обработчик). Интерфейс обработчика события самый простой для понимания - это просто интерфейс-маркер. EventHandler для ItemAdded выглядит так:

      
public interface ItemAddedHandler extends EventHandler {

 void onItemAdded(ItemAddedEvent event);
} 
		

Класс ItemAddedEvent выглядит так:

      
public class ItemAddedEvent extends GwtEvent<ItemAddedHandler> {
 private static final Type TYPE = new Type<ItemAddedHandler>();
 
 private final ListItem listItem;
 
 public ItemAddedEvent(ListItem listItem) {
  this.listItem = listItem;
 }
 
 public static Type getType() {
  return TYPE;
 }
 
 /** @returns The item added to the model */
 public ListItem getListItem() {
  return listItem;
 }
 
 @Override
 protected void dispatch(ItemAddedHandler handler) {
  handler.onItemAdded(this);
 }

 @Override
 public com.google.gwt.event.shared.GwtEvent.Type getAssociatedType() {
  return TYPE;
 }
}
		

Объект-событие немного запутанный потому что он выполняет две роли. С одной стороны это объект, который может получать ListItem при уведомлении. С другой стороны объект-событие выступает как диспетчер для данного события.

Выбрасывание события

Когда предмет добавляется в нашу модель, мы можем выбрасывать событие. Выглядит это следующим образом:

      
public void addItem(ListItem item) {
  items.add(item);
  handlerManager.fireEvent(new ItemAddedEvent(item));
 }
		

Когда fireEvent вызывается в HandlerManager, первым вызывается setSource объекта-события и устанавливается в значение переданное в конструктор HandlerManager. Затем он будет искать обработчики, зарегистрированных в отношении данного события и вызовет их в том порядке, в котором они зарегистрированы.

Регистрируем EventHandler

В данный момент клиент не может слушать событие ItemAdded. Для того, чтобы это сделать, мы должны добавить addItemAddedHandler в наш класс ListModel:

      
public void addItemAddedHandler(ItemAddedHandler handler) {
      handlerManager.addHandler(ItemAddedEvent.getType(),handler);  
}
		

Полная модель

      
public class ListModel {
 private final HandlerManager handlerManager = new HandlerManager(this);
 private final List items = new ArrayList<ListItem>();

 public void addItem(ListItem item) {
  items.add(item);
  handlerManager.fireEvent(new ItemAddedEvent(item));
 }
 
 public void removeItem(ListItem item) {
  items.remove(item);
  handlerManager.fireEvent(new ItemRemovedEvent(item));
 }
 
 public void removeAll() {
  for (ListItem item : items) {
   handlerManager.fireEvent(new ItemRemovedEvent(item));  
  }
  
  items.clear();
 }
 
 public List getItems() {
  return Collections.unmodifiableList(items);
 }
 
 public void addItemAddedHandler(ItemAddedHandler handler) {
  handlerManager.addHandler(ItemAddedEvent.getType(),handler);  
 }
 
 public void addItemRemovedHandler(ItemRemovedHandler handler) {
  handlerManager.addHandler(ItemRemovedEvent.getType(),handler);
 }
}
		

List компонент

Компонент-список очень простой, он является оберткой либо вокруг LI либо OL элемента:

      
public class ListComponent extends Widget {
 private static final String IDENTIFIER_PROPERTY = "IDENTIFIER"; 
 
 public enum ListType {
  ORDERED {
   protected Element createElement() {
    return Document.get().createOLElement();
   }
  },  
  UNORDERED {
   protected Element createElement() {
    return Document.get().createULElement();
   }
  };
  
  protected abstract Element createElement();
 }

 
 public ListComponent(ListType type) {
  setElement(type.createElement());
 }
 
 public void addItem(String label, Object identifier) {
  LIElement liElement = Document.get().createLIElement();
  liElement.setInnerText(label);
  liElement.setPropertyObject(IDENTIFIER_PROPERTY, identifier);
  
  getElement().appendChild(liElement);
 }
 
 public void removeItem(Object identifier) {
  Element childNode = getElement().getFirstChildElement();
  
  while (childNode != null) {
   if (identifier.equals(childNode.getPropertyObject(IDENTIFIER_PROPERTY))) {
    childNode.removeFromParent();
    break;
   }
   
   childNode = childNode.getNextSiblingElement();
  }
 }
}
		

Как вы можете видеть, предмет состоит из лейбла и идентификатора объекта. Идентификатор хранится как свойство Object в новом LI элементе, поэтому при удалении мы можем найти данный элемент с конкретным идентификатором.

Binding. Связывание модели с UI компонентом

C ListModel мы закончили, теперь нам нужно что-то, что будет регистрироваться на события и изменить наш UI компонент, когда что-то добавляется. Для этих целей мы создадим binder, который будет обновлять UI список. Это выглядит так:

      
public class ListBinder {
 public ListBinder(final ListComponent list, ListModel listModel) {

  listModel.addItemAddedHandler(new ItemAddedHandler() {
   public void onItemAdded(ItemAddedEvent event) {
    ListItem listItem = event.getListItem();
    list.addItem(listItem.getLabel(), listItem);
   }
  });

  listModel.addItemRemovedHandler(new ItemRemovedHandler() {
   public void onItemRemoved(ItemRemovedEvent event) {
    list.removeItem(event.getListItem());
   }
  });
 }
}
		

Binder очень прост, он конвертирует события вызванные моделью в UI действия. Это нужно для разделения программы на части.

Быстрый тест

В конце мы протестируем наше приложение. Для этого нужно создать простого тестового клиента, который добавляет список предметов каждую секунду:

      
public class TestClient implements EntryPoint {

 public void onModuleLoad() {
  final ListModel model = new ListModel();
  final ListComponent list = new ListComponent(ListType.ORDERED);
  
  ListBinder binder = new ListBinder(list, model);
  
  Timer t = new Timer() {
   private int counter = 0;
   public void run() {
    model.addItem(new ListItem("New Item "+counter++));
   }
  };

  t.scheduleRepeating(1000);
  
  RootPanel.get().add(list);
 }
}
		

О том как работает вся эта схема

  1. Создаем Binder
  2. В Binder кладем list и model (не заполненные)
  3. При создании экземпляра Binder выполняется метод в model addItemAddedHandler
  4. В предыдущий метод подаем itemAddedHandler (создаем экземпляр)
  5. В модели выполняется добавление Handler

  6. Выполняется model.addItem
  7. Выбрасывается событие с конкретным item
  8. Тут же вызывается dispatcher и выполняет реализацию метода onItemAdded - Handler. Получается, что Dispatcher запускает EventHandler.

Create GWT 2.0 custom event