UIBinder

22.05.2018
Иванов Аркадий

Перевод оригинального текста: 
http://www.gwtproject.org/doc/latest/DevGuideUiBinder.html

Этот документ рассказывает о том, как создать структуры Widget и DOM из XML-разметки, используя UiBinder, который появился в GWT 2.0. Здесь не рассказывается о локализации - читайте об этом в  Internationalization - UiBinder.

Обзор
Hello World
Hello Composite World
Использование панелей
HTML объекты
Простая привязка обработчиков событий
Использование виджета, которому требуются аргументы конструктора
Стилизованный Hello World
Программный доступ к встроенным стилям
Использование внешних ресурсов в UiBinder
Совместное использование экземпляров ресурсов.
Hello Text Resources
Hello HTML Resources
Применение разных XML шаблонов к одному и тому же виджету
LazyPanel и LazyDomElement
Формирование HTML для Cells
Обработка событий в Cell с помощью UiBinder
Получение сформированных элементов
Доступ к стилям с помощью UiRenderers

Обзор

GWT-приложение - это web-страница. Когда вы создаёте web-страницу, использование HTML и CSS - это наиболее естественный способ сделать это.  UiBinder фреймворк позволяет вам сделать в точности следующее: создать ваше приложение как HTML-страницы с вкраплениями GWT-виджетов внутри них.

Кроме того, это ещё и более естественный и краткий путь сделать UI в сравнении с тем, как делать это в коде программ.
UiBinder может также сделать ваше приложение более эффективным. Броузеры более хороши в построении DOM-структур впихиванием длинных HTML-строк в аттрибуты innerHTML, а не в обработке кучи запросов к API. UiBinder естественным образом использует эти преимущества и в результате мы имеем то, что самый приятный способ сделать приложение - это ещё и самый лучший способ.

UiBinder:

  • улучшает производительность и облегчает сопровождение — легко создать UI с нуля или скопипастить из шаблонов;
  • облегчает сотрудничество с дизайнерами UI, которым существенно удобнее работать с XML, HTML и CSS, нежели с Java.
  • обеспечивает постепенный переход от HTML поделок к настоящему интерактивному UI;
  • подталкивает к чёткому разделению эстетики вашего UI (декларативный XML шаблон) и его программного поведения (Java class);
  • осуществляет контроль кросс-ссылок между Java и XML во время компиляции в обе стороны.
  • даёт прямую поддержку по интернационализации интерфейса, которая хорошо состыковывается с возможностями GWT’s i18n
  • способствует более эффективному использованию ресурсов броузера за счёт удобства использования легковесных элементов HTML, а не тяжёлых виджетов и панелей.

Когда вы изучите чем является UiBinder, вы также поймёте чем он не является. Он не рендерер, он ни на йоту для этого не предназначен. Тут нет циклов, условий, никаких if в его разметке, а только очень ограниченный набор выражений. UiBinder позволяет вам разместить ваш пользовательский интерфейс.  А уже виджеты и друге контроллеры преобразовают строки данных в строки HTML.

Дальше объясняется как использовать UiBinder на примерах его применения. Вы увидите как разместить UI, как изменить его стиль,
и как подключить обработчики событий к нему. В Internationalization - UiBinder объясняется как интернационализировать его.

Быстрый старт: Если вместо дальнейшего изучения вы хотите сразу кодить, взгляните  на этот отрывок. Он включает в себя действия, которые переводят древний пример с Mail на то, чтобы использовать UiBinder. Взгляните на пару файлов Mail.java и Mail.ui.xml.

 

Hello World

Вот тут очень простой пример шаблона UiBinder, который не содержит никаких виджетов, только HTML. Это могло бы показаться странным примером Hello World, поскольку это совершенно нетипичный способ делать свой UI в GWT.  Зато он показывает азы и говорит , что нам вовсе необязательно нести расходы на виджет для того, чтобы иметь шаблон.

<!-- HelloWorld.ui.xml -->

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
 <div>
 Hello, <span ui:field='nameSpan'/>.
 </div>
</ui:UiBinder>

Теперь представьте, что вам вы хотите из программы читать и писать текст в поле span (то, у которого есть аттрибут ui:field='nameSpan') в тексте выше. Возможно вы захотите написать Java код для этого, и для этого шаблон UiBinder имеет связанный класс-владелец, который даёт программный доступ к элементам, объявленным в шаблоне. Класс-владелец для приведённого выше шаблона может выглядеть так:

