SPRING-SOURCE.RU

Создаем GWT компонент

GWT виджеты - это видимые элементы GWT приложения и поэтому очень важно понимать, как они работают. В этом уроке мы будем использовать Java 1.5 Enums для уменьшения количества ошибок браузера во время выполнения (runtime errors).

Мы назовем наш будущий компонент как HtmlList. Он будет просто отображать список (сортированный или несортированный) в браузере. Список предметов будет иметь действия, которые будут вызываться когда пользователь будет кликать на предмете. Также конкретный предмет в списке предметов будет подсвечиваться, когда указатель будет над ним.

Создание виджета

Все GWT виджеты исходят от com.google.gwt.user.client.ui. Создадим класс под названием HtmlList и расширим com.google.gwt.user.client.ui.Widget. Это создаст виджет, который ничего не будет визуализировать.

Так как GWT виджеты - это простые Java классы, то первым делом мы должны объявить конструктор. Помните, что наш список должен выводить либо сортированный список (OL элемент) либо несортированный (UL элемент). Поэтому в конструктор мы будем подавать булевское значение будет список таким или другим:

      
public HtmlList(boolean orderedList) {

}
		

Этот подход работает, но не самый лучший по двум причинам:

  1. Пользователь виджета видит только, что конструктор берет boolean и затем должен понимать, что этот boolean означает. Для вызова компонента мы должны будем вызвать new HtmlList(true) - такая запись будет не совсем понятной для человека, который видит этот виджет впервые.
  2. Например, если в будущем мы захотим внести в виджет SPAN элемент, а на входе у нас стоит boolean принимающий только два значения.

Мощь Enum

Наш HtmlList будет использовать Java 1.5 Enum (Enumerated type) как параметр в конструктор для указания типа нужного списка. Простой Enum будет выглядеть следующим образом:

      
public enum ListType {
  UNORDERED, ORDERED
 }
		

В итоге конструктор будет выглядеть следующим образом:

      
public HtmlList(ListType listType) {
  switch(listType){
    case UNORDERED:
      // Create UL Element
      break;
    case ORDERED:
      // Create OL Element
      break;
  }
}
		

Это уже лучше. В итоге будет выглядеть вот так: HtmlList(ListType.ORDERED).

ListType будет выглядеть так:

      
public static enum ListType {
  UNORDERED {
   public Element createElement() {
    return Document.get().createULElement();
   }
  },
  ORDERED {
   public Element createElement() {
    return Document.get().createULElement();
   }
  };

  public abstract Element createElement();
 }
		

Здесь каждый Enum значение ДОЛЖНО иметь (из-за абстрактного ключевого слова) метод createElement, который возвращает Element. Конструктор после этих изменений будет выглядеть следующим образом:

      
public HtmlList(ListType listType) {
  setElement(listType.createElement());
 }
		

Теперь это на 100% безопасно, компилятор имеет информацию о том, что Enum значение должно иметь createElement и поэтому он не будет компилировать приложение, если это не так.

В конце мы получим вот такой код:

      
public class HtmlList extends Widget {
 public static enum ListType {
  UNORDERED {
   public Element createElement() {
    return Document.get().createULElement();
   }
  },
  ORDERED {
   public Element createElement() {
    return Document.get().createULElement();
   }
  };

  public abstract Element createElement();
 }

 public HtmlList(ListType listType) {
  setElement(listType.createElement());
 }
}
		

Здесь нужно раскрыть две вещи. Первая - это метод createElement, который использует объект GWT Document, который на самом деле слой абстракции GWT поверх HTML DOM браузера. Вторая вещь - это setElement метод. Этот метод важен, он говорит GWT, что элемент должен представлять этот компонент в HTML DOM. Этот метод ДОЛЖЕН быть вызван когда Widget создается.

Добавление предметов

На данный момент мы имеем компонент, который создает либо OL либо UL элемент. Давайте добавим некоторый список предметов. Не забываем, что при клике на элементе должно выполняться действие. Мы можем сделать это двумя способами, первый - выбросить событие, когда кликаем на предмете, а компонент отвечает на событие. Второй способ - переход в GWT command object, когда добавили предмет и вызов Command.execute метода, когда мы кликаем на предмете. Для упрощения решения задачи мы будем использовать Command object шаблон; что означает добавление следующего addItem метода в HtmlList:

      
public void addItem(String text, Command command) {
  LIElement liElement = Document.get().createLIElement();
  liElement.setInnerText(text);
  getElement().appendChild(liElement);

  listItems.put(liElement, command);

  sinkEvents(Event.ONCLICK);
}
		

Теперь становится немного интереснее. Первая линия этого метода создает HTML LI элемент. Вторая линия устанавливает текст этого LI элемента и третья линия извлекает элемент этого компонента (элемент передается в setElement) и добавляет к нему LI элемент.

Следующая линия сохраняет Command object в Map под названием listItems. Мы еще не объявляли map, давайте сделаем это:

      
public class HtmlList extends Widget {
 private final Map listItems = new HashMap();
		

Map будет ключом от фактического LIElement, который является безопасным, потому что GWT Element метод содержит реализацию equals и hashCode.

Возвращаясь назад к методу addItem, к последней линии sinkEvents(Event.ONCLICK). Когда наш GWT виджет создан, он представляет просто HTML список, он не получает ни каких событий. Если бы это был JavaScript объект нам нужно было бы добавлять onClick событие, как раз именно это sinkEvents метод и делает, он говорит GWT что мы хотим знать любые onClick события, происходящие над нашими (OL или UL) элементами.

Обработка события

Когда событие, которое мы объявили в нашем sinkEvents, произошло, мы вызовем onBrowser событие-метод, для этого мы должны добавить следующий метод в HtmlList:

      
@Override
 public void onBrowserEvent(Event event) {

  switch(event.getTypeInt()) {
   case Event.ONCLICK:
    Element target = event.getTarget();
    if (listItems.containsKey(target))
     DeferredCommand.addCommand(listItems.get(target));

    break;
  }
}
		

Объект события содежращий gwtTypeInt метод, информирующий нас, что событие произошло.

DeferredCommand

Как только мы извлекли Command объект мы добавили его в специальный GWT DeferredCommand объект, который требует особого внимания. Следует помнить, что JavaScript однопоточный язык, если некоторый код события запущен, то весь другой код блокируется, до завершения первого. Теперь мы не знаем, какую команду клиент собирается выполнить в принятом Command объекте, это может быть короткая операция или длинная операция. Если это длительная операция и мы просто вызываем Command.execute(), мы не имели бы возможности вернуться из onVrowserEvent до окончания этой операции и теоретически наш UI был бы заблокирован.

DeferredCommand объект вызывает Command.execute() метод после 1 миллисекунды в течение которой может вернуть наш onBrowserEvent и уже потом вызывает команду, использующую JavaScript поток.

Быстрый тест

В данный момент, мы имеем компонент, отображающий список и запускающий команду когда мы кликаем на элементе. Вот код метода onModuleLoad:

      
public void onModuleLoad() {
   HtmlList list = new HtmlList(ListType.ORDERED);
   list.addItem("Test 1", new Command() {
  public void execute() {
   Window.alert("You Selected Test 1");
  }
   });

   list.addItem("Test 2", new Command() {
   public void execute() {
    Window.alert("You Selected Test 2");
   }
    });

   RootPanel.get().add(list);
  }
		

Теперь мы должны добавить наш модуль в TestApp.gwt.xml файл:

      
<inherits name="com.maddison.killerwidgets.KillerWidgets"/>
		

Все, компонент готов.