GWT-GXT пример с ComboBox

Библиотека GXT позволяет расширить интерфейсные возможности фреймворка GWT дополнительными визуальными компонентами, различными вариантами их размещения, возможностью работы с моделями данных и кэширующей их подсистемой. Интерфейс WEB-приложения с совместным использованием GWT-GXT можно оформить в виде традиционных десктопных приложений. Подробности инсталляции и демонстрационные возможности библиотеки Sencha GXT рассмотрены здесь.

На данной странице рассматривается GWT-GXT в среде разработки IDE Eclipse. В примере используется визуальный компонент в виде выпадающего списка типа ComboBox. Возможности компонента не ограничиваются выбором значения из простого списка записей. Можно в раскрывающемся окне рядом с записью отобразить иконку, либо отобразить информацию о записи в нескольких колонках.

В примере будет представлено 3 различных варианта использования компонента ComboBox. На следующем скриншоте представлен один из вариантов использования ComboBox с дополнительным размещением иконок (флаги стран) в записях :

Описание компонента с отображением иконок в выпадающем списке представлено здесь.

Структура GWT-GXT примера

Процесс создания GWT-проекта в среде Eclipse здесь не рассматривается; он представлен на странице примера Welcome. При определении параметров данного проекта выбрано наименование gxt-combobox, определен корневой пакет для java-классов com.example, сняты флажки «Use Google App Engine» и «Generate project sample code».

На следующем скриншоте представлена структура GWT-GXT проекта в среде разработки IDE Eclipse, включающая файл конфигурации Gxt_combobox.gwt.xml, главный модуль приложения Gxt_combobox.java, поддиректорию с файлами данных com.example.client.data, поддиректорию с widget'ом ComboboxStock, директорию war со стандартным набором файлов для WEB-приложения и графическими файлами поддиректории images/flags, используемые для отображения флагов стран. Формируемые GWT-компилятором коды скриптов (JavaScript) размещаются в поддиректории gwt-unitCache.

Фреймворк GWT был подключен к проекту на этапе его создания. Библиотека GXT была подключена как пользовательская (User Library). Более подробно об инсталляции и подключении библиотеки GXT к проекту GWT можно прочитать здесь.

Хранилище объектов компонентов ComboBox

Для компонентов ComboBox необходимо создать хранилище типа ListStore<T>. Тип объектов хранилища должен совпадать с типом объектов ComboBox. В примере используются два типа объектов (Stock, Country), для которых создаются два хранилища. Ниже представлено описание хранилищ и объектов, интерфейсы свойств объектов, файлы с данными и описан процесс создания хранилищ ListStore<T>.

Описание хранилищ и объектов

В следующем коде представлено определение хранилищ и описание объектов (методы get/set не включены) :

public  static ListStore<Stock>   STORE     = null;
private        ListStore<Country> COUNTIRES = null;
...
public class Stock
{
    private  int      id;
    private  String   name;
    private  String   symbol;

    private static int COUNTER = 0;

    public Stock() {
        this.id = COUNTER++;
    }
    public Stock(String name, String symbol) {
        this();
        this.name   = name;
        this.symbol = symbol;
    }
}

public class Country
{
    private String abbr ;
    private String name ;

    public Country() {}

    public Country(String abbr, String name) {
        setAbbr(abbr);
        setName(name);
    }
}

Поскольку хранилище STORE будет использоваться в двух компонентах ComboBox, то оно определено как статическое, чтобы можно было бы получить к нему удаленный доступ без передачи ссылки.

Листинги интерфейсов свойств объектов

Для создания хранилища необходимо определить интерфейсы свойств объектов (StockProperties, CountryProperties) типа PropertyAccess<T>.

import com.google.gwt.editor.client.Editor.Path;

import com.sencha.gxt.data.shared.LabelProvider;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.PropertyAccess;
import com.sencha.gxt.core.client.ValueProvider;

public interface StockProperties extends PropertyAccess<Stock>
{
    @Path("id")
    ModelKeyProvider<Stock> key();
  
    @Path("symbol")
    LabelProvider<Stock> symbolLabel();

    ValueProvider<Stock, String> name  ();
    ValueProvider<Stock, String> symbol();
}

public interface CountryProperties extends PropertyAccess<Country>
{
    @Path("abbr")
    ModelKeyProvider<Country> abbr();
  