public class HelloWorld {
 interface MyUiBinder extends UiBinder<DivElement, HelloWorld> {}
 private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

 @UiField SpanElement nameSpan;

 private DivElement root;

 public HelloWorld() {
  root = uiBinder.createAndBindUi(this);
 }

 public Element getElement() {
  return root;
 }

 public void setName(String name) { nameSpan.setInnerText(name); }
}

Затем вы создаёте экземпляр класса-владельца и используете его как часть UI кода. Дальше будут примеры использования виджетов с UiBinder, а в этом примере используются прямые манипуляции с DOM:

HelloWorld helloWorld = new HelloWorld();
// Не забывайте, что этот код только для работы с DOM; он не будет работать с GWT виджетами
Document.get().getBody().appendChild(helloWorld.getElement());
helloWorld.setName("World");

Экземпляры UiBinder - это фабрики, которые генерят структуру UI и склеивают её с Java классом-владельцем.
Интерфейс UiBinder<U, O> объявляет два типа параметров:

  • U - это тип корневого элемента, объявленного в файле ui.xml, который возвращается вызовом метода createAndBindUi
  • O - это класс владелец, чьи @UiFields будут заполняться.
    (В этом примере U это DivElement, а O это HelloWorld.)

Любой объект, объявленный в файле ui.xml, включая любой элемент DOM, может быть доступен в Java классе-владельце через его имя. Здесь, элементу <span> в разметке дан аттрибут ui:field и установлен как nameSpan. В Java коде поле с тем же именем помечено аннотацией @UiField. Когда выполняется uiBinder.createAndBindUi(this), поле заполняется соответствующим SpanElementэкземпляром.

В нашем объекте HelloWorld нет ничего особенного, у него нет суперкласса. Но он бы мог легко расширять UIObject или Widget или Composite. Тут нет ограничений. Однако заметьте, что поля отмеченные как @UiField по умолчанию видимы. Если их надо заполнять через байндер, они не могут быть private.

 

Hello Widget World

Вот пример шаблона UiBinder, который использует виджеты:

<!-- HelloWidgetWorld.ui.xml -->
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
 xmlns:g='urn:import:com.google.gwt.user.client.ui'>

<g:HTMLPanel>
 Hello, <g:ListBox ui:field='listBox' visibleItemCount='1'/>.
</g:HTMLPanel>

</ui:UiBinder>

 

public class HelloWidgetWorld extends Composite {
interface MyUiBinder extends UiBinder<Widget, HelloWidgetWorld> {}
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
  @UiField ListBox listBox;

  public HelloWidgetWorld(String... names) {
  // sets listBox
    initWidget(uiBinder.createAndBindUi(this));
    for (String name : names) {
      listBox.addItem(name);
    }
  }
}

Использование:

HelloWidgetWorld helloWorld = new HelloWidgetWorld("able", "baker", "charlie");

От переводчика: чтобы воспользоваться полученным виджетом helloWorld, его можно добавить в RootLayotPanel:

Заметьте, что мы используем виджеты, а также создаём виджет. HelloWorldWidget может быть добавлен на панель любого класса. Чтобы использовать набор виджетов в файле шаблона ui.xml, необходимо связать их пакет с XML префиксом пространства имён. Это как раз и происходит в аттрибуте корневого <ui:uibinder> элемента: xmlns:g='urn:import:com.google.gwt.user.client.ui'. Эта запись говорит, что каждый класс пакета com.google.gwt.user.client.ui package может быть использован как элемент с префиксом g и тегом, совпадающим с именем Java класса, как в примере и использовано <g:ListBox>.

Посмотрим, а как элемент g:ListBox может иметь аттрибут visibleItemCount='1' ? Он превращается в вызов  ListBox#setVisibleItemCount(int). Каждый метод виджета, который соответствует соглашениям JavaBean по установке свойства класса (сеттер), может быть использован таким образом.

Особо обратите внимание на использование экземпляра HTMLPanel. HTMLPanel выделяется среди других при произвольном смешении HTML и виджетов, а UiBinder очень хорошо работает с HTMLPanel. В общем случае когда вы хотите использовать HTML разметку внутри иерархии виджетов, вам потребуется экземпляр HTMLPanel или HTML виджета.

 

Использование панелей

Любая панель (в теории, всё что угодно, реализующее интерфейс HasWidgets) может быть использовано в файле шаблона и может иметь другие панели внутри себя:

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
 xmlns:g='urn:import:com.google.gwt.user.client.ui'>
  <g:HorizontalPanel>
    <g:Label>Keep your ducks</g:Label>
    <g:Label>in a row</g:Label>
  </g:HorizontalPanel>
</ui:UiBinder>


Некоторые виджеты из состава GWT требуют специальной разметки, которую вы найдёте в javadoc к ним.
Вот пример, как работает  DockLayoutPanel:

<g:DockLayoutPanel unit='EM'>
  <g:north size='5'>
    <g:Label>Top</g:Label>
  </g:north>
  <g:center>
    <g:Label>Body</g:Label>
  </g:center>
  <g:west size='10'>
    <g:HTML>
      <ul>
        <li>Sidebar</li>
        <li>Sidebar</li>
        <li>Sidebar</li>
      </ul>
    </g:HTML>
  </g:west>
</g:DockLayoutPanel>

Потомки DockLayoutPanel собраны в организующие элементы типа <g:north> и <g:center>. В отличие от большей части того, что появляется в шаблоне, они не представляют собой объекты времени выполнения. Вы не можете дать им ui:field аттрибуты, поскольку вам нечего засунуть в это поле в вашем Java классе. Поэтому их название не начинается с большой буквы, чтобы подсказать вам, что они не "реальные". Вы встретитесь с другими элементами не времени выполнения, которые следуют тому же соглашению.

Стоит отметить, что в большинство панелей нельзя напрямую засовывать HTML, а только в виджеты, которые знают, что делать с HTML, особенно HTMLPanel, а также в виджеты, которые реализуют интерфейс HasHTML (такие, как боковая панель <g:west>). В будущих релизах GWT возможно будет снято это ограничение, но пока что это ваша забота вставлять свой HTML в виджеты, которые понимают HTML.

 

HTML объекты.

Шаблоны UiBinder - это XML файлы, а XLM не понимает объекты типа &amp;&nbsp;. Когда вам нужны такие символы, вам надо самому их определить. Для удобства мы даём набор определений, которые вы можете импортировать, указав соответствующий DOCTYPE:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">

Заметим, что компилятор GWT на самом деле не обращается на сайт за файлом, поскольку копия встроена в компилятор. Однако ваша IDE может его скачивать.

 

Простая привязка обработчиков событий.

Одной из целей UiBinder - это уменьшить утомление от создания пользовательских интерфейсов в Java коде, а также несколько вещей, которые на Java требуют ещё более отупляющих шаблонных действий, чем написание обработчиков событий. Как часто вам приходилось писать что-то вроде?

public class MyFoo extends Composite {
  Button button = new Button();

  public MyFoo() {
    button.addClickHandler(new ClickHandler() {
      public void onClick(ClickEvent event) {
        handleClick();
      }
    });
    initWidget(button);
  }

  void handleClick() {
    Window.alert("Hello, AJAX");
  }
}

В UiBinder классе-владельце вы можете использовать аннотацию @UiHandler, чтобы за вас была написана вся эта чепуха анонимного класса:

public class MyFoo extends Composite {
  @UiField Button button;

  public MyFoo() {
    initWidget(button);
  }

  @UiHandler("button")
  void handleClick(ClickEvent e) {
    Window.alert("Hello, AJAX");
  }
}

Однако, в данный момент есть одно ограничение: вы можете использовать @UiHandler только для событий, генерируемых объектами виджетов, а не элементами DOM. Т.е. <g:Button>, а не <button>.

 

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

Каждый виджет, который объявлен в шаблоне, создаётся вызовом GWT.create(). В большинстве случаев их экземпляры должны уметь создаваться по умолчанию; т.е. они должны иметь конструктор без аргументов. Однако есть несколько способов обойти это. В добавок к механизмам @UiFactory и @UiField(provided = true), описанных в Shared resource instances, вы можете пометить свои виджеты аннотацией @UiConstructor.

Представьте, что у вас есть виджет, который требует аргументов конструктора:

public CricketScores(String... teamNames) {...}

И вы используете его в шаблоне:

<!-- UserDashboard.ui.xml -->
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
 xmlns:g='urn:import:com.google.gwt.user.client.ui'
 xmlns:my='urn:import:com.my.app.widgets' >

  <g:HTMLPanel>
    <my:WeatherReport ui:field='weather'/>
    <my:Stocks ui:field='stocks'/>
    <my:CricketScores ui:field='scores' />
  </g:HTMLPanel>
</ui:UiBinder>
public class UserDashboard extends Composite {
  interface MyUiBinder extends UiBinder<Widget, UserDashboard> {}
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

   public UserDashboard() {
     initWidget(uiBinder.createAndBindUi(this));
   }
}

В результате получим ошибку:

[ERROR] com.my.app.widgets.CricketScores has no default (zero args) constructor.
To fix this, you can define a @UiFactory method on the UiBinder's owner, or
annotate a constructor of CricketScores with @UiConstructor.
Так что вы либо делаете @UiFactory метод…
public class UserDashboard extends Composite {
  interface MyUiBinder extends UiBinder<Widget, UserDashboard>;
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
  private final String[] teamNames;

  public UserDashboard(String... teamNames) {
    this.teamNames = teamNames;
    initWidget(uiBinder.createAndBindUi(this));
  }

/** Used by MyUiBinder to instantiate CricketScores */
  @UiFactory CricketScores makeCricketScores() {
    // method name is insignificant
    return new CricketScores(teamNames);
  }
}

... либо делаем аннотацию конструктора...

public @UiConstructor CricketScores(String teamNames) {
  this(teamNames.split("[, ]+"));
} 

 

<!-- UserDashboard.ui.xml -->
<g:HTMLPanel xmlns:ui='urn:ui:com.google.gwt.uibinder'
             xmlns:g='urn:import:com.google.gwt.user.client.ui'
             xmlns:my='urn:import:com.my.app.widgets' >
  <my:WeatherReport ui:field='weather'/>
  <my:Stocks ui:field='stocks'/>
  <my:CricketScores ui:field='scores' teamNames='AUS, SAF, WA, QLD, VIC'/>
</g:HTMLPanel>

 …или заполняем поле, помеченное как @UiField(provided=true)

public class UserDashboard extends Composite {
  interface MyUiBinder extends UiBinder<Widget, UserDashboard>;
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  @UiField(provided=true) final CricketScores cricketScores; // cannot be private

  public UserDashboard(CricketScores cricketScores) {
  // DI fans take note!
    this.cricketScores = cricketScores;
    initWidget(uiBinder.createAndBindUi(this));
  }
}

 

 Стилизованый Hello World

С помощью элемента <ui:style> вы можете определить CSS для своего UI прямо в том месте, где вам это нужно.
Замечание: элементы <ui:style> должны быть прямыми потомками root элемента. Сказанное верно также и для других элементов-ресурсов (<ui:image> и <ui:data>)

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>

  <ui:style>
    .pretty { background-color: Skyblue; }
  </ui:style>

  <div class='{style.pretty}'>
    Hello, <span ui:field='nameSpan'/>.
  </div>
</ui:UiBinder>

Вы можете устанавливать стиль для виджета, не только к HTML. Используйте аттрибут styleName, чтобы переопределить CSS стили виджета, которые в нём есть по умолчанию (это похоже на setStyleName() в коде). Или, чтобы добавить имена классов без перезатирания  встроенных в виджет стилей, используйте специальный аттрибут addStyleNames:

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
 xmlns:g='urn:import:com.google.gwt.user.client.ui'>

  <ui:style>
    .hot { color: magenta; }
    .pretty { background-color: Skyblue; }
  </ui:style>

  <g:PushButton styleName='{style.pretty}'>This button doesn't look like one</g:PushButton>
  <g:PushButton addStyleNames='{style.pretty} {style.hot}'>Push my hot button!</g:PushButton>

</ui:UiBinder>

Отметим, что аттрибутов addStyleNames может быть не один. 

 

Программный доступ к встроенным стилям.

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

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
  <ui:style type='com.my.app.MyFoo.MyStyle'>
    .redBox { background-color:pink; border: 1px solid red; }
    .enabled { color:black; }
    .disabled { color:gray; }
  </ui:style>

  <div class='{style.redBox} {style.enabled}'>I'm a red box widget.</div>

</ui:UiBinder>
public class MyFoo extends Widget {
  interface MyStyle extends CssResource {
    String enabled(); String disabled();
  }