    @Path("name")
    LabelProvider<Country> name();
}

Файл данных StockData.java

import java.util.List;
import java.util.ArrayList;

public class StockData 
{
    public static List<Stock> getStocks()
    {
        List<Stock> stocks = new ArrayList<Stock>();

        stocks.add(new Stock("Apple Inc."           , "Apple"    ));
        stocks.add(new Stock("Cisco Systems, Inc."  , "Cisco"    ));
        stocks.add(new Stock("Google Inc."          , "Google"   ));
        stocks.add(new Stock("Intel Corporation"    , "Intel"    ));
        stocks.add(new Stock("Microsoft Corporation", "Microsoft"));
        stocks.add(new Stock("Nokia Corporation"    , "Nokia"    ));
        stocks.add(new Stock("Oracle Corporation"   , "Oracle"   ));
        stocks.add(new Stock("eBay Inc."            , "Ebay"     ));
        stocks.add(new Stock("Avaya Inc."           , "Avaya"    ));
        return stocks;
    }
}

Файл данных CountryData.java

import java.util.List;
import java.util.ArrayList;

public class CountryData 
{
    public static List<Country> getCountries()
    {
        List<Country> countries = new ArrayList<Country>();
        countries.add(new Country("ad", "Andora"             ));
        countries.add(new Country("ae", "Arab Emirates"      ));
        countries.add(new Country("ag", "Antigua And Barbuda"));
        countries.add(new Country("ai", "Anguilla"           ));
        countries.add(new Country("al", "Albania"            ));
        countries.add(new Country("am", "Armenia"            ));
        countries.add(new Country("an", "Neth. Antilles"     ));
        countries.add(new Country("ao", "Angola"             ));
        countries.add(new Country("ar", "Argentina"          ));
        return countries;
    }
}

Листинг метода создания хранилищ

При создании хранилищ сначала определяются интерфейсы свойств объектов (sprops, cprops). После этого создаются хранилища, в конструкторы которых передаются объекты типа ModelKeyProvider, определяющие уникальность записей. После создания хранилищ в них загружаются данные списком методом addAll.

private  StockProperties    sprops = null;
private  CountryProperties  cprops = null;	

private void createStores()
{
    sprops = GWT.create(StockProperties  .class);
    cprops = GWT.create(CountryProperties.class);

    STORE = new ListStore<Stock> (sprops.key());
    STORE.addAll(StockData.getStocks());

    COUNTIRES = new ListStore<Country>(cprops.abbr());
    COUNTIRES.addAll(CountryData.getCountries());
}

Создание простого ComboBox

Создание простого объекта combo1 выполняется в методе createCombobox1. При создании выпадающего списка combo1 в конструктор передается хранилище STORE и объект типа LabelProvider<? super Stock>, определяющий отображаемое в компоненте поле объекта.

private ComboBox<Stock> combo1 = null;