  @UiField MyStyle style; /* ... */
  void setEnabled(boolean enabled) {
    getElement().addClassName(enabled ? style.enabled() : style.disabled());
    getElement().removeClassName(enabled ? style.disabled() : style.enabled());
  }
}

У элемента <ui:style> появился новый аттрибут type='com.my.app.MyFoo.MyStyle'.  Это означает, что для него необходимо создать интерфейс, указанный выше в Java-коде для MyFoo и создать 2 CSS класса, к которым он обращается,  enabled и disabled.

Теперь обратите внимание на поле @UiField MyStyle style в MyFoo.java. Оно даёт коду доступ к CssResource, сгенерированного для блока <ui:style>. Метод setEnabled использует это поле для применения стилей enabled и disabled к виджету, когда его включают
и выключают.

В стилевом блоке указанного типа вы вольны определить сколько угодно ещё классов, но ваш код будет иметь доступ только к тем, к которым есть доступ через интерфейс.

 

 Использование внешних ресурсов в UiBinder

Иногда вашему шаблону необходимо работать со стилями или другими объектами, которые берутся извне самого шаблона. Используйте  элемент <ui:with>, чтобы дать доступ к ним.

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    xmlns:g='urn:import:com.google.gwt.user.client.ui'>

  <ui:with field='res' type='com.my.app.widgets.logoname.Resources'/>

  <g:HTMLPanel>

    <g:Image resource='{res.logo}'/>

    <div class='{res.style.mainBlock}'>
      <div class='{res.style.userPictureSprite}'/>

      <div>
        Well hello there
        <span class='{res.style.nameSpan}' ui:field='nameSpan'/>
      </div>
    </div>

  </g:HTMLPanel>
</ui:UiBinder>

 

/**
 * Ресурсы, которые используются всем приложением.
 */
public interface Resources extends ClientBundle {
  @Source("Style.css")
  Style style();

  @Source("Logo.jpg")
  ImageResource logo();

  public interface Style extends CssResource {
    String mainBlock();
    String nameSpan();
    Sprite userPictureSprite();
  }
}

 

// Внутри класса-владельца UiBinder шаблона
@UiField Resources res;

...

res.style().ensureInjected();

 

Элемент “with” объявляет поле, которое содержит объект ресурсов, чьи методы могут быть вызваны, чтобы заполнить значения аттрибутов. В этом случает его экземпляр создаётся вызовом GWT.create(Resources.class). (Читайте дальше, чтобы увидеть как передать объект вместо того, что он для вас будет создан).

Примечание: ui:with ресурс вовсе не обязан реальзовать интерфейсClientBundle interface; тут это только для примера.

Если вам нужна большая гибкость в работе с ресурсов, вы можете устанавливать параметры с помощью элемента <ui:attributes>. Любые сеттеры или аргументы конструктора могут быть вызваны на объекте ресурса таким образом, как и для любого объекта в шаблоне. Обратите внимание в примере ниже, как объект FancyResources получает ссылку на Resource, объявленный в предыдущем примере:

 

public class FancyResources {
  enum Style {
    MOBILE, DESKTOP
  }

  private final Resources baseResources;
  private final Style style;

  @UiConstructor
  public FancyResources(Resources baseResources, Style style) {
    this.baseResources = baseResources;
    this.style = style;
  }
}
<ui:with field='fancyRes' type='com.my.app.widgets.logoname.FancyResources'>
  <ui:attributes style="MOBILE" baseResources="{res}"/>
</ui:with>

 

Совместное использование экземпляров ресурсов.

Вы можете сделать ресурсы доступными своему шаблон с помощью элемента <ui:with>, однако ценой этого будет создание нового экземпляра для вас. Если вместо этого вы хотите, чтобы ваш код решал создать или найти ресурс, у вас для этого есть 2 способа управлять этим. Вы можете пометить фабричный метод с помощью @UiFactory, или вы можете заполнить поле сами и аннотировать его как@UiField(provided = true).

Вот как используется @UiFactory чтобы дать доступ к экземпляру Resources, который нужен шаблону из предыдущего примера:

public class LogoNamePanel extends Composite {
  interface MyUiBinder extend UiBinder<Widget, LogoNamePanel> {}
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  @UiField SpanElement nameSpan;
  final Resources resources;

  public LogoNamePanel(Resources resources) {
    this.resources = resources;
    initWidget(uiBinder.createAndBindUi(this));
  }

  public void setUserName(String userName) {
    nameSpan.setInnerText(userName);
  }

  @UiFactory /* this method could be static if you like */
  public Resources getResources() {
    return resources;
  }
}

 

 

Для любого поле в шаблоне с типом Resources будет создан экземляр вызовом getResources. Если ваш фабричный метор нуждается в аргументах, они будут обязательны как аттрибуты.

Вы можете всё сделать ещё чётче и иметь больший контроль тем, что используете @UiField(provided = true).

 

 Hello Text Resources

Теперь, когда у нас есть ресурсы, давайте оглянемся на пример Hello world в начале этого документа. Писать код только для того, чтобы отобразить имя, несколько громоздко, особенно если вы никогда не собираетесь его менять. Вместо этого используйте <ui:text> чтобы вшить его прямо в шаблон.

<ui:with field='res' type='com.my.app.widgets.logoname.Resources'/>

<div>
  Hello, <ui:text from='{res.userName}'/>.
</div>

 

Заметка об оптимизации: Если этот ресурс приходит из метода, который распознаётся компилятором GWT, как делающий ничего более, чем возврат константы времени компиляции, любой static final String, то ресурс становится частью шаблона через магию встраивания - никаких дополнительных вызовов функций делаться не будет.

 

Hello HTML Resources 

Полагаться только на текст, как было сделано в предыдущем примере, несколько ограничено. Иногда у вас есть разметка, которую вы бы хотели использовать и вам при этом не хочется  разбираться сразу со всем виджетом. В подобных случаях используйте <ui:safehtml> чтобы вшить любой SafeHtml  в ваш HTML-контекст.

<ui:with field='res' type='com.my.app.widgets.logoname.Resources'/>

<div>
  Hello, <ui:safehtml from='{res.fancyUserName}'/>.
</div>

Для HTML у вас есть и другая возможность. Любой SafeHtml класс может быть использовать напрямую, очень похожим образом как виджет.

<div>
  Hello, <my:FancyUserNameRenderer style="MOBILE">World</my:FancyUserNameRenderer>.
</div>

 

Вы должны реализовать подобный рендерер следующим образом. (Заметьте, что это слегка надуманный пример использования здесь SafeHtmlTemplates для защиты от XSS атак.)

public class FancyUserNameRenderer implements SafeHtml, HasText {
  enum Style {
    MOBILE, DESKTOP
  }

  interface Templates extends SafeHtmlTemplates {
    @SafeHtmlTemplates.Template("<span class=\"mobile\">{0}</span>")
    SafeHtml mobile(String name);

    @SafeHtmlTemplates.Template("<div class=\"desktop\">{0}</div>")
    SafeHtml desktop(String name);
  }
  private static final Templates TEMPLATES = GWT.create(Templates.class);

  private final Style style;
  private String name;

  @UiConstructor
  public FancyResources(Style style) {
    this.style = style;
  }

  void setText(String text) {
    this.name = text;
  }

  @Override
  String asString() {
    switch (style) {
      Style.MOBILE: return TEMPLATES.mobile(name);
    }
    return Style.DESKTOP: return TEMPLATES.desktop(name);
  }
}

Несмотря на то, что это классная техника, она всё-таки имеет свои ограничения. Объекты, которые так используются представляют собой что-то типа билета в один конец. Их методы SafeHtml.asString() вызываются в момент рендеринга (на самом деле в большинстве случаев во время компиляции из-за встравивания в код). Таким образом, если вы обращаетесь к объекту через @UiField в вашем Java-коде, у вас не будет доступа к управлению DOM-структорой, которую он создаст.

 

Применение разных XML шаблонов к одному и тому же виджету

Вы MVP разработчик. У вас красивый визуальный интерфейс и виджет, основанный на шаблоне, который его реализует. Как вы могли бы применить несколько разных XML шаблонов к одному и тому же виду?

Честное предупреждение: Нижележащее - это только демонстрация использования разных ui.xml файлов с одним и тем же кодом. Это не проверенный образец реализации тем в приложении и может оказаться лучшим или не лучшим способом для этого.

public class FooPickerController {
  public interface Display {
    HasText getTitleField();
    SourcesChangeEvents getPickerSelect();
  }

  public void setDisplay(FooPickerDisplay display) { ... }
}