private void createCombobox1()
{
    combo1 = new ComboBox<Stock>(STORE, sprops.symbolLabel());
    combo1.setEmptyText("Select a company...");
    combo1.setName("сombo1");
    combo1.setTypeAhead(true);
    combo1.setTriggerAction(TriggerAction.ALL);
    combo1.setWidth(180);

    combo1.addSelectionHandler(selectStock);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SelectionHandler<Stock> selectStock = new SelectionHandler<Stock>()
{
    @Override
    public void onSelection(SelectionEvent<Stock> selectionEvent){
       /*
       * Очистка фильтра, устанавливаемого вводом символов с клавиатуры
       */
       if ((STORE != null) && (STORE.getFilters() != null) &&
                              (STORE.getFilters().size() > 0))
           STORE.removeFilters();
    }
};

Свойство EmptyText позволяет отображать в компоненте текст, если не выбрано ни одной записи из списка. Метод setTypeAhead при истинном значении (true) помогает пользователю выбрать запись из списка при наборе начальных символов с клавиатуры - оставшаяся часть наименования записи «дописывается».

Метод setTriggerAction позволяет исключить или установить взаимосвязь двух и более компонентов. Для этого можно использовать одно из значений :

  • TriggerAction.QUERY - установка данного значения приводит к тому, что после выбора записи (мышью или с помощью клавиатуры) при следующем открытии списка, а также в другом компоненте с этим же хранилищем, остальные записи не отображаются. Чтобы увидеть остальные записи в раскрывающемся списке необходимо очистить запись в компоненте.
  • TriggerAction.ALL - установка данного значения позволяет избежать влияния одного компонента на другой. Но это касается только для выбора значения мышью. Если выбор осуществляется с помощью клавиатуры по начальным символам, то влияние одного компонента на другой сохраняется, поскольку подключается фильтрация записей.

Важно отметить, что выбор записи из списка по начальным символам с использованием клавиатуры сопровождается установкой соответствующего фильтра в хранилище данных. Если хранилище данных используется совместно с другим компонентом, то непроизвольная фильтрация данных также отражается и на другом компоненте. Что исключить влияние одного компонента на другой к компоненту combo1 подключается обработчик событий selectStock, который, после выбора значения, очищает фильтр записей, устанавливаемый при работе с клавиатуры.

Можно заблокировать выбор записи по первым символам с использованием клавиатуры. Для этого необходимо использовать метод setEditable(boolean) со значением false.

На следующем скриншоте представлен раскрытый список combo1. Если ни одной записи не выбрано, то в компоненте отображается подстановочная запись EmptyText («Select a company...»).

Создание ComboBox с иконками в выпадающем списке

Для создания выпадающего списка ComboBox с отображением иконок необходимо использовать конструктор, которому в качестве параметров передается renderer типа SafeHtmlRenderer<T> для отображения значения в виде HTML строки :

ComboBox<T>(ListStore<T> store, 
            LabelProvider<? super Country> label,
            SafeHtmlRenderer<T> renderer);
где
  • store - хранилище данных, из которых можно выбрать запись;
  • label - текст, получаемый из описания интерфейса модели данных, для отображения в компоненте (не в выпадающем списке).;
  • renderer - текст в формате HTML, формируемый на основе модели данных для отображения в выпадающем списке.

Необходимо подготовить renderer. Для этого создается интерфейс ISafeHtml, расширяющий свойства класса XTemplates библиотеки GXT и включающий функцию SafeHtml country(SafeUri imageUri, String name). В качестве параметров в функцию необходимо передать адрес изображения URL и наименование соответствующей записи. Аннотация к методу @XTemplate позволяет определить формат представления записей в выпадающем списке.

import com.google.gwt.safehtml.shared.SafeHtml;

import com.sencha.gxt.core.client.XTemplates;
. . .
interface ISafeHtml extends XTemplates 
{
    @XTemplate("<img width=\"16\" src=\"{imageUri}\"> {name}")
    SafeHtml country(SafeUri imageUri, String name);
}

Для создания выпадающего списка combobox2 с иконками используется метод createCombobox2. Первоначально создается рендерер iSafeHtml стандартным для GWT методом create. После этого создается combo2. В методе render формируется URL изображения флага для каждой страны и вызывается метод country объекта iSafeHtml.

При определении URL изображения используется GXT форматирование (Format.substitute), подробно описанное здесь. Методы setTypeAhead и setTriggerAction описаны выше.

import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.safehtml.shared.UriUtils;

import com.sencha.gxt.core.client.XTemplates;
. . .
private  ISafeHtml       iSafeHtml = null;
private  ComboBox<Country>  combo2 = null;

private  final  String  URL_TEMPL = "{0}images/flags/{1}.png";
. . .
private void createCombobox2()
{
    iSafeHtml = GWT.create(ISafeHtml.class);
    combo2 = new ComboBox<Country>(COUNTIRES, cprops.name(),
                       new AbstractSafeHtmlRenderer<Country>() {
            @Override
            public SafeHtml render(Country item){
                String url = Format.substitute(URL_TEMPL, 
                                               GWT.getHostPageBaseURL(),
                                               item.getAbbr());
                SafeUri imageUri = UriUtils.fromString(url);
                return iSafeHtml.country(imageUri, item.getName());
            }
    });
    combo2.setWidth(180);
    combo2.setTypeAhead(true);
    combo2.setTriggerAction(TriggerAction.ALL);
}

Скриншот компонента ComboBox с иконками в выпадающем списке представлен наверху.

Создание ComboBox с двумя колонками в выпадающем списке

Для создания выпадающего списка из двух и более колонок со значениями, определенными в модели данных, необходимо предварительно :

  • определить формат вывода данных;
  • создать класс формирования данных.

Формат представления данных опишем в файле stock.html. Класс StockCell.java будет формировать данные для представления в выпадающем списке.

Листинг stock.html

В файле stock.html определены 2 колонки, размеры которых составляют 20% и 80% соответственно. В первой колонке будет представлено значение stock.id, во второй stock.name.

<!-- stock.html -->
<div style="width: 100%; overflow: hidden; white-space: nowrap;">
    <div style="width: 20%; display: inline-block; overflow: hidden; 
                           vertical-align: middle;">{stock.id}</div>
    <div style="width: 80%; display: inline-block; overflow: hidden;
                           vertical-align: middle;">{stock.name}</div>
</div>

Листинг StockCell.java

Класс StockCell наследует свойства AbstractCell и используется для описания представления ячейки выпадающего списка. Формирование записей для отображения выполнятся методом render. Интерфейс StockXTemplates, как и в предыдущем компоненте combo2, наследует свойства класса XTemplates. Аннотация метода renderCell определяет представление значения в интерфейсе раскрывающегося списка.

import com.example.client.data.Stock;
import com.google.gwt.core.client.GWT;
import com.sencha.gxt.core.client.XTemplates;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;

public class StockCell extends AbstractCell<Stock>
{
    private StockXTemplates tpl = GWT.create(StockXTemplates.class);

    public interface StockXTemplates extends XTemplates {
        @XTemplate(source = "stock.html")
        SafeHtml renderCell(Stock stock);
    }
    @Override
    public void render(Context context, Stock stock,SafeHtmlBuilder sb){
        sb.append(tpl.renderCell(stock));
    }
}

Листинг ComboboxStock.java

Третий компонент выпадающего списка выполнен в виде отдельного widget'а. Конструктор компонента в качестве параметра получает ссылку на обработчик события, возникающего при выборе записи в компоненте; обработка выполняется «родителем».

public class ComboboxStock implements IsWidget
{
    private  ComboBox <Stock>  comboBox   = null;
    private  StockProperties   properties = null;

    public ComboboxStock (SelectionHandler<Stock> handler)
    {
        properties = GWT.create(StockProperties.class);
        createComboBox(handler);
    }

    ComboBox<Stock> createComboBox(SelectionHandler<Stock> handler)
    {
        ListView<Stock, Stock>       listView  = null;
        IdentityValueProvider<Stock> identity  = null;
        StockCell                    stockCell = null;
        ComboBoxCell<Stock>          comboCell = null;

        identity  = new IdentityValueProvider<Stock>();
        stockCell = new StockCell();
        listView  = new ListView<Stock, Stock>(Gxt_combobox.STORE, 
                                               identity, stockCell);
        comboCell = new ComboBoxCell<Stock>   (Gxt_combobox.STORE, 
                                               properties.symbolLabel(),
                                               listView);
        comboBox = new ComboBox<Stock>(comboCell);
        comboBox.setEditable(false);
        comboBox.setTriggerAction(TriggerAction.ALL);
        comboBox.setWidth(80);

        comboBox.addSelectionHandler(handler);
        return comboBox;
    }
    @Override
    public Widget asWidget()
    {
        return comboBox;
    }
}

В методе createComboBox создается третий компонент с выпадающим списком. Сначала подготавливается объект представления listView, после этого создается ячейка представления записи comboCell. Конструктор ComboBox получает в качестве единственного параметра объект comboCell. Настройка свойств объекта (setTriggerAction, setEditable) описана выше.

Важно знать, что метод asWidget вызывается при размещении компонента в интерфейсе. Если widget используется неоднократно, то нежелательно в этом методе создавать объект. В примере объект создается в конструкторе компонента.

Интерфейс компонента с раскрытым списком из двух колонок представлен на следующем скриншоте.

Главный модуль проекта

Главный модуль Gxt_combobox.java реализует интерфейсы IsWidget и EntryPoint, включающие методы asWidget (widget интерфейса) и onModuleLoad (точка входа). Листинг главного модуля приложения представлен в усеченном виде. Все остальное представлено выше и не составит особого труда «слепить весь код воедино», либо скачать исходный код.

public class Gxt_combobox implements IsWidget, EntryPoint
{
    SelectionHandler<Stock> handler = new SelectionHandler<Stock>() {
        @Override
        public void onSelection(SelectionEvent<Stock> selectionEvent) {
            Stock stock = selectionEvent.getSelectedItem();
            Info.display("SelectionHandler", stock.getName());
        }
    };
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    @Override
    public Widget asWidget()
    {
        ContentPanel panel = new ContentPanel();
        panel.setHeadingText("GXT ComboBox");
        panel.setPixelSize(240, 180);
        panel.addStyleName("margin-5");

        createStores   ();
        createCombobox1();
        createCombobox2();
        ComboboxStock combo3 = new ComboboxStock(handler);

        Margins margins = new Margins(10, 6, 0, 6);

        VerticalLayoutContainer vlc = new VerticalLayoutContainer();
        vlc.add(combo1, new VerticalLayoutData(-1, -1, margins));
        vlc.add(combo2, new VerticalLayoutData(-1, -1, margins));
        vlc.add(combo3, new VerticalLayoutData( 1, -1, margins));

        panel.add(vlc);
        return panel;
    }
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    public void onModuleLoad()
    {
        RootPanel.get("wrapper").add(asWidget());
    }
}

Обработчик событий handler будет вызываться при выборе одной из записи выпадающего списка combo3 для отображения наименования в кратковременно всплывающем окне Info .

В методе asWidget создается панель типа ContentPanel - определяется размер панели и стиль "margin-5". Чтобы компоненты равномерно разместить в интерфейсе используется контейнер вертикального размещения VerticalLayoutContainer. Интерфейс главной панели создается как widget и размещается в секции «wrapper» (Gxt_toolbar.html).

При формирование интерфейса страницы были использованы компоненты VerticalLayoutContainer и Margins, подробно описанные здесь.

Листинг Gxt_combobox.html

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

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="Gxt_combobox.css">
    <title>GXT ComboBox</title>
    <script type="text/javascript" 
            src="gxt_combobox/gxt_combobox.nocache.js" ></script>
  </head>
  <body>
    <div id="wrapper"></div>
  </body>
</html>

Конфигурационный файл проекта Gxt_combobox.gwt.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC 
  "-//Google Inc.//DTD Google Web Toolkit 2.7.0//EN"
  "http://gwtproject.org/doctype/2.7.0/gwt-module.dtd">
<module rename-to='gxt_combobox'>
  <inherits name='com.google.gwt.user.User'                        />

  <!-- Other module inherits -->
  <inherits name="com.sencha.gxt.ui.GXT"                           />
  
  <inherits name="com.sencha.gxt.theme.gray.Gray"                  />
  <!-- inherits name="com.sencha.gxt.theme.blue.Blue"             -->
  <!-- inherits name='com.google.gwt.user.theme.standard.Standard'-->
  <!-- inherits name='com.google.gwt.user.theme.dark.Dark'        -->
  <!-- inherits name='com.google.gwt.user.theme.chrome.Chrome'    -->

  <!-- Entry point class -->
  <entry-point class='com.example.client.Gxt_combobox'             />

  <!-- Specify the paths for translatable code -->
  <source path='client'/>
  <source path='shared'/>
</module>

В конфигурационном файле Gxt_combobox.gwt.xml определяем библиотеку GXT, выбираем тему (стиль интерфейса) Gray бибилиотеки GXT (Gray, Blue). Можно выбрать тему и из набора GWT (Standard, Dark, Chrome). В качестве главного модуля приложения, включающего точку входа в приложение (метод onModuleLoad), определен com.example.client.Gxt_combobox.

Скачать исходный код примера

Исходный код рассмотренного GWT-GXT примера с использованием ComboBox в виде проекта Eclipse можно скачать здесь (7.85 Мб).

В архив примера включен библиотечный файл gwt-servlet.jar, GXT библиотека gxt-3.1.1.jar не включена. После инcталляции примера в IDE Eclipse необходимо настроить GWT SDK самостоятельно («Build Path/Configure Build Path»).

Примечание : библиотека gxt-3.1.1.jar входит в дистрибутив GWT-GXT-примера с использованием табличного компонента Grid

  Рейтинг@Mail.ru