public class FooPickerDisplay extends Composite
    implements FooPickerController.Display {

  @UiTemplate("RedFooPicker.ui.xml")
  interface RedBinder extends UiBinder<Widget, FooPickerDisplay> {}
  private static RedBinder redBinder = GWT.create(RedBinder.class);

  @UiTemplate("BlueFooPicker.ui.xml")
  interface BlueBinder extends UiBinder<Widget, FooPickerDisplay> {}
  private static BlueBinder blueBinder = GWT.create(BlueBinder.class);

  @UiField HasText titleField;
  @UiField SourcesChangeEvents pickerSelect;

  public HasText getTitleField() {
    return titleField;
  }
  public SourcesChangeEvents getPickerSelect() {
    return pickerSelect;
  }

  protected FooPickerDisplay(UiBinder<Widget, FooPickerDisplay> binder) {
    initWidget(binder.createAndBindUi(this));
  }

  public static FooPickerDisplay createRedPicker() {
    return new FooPickerDisplay(redBinder);
  }

  public static FooPickerDisplay createBluePicker() {
    return new FooPickerDisplay(blueBinder);
  }
}

 

 

LazyPanel и LazyDomElement

Вы пытаетесь выжать последнюю каплю производительности из вашего приложения. Некоторые виджеты в вашей панели с закладками требуют времени, чтобы быть готовыми к работе, хотя они даже не видны.  Вы захотите использовать преимущества LazyPanel. Но вы чувствуете себя ленивым: панель абстрактна и вам и вправду не хочется заниматься её наследованием, расширять её.

<gwt:TabLayoutPanel barUnit='EM' barHeight='1.5'>
  <gwt:tab>
    <gwt:header>Summary</gwt:header>
    <gwt:LazyPanel>
      <my:SummaryPanel/>
    </gwt:LazyPanel>
  </gwt:tab>
  <gwt:tab>
    <gwt:header>Profile</gwt:header>
    <gwt:LazyPanel>
      <my:ProfilePanel/>
    </gwt:LazyPanel>
  </gwt:tab>
  <gwt:tab>
    <gwt:header>Reports</gwt:header>
    <gwt:LazyPanel>
      <my:ReportsPanel/>
    </gwt:LazyPanel>
  </gwt:tab>
</gwt:TabLayoutPanel>

Вот это помогает, но есть ещё кое-что, что вы можете сделать.

Где-то в вашем приложении есть шаблон со множеством полей элементов DOM.  Вы знаете, что когда строится ваш uit, вызов getElementById() делается для каждого из них в этой большой странице, что тоже вносит свой вклад в задержку. Используя LazyDomElement вы можете отложить эти вызовы до момента, когда они и вправду потребуются — если вообще когда-нибудь потребуются.

public class HelloWorld extends UIObject { // Could extend Widget instead
  interface MyUiBinder extends UiBinder<DivElement, HelloWorld> {}
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  @UiField LazyDomElement<SpanElement> nameSpan;

  public HelloWorld() {
    // createAndBindUi initializes this.nameSpan
    setElement(uiBinder.createAndBindUi(this));
  }

  public void setName(String name) { nameSpan.get().setInnerText(name); }
}

 

Формирование HTML для Cells

Cell виджеты требуют создания HTML строк, однако код, написанный для объединения строк, чтобы сформировать нужный HTML, быстро устаревает.  UiBinder позволяет вам использовать одни и те же шаблоны, чтобы сформировать этот HTML.

<!-- HelloWorldCell.ui.xml -->

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
  <ui:with field='name' type='java.lang.String'/>

  <div>
    Hello, <span><ui:text from='{name}'/></span>.
  </div>
</ui:UiBinder>

Тэг <ui:with> определяет поля, которые используются как данные для обработки шаблона. Шаблон может содержать только HTML элементы, виджеты или панели недопустимы.

Теперь определим виджет HelloWorldCell . Добавим интерфейс, который расширяет интерфейс UiRenderer (вместо UiBinder).

public class HelloWorldCell extends AbstractCell<String> {
  interface MyUiRenderer extends UiRenderer {
    void render(SafeHtmlBuilder sb, String name);
  }
  private static MyUiRenderer renderer = GWT.create(MyUiRenderer.class);

  @Override
  public void render(Context context, String value, SafeHtmlBuilder builder) {
    renderer.render(builder, value);
  }
}

UiBinder использует имена параметров  в MyUiRenderer.render() в соответствии с полями, определёнными в тэгах <ui:with>.  Используйте любое необходимое вам количество таковых, чтобы сформировать ваши данные.

Обработка событий в Cell с помощью UiBinder

События в Cell требуют от вас написания кода, в которм вы определяете точную ячейку, где было получено событие и даже более того. UiBinder делает за вас большую часть работы. Он направит событие в ваши методы обработчики, основываясь на типе события, и также HTML элемент, в котором было получено событие.

Взяв шаблон HelloWorldCell.ui.xml, давайте обработаем клики в элементе  “name” <span>.

Сначала добавим аттрибут a ui:field к <span>. Это позволит генерируемому коду отличить элемент span от остальных элементов в шаблоне.

<div>
  Hello, <span ui:field='nameSpan'><ui:text from='{name}'/></span>.
</div>

Добавим  метод onBrowserEvent в MyUiRenderer. onBrowserEvent в интерфесе рендерера требует, чтобы только первые 3 аргумента были заданы. Любые последующие аргументы тут только для нашего удобства и будут переданы без изменений в обработчики. Первый аргумент это то, что UiRenderer использует для диспетчеризации событий из onBrowserEvent в методы объекта  Cell Widget.

interface MyUiRenderer extends UiRenderer {
  void render(SafeHtmlBuilder sb, String name);
  onBrowserEvent(HelloWorldCell o, NativeEvent e, Element p, String n);
}

Сделаем известным для  AbstractCell, что мы обрабатываем события click.

public HelloWorldCell() {
  super("click");
}

Сделаем так, что Cell onBrowserEvent будет делегировать обработку в renderer.

@Override
public void onBrowserEvent(Context context, Element parent, String value,
    NativeEvent event, ValueUpdater<String> updater) {
  renderer.onBrowserEvent(this, event, parent, value);
}

И наконец, добавим метод обработчик в  HelloWorldCell, и пометим его как @UiHandler({"nameSpan"}). Тип первого параметра ClickEvent определит тип обрабатываемого события.

@UiHandler({"nameSpan"})
void onNameGotPressed(ClickEvent event, Element parent, String name) {
  Window.alert(name + " was pressed!");
}

Получение сформированных элементов

Как только ячейка сформирована, появляется возможность извлекать и работать с элементами, промаркированными с помощью ui:field. Это полезно, когда вам надо манипулировать элементами DOM.

interface MyUiRenderer extends UiRenderer {
  // ... snip ...
  SpanElement getNameSpan(Element parent);
  // ... snip ...
}

Используйте геттер с передачей родительского элемента, полученного Cell виджетом. Имена таких геттеров должны соответствовать тэгам ui:field помещёными в шаблон, с приставкой “get”. Так например, для ui:field с именем someName, геттер должен называться getSomeName(Element parent).

@UiHandler({"nameSpan"})
void onNameGotPressed(ClickEvent event, Element parent, String name) {
  renderer.getNameSpan(parent).setInnerText(name + ", dude!");
}

Доступ к стилям с помощью UiRenderers

UiRenderer позволяет вам определить геттеры, чтобы получать стили, определённые в шаблоне. Всего лишь определите геттер без параметров, которые соответствует имени стиля и возвращает тип стиля.

<ui:style field="myStyle" type="com.my.app.AStyle">
  .red {color:#900;}
  .normal {color:#000;}
</ui:style>

<div>
  Hello, <span ui:field="nameSpan" class="{myStyle.normal}">
    <ui:text from="{name}"/></span>.
</div>

Определите интерфейс для стиля:

public interface AStyle extends CssResource {
  String normal();
  String red();
}

Определите геттер стиля в интерфейсе UiRenderer, с приставкой "get" перед именем стиля:

interface MyUiRenderer extends UiRenderer {
  // ... snip ...
  AStyle getMyStyle();
  // ... snip ...
}

Затем используйте стиль там, где он вам потребовался. Заметим, что вам необходимо получать имя стиля используя метод доступа red(). Компилятор GWT compiler подменяет настоящее имя стиля, чтобы предотвратить коллизии с другими стилями с тем же именем в вашем приложении.

@UiHandler({"nameSpan"})
void onNameGotPressed(ClickEvent event, Element parent, String name) {
  String redStyle = renderer.getMyStyle().red();
  renderer.getNameSpan(parent).replaceClass(redStyle);
}