Работа с библиотекой ExtGWT
Глава 1. Обзор библиотек GXT и GWT
правитьБиблиотеки GWT и GXT представляют собой мощнейшее решение для разработки web-приложений, которые выглядят и обладают всеми функциями традиционных десктопных приложений. Разработчики, знающие язык программирования Java, могут использовать свой ранее накопленный опыт и существующие наработки для создания современного программного обеспечения. Стоит отметить, что ваше приложение не будет привязано к определенному контейнеру сервлетов, и вы можете создавать и запускать его даже дома на локальном компьютере. Мы также рассмотрим использование популярной среды разработки Eclipse, однако вы не будете ограничены каким-то одним программным продуктом и сможете работать с тем набором средств разработки, к которому уже привыкли.
В этой главе вы кратко познакомитесь с основными возможностями GXT. Мы также поверхностно рассмотрим особенности библиотеки GWT, основные приемы ее использования и сборки приложений.
Немного об GXT
правитьБиблиотека GXT (ранее имевшая название ExtGWT) - это основанное на фреймворке Google Web Toolkit решение, предназначенное для построения высококлассных пользовательских интерфейсов web-приложений, разрабатываемое компанией Sencha, которой принадлежит JavaScript одноименная библиотека Sencha ExtJS.
Так как GXT является надстройкой над мощной системой от Google, вместо изобретения очередного велосипеда она просто расширяет базовые возможности этой платформы, привнося в нее дополнительные компоненты, различные варианты их размещения, возможность работы с моделями данных и кэширующей их подсистемой. Библиотека начала свою жизнь под именем "MyGWT", а в дальнейшем ее главный разработчик присоединился к компании ExtJS (которая затем была переименована в Sencha) и значительно расширил ее возможности, практически переписав некоторые ее части с нуля. На сегодняшний момент существует уже третья версия - GXT 3.0, которая полностью покрывает потребности в компонентах типового RIA приложения.
Краткий перечень возможностей GXT:
- Компоненты для отображения и редактирования данных в их различных представлениях: Grid, List, DataView, Tree, Table
- Панели, табы и методы автоматического расположения визуальных компонентов
- Расширенные возможности по работе с окнами, диалогами, сообщениями и информационными панелями
- Поддержка работы с формами ввода данных как с простым, так и с форматированным текстом, полей для ввода чисел, паролей, выпадающих списков, календарей, а также других элементов
- Кнопки, всплывающие подсказки, панели инструментов, панели статуса и меню
- Локальные кэширующие хранилища объектов данных, их автоматические загрузчики и модели данных, позволяющие легко взаимодействовать с компонентами библиотеки
- Возможность создания интерактивных портальных и имитирующих десктоп web-приложений, написанных с использованием фреймворка MVC
- Большой выбор графических эффектов, таких как изменяемые размеры и drag-n-drop для компонентов и их контейнеров
Совместное использование GXT и ExtJS: GXT была спроектирована таким образом, что для работы ей требуется только фреймворк GWT. Крайне нежелательно использовать в коде приложения сторонние JavaScript библиотеки (такие как ExtJS), так как это может привести к непредсказуемым результатам. Лицензирование: GXT имеет двойную лицензию и доступна как вариант с открытым исходным кодом под GPLv3, так и под коммерческой, которую выбирают разработчики, кому по тем или иным причинам не подходит GPL Интеграция: GXT на 100% заточено под возможности фреймворка GWT, осуществляя всю свою внутреннюю работу при помощи его встроенных классов и методов. В отличии от некоторых других библиотек, GXT не является еще одной "оберткой" над существующей библиотекой ExtJS. Поддержка браузеров: GXT поддерживает работу создаваемых приложений во всех распространенных браузерах: Chrome 6+, Internet Explorer 6+, Firefox 3.6+ (PC, Mac), Safari 3+ и Opera 10.5+ (PC, Mac). |
Так же как и в других библиотеках на основе GWT, разработка приложений осуществляется на языке Java. Несмотря на то, что GXT предназначен для создания web-приложений, вам не потребуется иметь существенный опыт и знание HTML, каскадированных таблиц стилей (CSS) и языка JavaScript, что несомненно является большим преимуществом для Java-разработчиков, которые хотят попробовать перенести GUI приложения на Web. Следует отметить, что такие приложения масштаба предприятия можно создавать за короткое время даже не выходя из дома.
Так же как и в других приложениях, написанных с использованием GWT, в процессе сборки весь Java-код будет кросскомпилирован в JavaScript-код и связан с дополнительными HTML и CSS файлами, что в совокупности будет представлять собой законченное web-приложение. При этом, как ваш код, так и код библиотеки GXT подвергается оптимизации отдельно под каждый из браузеров, представленных в текущий момент на рынке. Такая так называемая "компиляция в JavaScript" гарантирует, что результирующий код будет быстрее и эффективнее чем тот, который написан даже самым опытным разработчиком по той причине, что оптимизацию машина всегда сделает лучше.
Немного о GWT
правитьGoogle впервые представила первую версию GWT в мае 2006 года. На текущий момент в версии 2.5 GWT содержит в себе все возможности, которые только могут потребоваться интерактивному web-приложению. Являясь проектом с открытым исходным кодом (выпущенным под лицензией Apache версии 2.0), GWT открыт для сообщества и активно развивается самим Google. Кроме того, проект имеет конкретный план развития, регулярно исправляются обнаруженные ошибки и выходят обновления.
Несомненно, GWT предназначен для быстрой и эффективной работы с технологией Ajax (асинхронный JavaScript и XML), однако самым большим его недостатком является то, что он не сможет предоставить потенциальному разработчику все многообразие визуальных компонентов, к которому тот мог уже привыкнуть в GUI средах.
Краткий список возможностей GWT:
- Полная поддержка версии 5 языка Java (дженерики, перечисляемые типы, аннотации и т.д.)
- Кросс-компилятор Java-в-JavaScript с поддержкой хост-режима, позволяющий осуществлять отладку Java кода
- Независимость кода от браузера - проблемы переходных версий и кнопок навигации остаются в прошлом
- Некоторые простые визуальные компоненты: кнопки, формы, элементы, деревья, диалоги, панели и простейшие таблицы
- Поддержка нескольких способов обмена данными с сервером (RPC, XML или JSON)
- Поддержка автоматизированного тестирования с JUnit
- Интернационализация приложений и выбор языка
Несомненно, что начинающий разработчик GWT-приложений уже должен уверенно владеть языком Java, даже несмотря на то, что рассматриваемый фреймворк использует не все его возможности. Следует отметить, что серверная часть приложения может быть построена с помощью технологии J2EE, или же выбран альтернативный любой другой удобный и знакомый разработчику язык и серверный фреймворк. Работа GWT (а также GXT) ни в кое мере не зависит от движка используемой платформы.
GWT эмулирует классы, расположенные в пакетах java.lang
, java.util
и некоторые классы из java.io
и java.sql
, но не более. Так как GWT в конечном счете выполняет компиляцию Java кода в JavaScript, запускаемый в дальнейшем в пользовательском браузере, вы должны учитывать принципиальную невозможность работы пакетов JDBC, Swing, произвольной работы с сетью и файлами на компьютере клиента. Другие специфичные стандартизированные возможности языка Java (вроде Java Reflection - отражений) также невозможно использовать из-за природы JavaScript.
Однако, учитывая то, что GWT создавался с целью разработки промышленных web-приложений, он включает в себя все возможности, которые только могут понадобиться.
Вводный курс в GWT
правитьТак как GXT является расширением GWT, привнося в него дополнительные визуальные компоненты и полный стек для создания бизнес-приложений, нам стоит подробнее рассмотреть работу и сборку GWT-приложений. Если вы уже знакомы с GWT, смело пропускайте этот материал и переходите к Главе 2.
Как это работает?
правитьЖизнь любого GWT приложения начинается с исходного кода на языке Java, который затем либо компилируется, либо интерпретируется в реальном времени (в хост-режиме), в результате чего на выходе получается JavaScript код. Структура результирующего кода является монолитной, представляющей собой отдельное целостное приложение, которое для загрузки и исполнения даже не требует web-сервера. Это кардинально отличается от обычных web-приложений, к которым мы уже привыкли, выполняющих перегрузку содержимого страницы при каждом обращении пользователя.
Глава 2. Подготовка среды разработки
правитьОрганизация среды для разработки для типового приложения с библиотекой GXT практически не отличается от проектов, использующих только Google Web Toolkit: необходимо выполнить всего пару дополнительных движений. В этой главе мы рассмотрим основные инструменты, потребующиеся для подготовки законченной среды разработки, а также создание базовой структуры нового проекта, который в последующем будет использоваться для работы с примерами кода из этого учебника.
Что потребуется
правитьСоздание RIA-приложений с помощью GXT строится путем взаимосвязи нескольких программных компонентов, которые можно свободно загрузить из сети Интернет и настроить на правильную работу. Так как фреймворк GWT использует всю мощь языка Java и многообразие сторонних инструментов, завязанных на нем, то прежде всего необходимо установить среду разработки для Java, которая будет интегрирована с GXT.
В приведенной ниже таблице указаны конкретные версии программных компонентов, протестированных с примерами из нашего учебника. Мы рекомендуем использовать именно эти версии (или даже более новые), чтобы не получить неожиданных проблем при прогонке примеров, до тех пор, пока вы самостоятельно не станете понимать их взаимосвязь.
Компонент | Описание | Сайт |
---|---|---|
JDK7 | Комплект среды разработки Java SE Development Kit (JDK), включающий в себя среду выполнения Java и инструменты командной строки для разработки | http://www.oracle.com/technetwork/java/javase/downloads/index.html |
Eclipse 4.2.2 | Среда разработки Eclipse включает в себя Java компилятор, отладчик, редактор кода с подсветкой синтаксиса и огромное количество встроенных и сторонних дополнений | http://www.eclipse.org/downloads/ |
Google Plugin для Eclipse | Специальное дополнение от Google для среды Eclipse, позволяющее интегрировать SDK в среду разработки | http://code.google.com/eclipse/ |
GWT 2.5.1 | Google Web Toolkit включает в себя инструмент отладки приложений, кросс-компилятор Java-в-JavaScript, инструменты командной строки и саму библиотеку GWT | http://code.google.com/webtoolkit/ |
GXT 3.0.1 | Библиотека GXT, включающая в себя исходный код, скомпилированные классы и примеры | http://www.sencha.com/products/gxt/ |
Для работы GWT с Eclipse мы будем использовать специальный плагин от Google, но фактически вы не ограничены инструментом и можете воспользоваться любой IDE средой или даже простым текстовым редактором. В примерах мы будем использовать для разработки платформу Windows 7, однако вы можете воспользоваться любой из числа поддерживаемых компанией Google.
Установка Eclipse и плагина от Google
правитьДо того, как вы начнете свой первый проект с GXT, необходимо установить Eclipse и настроить среду.
Установка JDK
правитьДля этого нам нужно будет перейти на официальную страницу продукта и скачать и установить последнюю версию JDK. На момент ревизии этого учебника последней версией была Java Platform (JDK) 7u17.
Обратите внимание, что если у вас установлена 64-битная операционная система, то скачивать и устанавливать желательно именно 64-битную JDK, так как в противном случае все возможности системы будут использованы не полностью.
Установка Eclipse
правитьКак и в предыдущем пункте обратимся к официальному сайту проекта Eclipse и скачаем zip-архив с версией 4.2 (на момент написания этого учебника последней доступной была версия 4.2.2) с официального сайта. Как мы говорили раньше, лучшим вариантом будет комплект Eclipse IDE for Java EE Developers. Для других платформ поступим также. После загрузки необходимо распаковать полученный архив в отдельный каталог и создать ярлык в любом удобном месте (в меню или на рабочем столе) на файл eclipse.exe (в случае Windows) или eclipse (в случае Linux или MacOS):
Установка плагина от Google
правитьGoogle плагин позволяет легко интегрировать два комплекта SDK для разработчиков: Google Web Toolkit и Google App Engine в среду разработки Eclipse. Со второй системой мы познакомимся в дальнейшем, а сейчас кратко ее можно описать как платформу для хостинга web-приложений от самого Google. Основной особенностью является высокая масштабируемость и отсутствие необходимости для разработчиков вникать в детали настройки серверного ПО - достаточно нажать одну кнопку для публикации проекта, и он сразу станет доступным всему миру.
Откроем Eclipse и выберем в меню Help -> Install New Software... и нажмем кнопку Add....
Далее введем в поле Name любое описание сайта с дополнениями (например, Google Plugin), а в поле Location адрес сайта http://dl.google.com/eclipse/plugin/4.2 и нажмем кнопку OK.
Мы вернемся обратно в окно Available Software, где увидим список пакетов с нового сайта обновлений. Необходимо поставить галочки напротив пакета Google Plugin for Eclipse (required), а также выбрать дополнительные SDK, которые загрузятся при установке плагина Google App Engine Java SDK и Google Web Toolkit SDK. Нажмите далее кнопку Next...
Обратите внимание, что на этом шаге загрузка дополнительных пакетов необязательна - их можно установить в любой момент позже. Если Eclipse слишком долго выполняет процедуру установки, попробуйте обратиться к соответствующему разделу в FAQ. В том случае, если возникают другие проблемы при установке, можно попробовать другой вариант. |
Далее еще раз проверьте, что выбраны верные опции и нажмите кнопку Next.
Прочитайте лицензионное соглашение, выберите пункт I accept the terms in the license agreements и нажмите Next.
Далее после успешной установки система попросит перезапустить среду Eclipse. Нажмите Yes.
Установка GXT
правитьТак же как и для Eclipse выполним загрузку zip-архива библиотеки GXT с официального сайта проекта. Далее произведем распаковку архива в отдельный каталог, определим ее в Eclipse в списке пользовательских библиотек.
Выберите в меню Window -> Preferences, далее в раздел Java -> Build Path -> User Libraries. Нажмите кнопку New... и задайте внутреннее имя библиотеки, например GXT_3_0, далее щелкните по кнопке OK. Затем нажмите кнопку Add External JARs..., перейдите в каталог, куда ранее был распакован архив, и выберите файлы gxt-3.0.1.jar и gxt-chart-3.0.1.jar. По завершении у вас должно получиться следующее:
Создание первого приложения
правитьДля создания нового проекта, найдите кнопку . Также можно воспользоваться этим мастером, выбрав пункт Web Application Project в ниспадающем меню, связанном с кнопкой New, или выбрав в меню File > New > Web Application Project.
Использование мастера плагина Google
правитьМастер New Web Application Project позволяет создать новое web-приложение, которое будет использовать в своей работе Google Web Toolkit (GWT) и/или Google App Engine:
Для начала необходимо ввести имя проекта и корневого пакета. Название проекта будет использовано при создании классов демо-приложения, которое всегда разворачивается мастером. Все классы помещаются в определенный на этом шаге пакет.
Система предлагает возможность подключить доступные SDK, которые в данный момент установлены в системе. При изучении примеров этого учебника мы будем использовать как Google Web Toolkit (являющийся базовым для нашей библиотеки GXT), так и Google App Engine для хранения данных и публикации приложений.
По завершении выбора необходимых опций нажмите кнопку Finish для создания нового проекта.
Демо-приложение
правитьДля удобства разработчиков, которые только начинают изучать фреймворк GWT, мастер не только создаст необходимые файлы и структуру каталогов для нового приложения, а также сгенерирует код, демонстрирующий возможности Google Web Toolkit:
Как вы успели заметить, проект имеет два основных каталога src, в котором размещаются исходные файлы, и war, в котором располагаются скомпилированные файлы классов, библиотеки времени выполнения, статический контент и конфигурационные файлы. Чтобы попробовать в действии новый проект найдите в каталоге war файл Test1.html, щелкните по нему правой кнопкой и выберите в выпадающем меню Run As -> Web application project.
В нижней части экрана Eclipse отобразится окно состояния с вкладкой Development Mode и ссылкой, перейдя по которой можно будет получить доступ к нашему приложению:
Щелкнув по нему мы перейдем в браузер, который установлен в нашей системе по-умолчанию (в моем случае это Google Chrome). При первом запуске вам будет предложено установить дополнение для браузера, которое будет взаимодействовать с нашим сервером для обеспечения возможности отладки и разного рода сервисных функций:
Соглашаемся и производим установку плагина для разработчиков. Далее закрываем/открываем браузер на этой странице либо просто повторно щелкаем url нашего приложения в Eclipse.
Обратите внимание, при первом обращении к нашему приложению GWT на лету произведет кросс-компиляцию из Java в JavaScript, что может вызвать задержку в реакции вашего компьютера. В дальнейшем перекомпиляцию проходят только те файлы в приложении, которые мы меняли в последний раз.
На рисунке снизу приведено изображение шаблонного Hello World GWT-приложения.
Работа с GXT
правитьНесколькими разделами ранее мы создали GWT приложение, и теперь пришло время выполнить ряд дополнительных настроек для совместимости с GXT. Мы проделаем небольшие изменения в XML файле настроек модуля приложения, его HTML файле и подключим ее JAR файл.
Подключение библиотеки GXT
правитьМы должны включить определенную ранее пользовательскую библиотеку (GXT_3_0) как в пути поиска классов при компиляции (buildpath), так и в пути библиотек времени исполнения (classpath).
- Buildpath: В окне Project Explorer щелкните правой кнопкой на названии нашего проекта Test1 и выберите в выпадающем меню пункт Properties. Перейдите в раздел Java Build Path. Щелкните по вкладке Libraries, затем по кнопке 'Add Library...', выберите тип User Library и поставьте галочку напротив выбора GXT_3_0. В завершении необходимо нажать кнопку OK:
- Classpath: В окне Project Explorer найдите файл Test1.gwt.xml, щелкните правой кнопкой на нем и выберите в выпадающем меню пункт Run As -> Run Configurations... Далее в появившемся окне промотайте список разделов вниз, найдите подраздел Web Application -> Test1 и выделите его. Перейдите на вкладку Classpath, выберите пункт User Entries, а затем щелкните по кнопке Advanced. Выберите переключатель Add Library, нажмите OK. Далее как и в предыдущем шаге укажите пользовательскую библиотеку (User Library) GXT_3_0, поставив напротив нее галочку. В завершении необходимо нажать кнопку Apply и Close:
Изменение файла-описателя GWT модуля
правитьДля того, чтобы сообщить GWT, что мы будем использовать библиотеку GXT в нашем проекте, необходимо добавить всего одну строчку в XML файл, описывающий GWT модуль приложения (в нашем случае это файл Test1.gwt.xml, расположенный в каталоге src/org.wikibooks.gxt). Щелкните по нему правой кнопкой мыши, выберите в выпадающем меню Open with -> Text editor, найдите строчку:
<inherits name='com.google.gwt.user.User'/>
и добавьте после нее строчку:
<inherits name='com.sencha.gxt.ui.GXT'/>
В результате изменений файл Test1.gwt.xml будет выглядеть примерно следующим образом:
Найдем в файле Test1.gwt.xml строчку <inherits name='com.google.gwt.user.theme.clean.Clean'/>
и закомментируем ее. Таким образом мы отключаем стандартные стили от GWT, которые будут переопределены движком от GXT. Если мы не сделаем эту операцию, то получим лишние отступы в десяток пикселей по краям экрана нашего приложения:
Изменение HTML файла приложения
правитьСледующим шагом необходимо внести ряд изменений в HTML файл проекта. В версии 2.5 фреймворка GWT этот файл расположен в каталоге war (в нашем случае это файл war/Test1.html).
Для подключения GXT к проекту необходимо убедиться, что используется корректный тип документа (doctype) и включены все требуемые файлы CSS-стилей библиотеки.
- В шапке файла должна быть такая строка (скорее всего ничего менять не потребуется):
<!doctype html>
- Далее добавьте эту строчку до тега
<link type="text/css" rel="stylesheet" href="Test1.css">
:
<link rel="stylesheet" type="text/css" href="Test1/reset.css" />
В данном случае здесь каталог Test1 задается как имя нашего проекта. Если проект называется по-другому, эту строчку также необходимо будет скорректировать.
Для правильного отображения визуальных компонентов библиотеки GXT необходимо чтобы браузер при загрузке приложения переключался в режим strict (который отличается от режима совместимости). Для дополнительной информации смотрите в Wikipedia о режимах браузеров. |
Опять же можно удалить ненужные строки из HTML файла с разметкой для демо-приложения, расположенные между тегами <body> и </body>. В итоге у нас должно получиться что-то вроде следующего:
Проверка работы: первое GXT приложение
правитьМастер создания нового приложения, который входит в состав Google плагина для Eclipse ничего не знает о библиотеке GXT, поэтому создает демо-приложение только для виджетов фреймворка GWT. Чтобы мы могли убедиться, что все ранее выполненные действия верны, нам необходимо попробовать запустить код с использованием GXT.
Произведем очистку структуры проекта от тестового GWT-кода: удалим все строки в файле Test1.java и скопируем новое содержимое, которое является пустым каркасом нашего модуля:
package org.wikibooks.gxt.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;
import com.sencha.gxt.widget.core.client.box.AlertMessageBox;
import com.sencha.gxt.widget.core.client.box.MessageBox;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;
public class Test1 implements EntryPoint {
public void onModuleLoad() {
}
}
Внимание! При желании вы также можете удалить файлы GreetingService.java, GreetingServiceAsync.java, GreetingServiceImpl.java и FieldVerifier.java которые также нам в дальнейшем не понадобятся: они представляют собой пример web-сервиса, к которому обращается демо-приложение. При удалении этих файлов не забудьте, что нужно удалить выделенный на картинке блок из файла web.xml в каталоге war/WEB-INF: |
В метод onModuleLoad() добавим тестовый код:
public void onModuleLoad() {
TextButton b = new TextButton("Нажми меня...");
b.addSelectHandler(new SelectHandler() {
@Override
public void onSelect(SelectEvent event) {
AlertMessageBox d = new AlertMessageBox("Нажата кнопка", "Вы нажали кнопку");
d.setIcon(MessageBox.ICONS.info());
d.show();
}
});
RootPanel.get().add(b);
}
Алгоритм его работы прост и мы сейчас не будем углубляться в суть каждой операции: на страницу приложения выводится кнопка, при нажатии на которую отображается сообщение.
Сохраним все измененные файлы, запустим снова наше приложение и мы увидим изменения на экране:
Если вместо кириллицы у Вас отображаются "квакозябры", измените кодировку исходных файлов на UTF-8. Это можно сделать в свойствах проекта. |
В заключение
правитьНесмотря на то, что в этой главе мы проделали огромную работу по настройке всего многообразия инструментов, библиотек и связки их между собой, в дальнейшем весь этот комплекс средств позволит сконцентрироваться на быстрой разработке.
В третьей главе мы рассмотрим визуальные компоненты, которые предлагает нам GXT, методы их использования и различные приемы, которые вы не найдете в официальной документации. После прочтения следующей главы вы сможете придать своему приложению уникальный внешний вид, используя стили и встроенную поддержку тем оформления.
Глава 3. Виджеты и все, что с ними связано
правитьЛюбая библиотека, которая помогает создавать «богатые web-приложения» (или по-английски Rich Internet Applications — RIA), прежде всего должна содержать широкий выбор интерфейсных элементов (или виджетов). Рассматриваемая нами библиотека GXT содержит более 10 различных пакетов, содержащих более 210 классов, предназначенных для работы с виджетами. Каждый из таких элементов имеет множество настраиваемых параметров и позволяет изменять свой внешний вид и дизайн, используя механизм тем оформления. Ниже приведено изображение демонстрационного приложения [1], которое содержит все доступные элементы библиотеки и служит хорошим подспорьем для изучения ее работы:
В этой главе вы узнаете какие типы визуальных компонентов предоставляет библиотека GXT, принципы их использования и нестандартные приемы, которые могут пригодиться при написании своего собственного приложения.
Пакет .widget
правитьБазовый пакет com.extjs.gxt.ui.client.widget
содержит наиболее часто используемые классы компонентов, контейнеров и виджетов общего назначения. Обычно именно с одного из виджетов, находящихся в этом пакете, вы начнете создавать новое приложение.
Component
правитьВсе виджеты библиотеки ExtGWT используют класс Component как родительский класс, либо наследуя его напрямую, либо наследуя его дочерний класс BoxComponent, в том случае, если элементу управления требуются функции изменения размера и позиционирования. Все дочерние классы, образованные от Component, автоматически управляются ядром библиотеки на протяжении всего цикла работы web-приложения (в том числе поддержка операций подсоединения и отсоединения виджета от DOM-модели браузера). Им также обеспечивается автоматическая поддержка скрытия и повторного отображения (hide/show) и отключения и включения (disable/enable) элементов управления. Класс Component позволяет своим дочерним классам использовать ленивое отображение в любом контейнере библиотеки ExtGWT. Это дает возможность таким контейнерам производить отображение видимых дочерних элементов (а следовательно выполнять дорогостоящие операции по работе с DOM-моделью в браузере пользователя и выделению памяти) только один раз при позиционировании самого контейнера.
Сам же класс Component является наследником класса Widget библиотеки GWT, что позволяет использовать все виджеты библиотеки ExtGWT в обычных GWT-приложениях. Все компоненты, подключаемые к панели GWT (класс Panel) будут отображены сразу же после операции добавления.
Следует сказать, что важным элементом класса Component (а также любых производных от него компонентов) является объект класса El, который можно получить, вызвав метод el(). Он представляет собой высокоуровневую обертку над базовым элементом DOM-модели, соответствующему нашему компоненту. Используя функционал, реализованный в классе El, вы можете изменить стиль, размеры, расположение и другие атрибуты визуального компонента. В целом El довольно похож на класс Element из фреймворка GWT, позволяет выполнять те же действия и даже больше, благодаря чему мы рекомендуем использовать в работе именно этот вариант.
Рендеринг компонентов
правитьСпособ, которым производится рендеринг виджетов в ExtGWT кардинально отличается от принятого в GWT.
В GWT при создании нового виджета, обычно вызывается метод setElement(element), и его содержимое сразу добавляется к DOM-модели. Обычно это происходит при подключении элемента к GWT-панели (класс Panel и его потомки).
В случае с ExtGWT виджет конструируется и отрисовывается при вызове внутреннего метода onRender. Если виджеты добавляются к контейнер Container, рендеринг будет запущен один раз при вызове метода layout() - при формировании раскладки дочерних элементов. В отличии от этого для GWT-панели рендеринг будет происходить сразу же после добавления каждого элемента (что справедливо и для других виджетов фреймворка GWT).
При разработке богатых по функционалу web-приложений вы можете неожиданно столкнуться с проблемами отображения компонентов, которые будут выглядеть не так, как было первоначально задумано и при этом долго искать источник проблемы. В некоторых случаях может оказаться виновным неверный порядок наложения стилей, в других - вы можете наткнуться на прежде неизвестную ошибку в коде самой ExtGWT, - всегда бывает очень сложно определить, что же хочет от вас браузер. По этой причине крайне важно иметь готовый инструмент, который поможет определить точный стиль или HTML код, добавляемый в DOM-модель. При работе в web-режиме проще всего будет воспользоваться расширениями Firebug для браузера Firefox или IE Developer Toolbar для Internet Explorer. Используя эти инструменты всегда видно, какие трансформации происходят в таблице стилей и какой HTML код в итоге генерируется. К сожалению, подобный комплекс недоступен при отладке приложения в хост-режиме, поэтому пока единственным вариантом является предварительная компиляция в GWT и отладка с помощью внешнего браузера. Помните, что мы рекомендуем заранее установить Firebug или IE Developer Toolbar, чтобы быть готовым к таким проблемам. Firebug можно свободно загрузить на сайте http://getfirebug.com |
Другим важным аспектом, о котором нельзя не упомянуть, является то, что некоторые компоненты имеют дополнительные атрибуты, чьи значения учитываются при формировании их внешнего облика. Методы, которые позволяют установить такие значения, отмечены в javadoc к ExtGWT словом pre-render.
События
правитьСобытия - это метод обеспечения обратной связи, позволяющий разработчику узнать об изменениях состояния виджетов, действиях пользователя и осуществлять взаимодействие с серверными компонентами приложения. Используемая в ExtGWT модель для событий должна быть знакома любому Java-разработчику - это паттерн разработки типа Observer, в котором сначала необходимо для конкретного события определить собственный программный обработчик, вызываемый системой при его возникновении.
В библиотеке реализация всех классов, связанных с обработкой событий, собрана в одном пакете com.extjs.gxt.ui.client.event
.
Каждый компонент имеет метод addListener, позволяющий ассоциировать новый обработчик конкретному событию. Для удобства разработки некоторые компоненты дополнительно имеют ряд методов, позволяющих быстро добавить обработчик для некоторых типов событий. Однако, это никак не ограничивает разработчика и он при необходимости вправе выполнить те же действия, используя общий метод addListener.
Список событий, поддерживаемых каждым компонентом, доступен в соответствующем javadoc руководстве, в котором также приведены события, наследуемые от родительских классов. |
Классы, в которых реализованы события, являются потомками класса BaseEvent, и в зависимости от своего типа могут поддерживать метод setCancelled, который используется для отмены действия на реакцию обработчика этого события. К примеру, если вы создаете обработчик для события BeforeHide какого-либо окна, после чего в процессе работы будет вызван метод setCancelled(true) объекта WindowEvent, последующая логика работы метода hide() будет отменена. Окно продолжит отображаться на экране, несмотря на то, что пользователь попытался его закрыть нажатием кнопки.
Подписка на события
правитьВ фреймворке GWT при создании реализации нового компонента, разработчик должен обеспечить его "подписку" на необходимые типы событий. Сделано это прежде всего во избежание возникновения утечек памяти в браузерах пользователей, которые может породить автоматически генерируемый JavaScript-код.
Так как ExtGWT фактически является расширением GWT, то для работы с событиями в тех компонентах, в которых они по-умолчанию не используются, необходимо также произвести подписку на эти события. Допустим, что вы хотите разместить текст у себя в приложении, воспользовавшись компонентом Html, который должен по задумке подсвечиваться при наведении на него мыши. Так как ExtGWT до проведения процедуры рендеринга компонента не добавляет его содержимое к DOM-модели, подписку нужно проводить либо во время, либо после рендеринга. Давайте попробуем реализовать эту идею в конкретном коде:
package com.myapp.client;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.widget.Html;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.RootPanel;
public class MyApplication implements EntryPoint {
@Override
public void onModuleLoad() {
Html text = new Html("Наведи на меня мышку") {
protected void onRender(Element target, int index) {
super.onRender(target, index);
el().addEventsSunk(Event.MOUSEEVENTS);
}
};
Listener<BaseEvent> listener;
listener = new Listener<BaseEvent>() {
public void handleEvent(BaseEvent be) {
Html h = (Html)be.getSource();
int b = h.el().getIntStyleAttribute("fontWeight");
b = b == 900 ? 100 : 900;
h.setIntStyleAttribute("fontWeight", b);
}
};
text.addListener(Events.OnMouseOver, listener);
text.addListener(Events.OnMouseOut, listener);
text.setPagePosition(20, 20);
RootPanel.get().add(text);
}
}
В приведенном выше коде мы сначала создаем компонент типа Html и с помощью анонимного класса переопределяем его метод OnRender. В нем производим вызов оригинального кода, извлекаем объект класса El и указываем на необходимость подписки на события группы MOUSEEVENTS в дополнении к стандартным для этого элемента. Далее определяется и подключается новый обработчик для событий типа OnMouseOver и OnMouseOut, выполняющий переключение между двумя стилями font-weight, реализующий изменение жирного начертания текста:
Внимание! Обратите внимание, что типы одних и тех же стилей, описываемых в CSS-разметке и коде JavaScript различаются регистром (в JavaScript используется метод CamelCase). Для преобразования одного в другое необходимо убрать разделители-дефисы между слов и начать каждое из них (кроме первого) с заглавной буквы (так CSS идентификатор font-weight будет равнозначен JavaScript идентификатору fontWeight). Полная таблица соответствий приведена в Приложении 1. |
Container
правитьКонтейнеры — это виджеты, которые содержат другие компоненты. Они берут на себя всю заботу о жизненном цикле своих дочерних элементов, их создании, подключении и отключении от DOM-модели. Контейнеры также выполняют необходимые действия по правильному позиционированию и изменению размеров своих компонентов. К стандартным виджетам, которые являются контейнерами, можно отнести компоненты ButtonBar, HtmlContainer, Menu, Portal, Table, TabPanel, ToolBar и Tree.
LayoutContainer
правитьЕсли вы уже имели опыт создания обычных приложений с графическим интерфейсом в средах Visual Studio, Delphi и тому подобных, то наверное помните их визуальные редакторы, позволяющие расположить несколько кнопочек, элементов ввода, редакторов текста в точно в заданных координатах оконного интерфейса. Разработчику было явно видно, что первая кнопка расположена в 8 пикселях от второй, а ширина всего диалогового окна жестко задана в 200 пикселей и не менялась пользователем, так как иначе могла «поехать» вся остальная разметка элементов.
Почему теперь такой подход считается неправильным? Да потому, что во-первых с тех пор кардинально изменилось число устройств, на которых могут работать приложения, а их технические характеристики выросли не только вверх, но и вниз. Одно и тоже приложение должно хорошо себя представлять как на 20" мониторе, установленном на рабочем столе, так и на маленьком экране нетбука с диагональю 7". Кроме того, обратите внимание на различную величину параметра DPI (число точек на дюйм) для различных устройств: число пикселей, которые помещаются в реальный физический размер экрана может отличаться в несколько раз, таким образом делая реальный отображаемый размер элементов или слишком маленьким, или наоборот слишком большим.
Мне сначала было сложно подобрать адекватный перевод технического термина layout, который используется как в документации Google Web Toolkit, так и библиотеки виджетов ExtGWT. Совершенно очевидно, что слово произошло фразы lay out, которая дословно переводится «выкладывать». Давайте в дальнейшем определимся, что наиболее подходящим русскоязычным аналогом мы будем считать термин раскладка.
Современным и более правильным подходом в проектировании расположения элементов управления графического интерфейса является использование раскладок. Представьте, что вы выбираете способ, с использованием которого будет осуществляться позиционирование виджетов в окне, задаете дополнительные параметры (например, процент отступа элементов от края области), а свою черную работу на себя берет движок интерфейсной библиотеки. Пользователи могут изменять размеры форм так, как им это будет удобно, задействуя все доступные возможности текущего оборудования.
Далее применительно к библиотеке ExtGWT мы рассмотрим компонент LayoutContainer, который является базовым контейнером, имеющим функции автоматической раскладки своих дочерних элементов. Его основной особенностью является возможность подключения различных вариантов раскладки элементов, позволяющую реализовать практически любой дизайн интерфейса приложения. По умолчанию в LayoutContainer используется тип раскладки FlowLayout, который подразумевает стандартное для HTML разметки расположение элементов (первый виджет прикрепляется к верхнему левому углу области, далее они выстраиваются сверху вниз). В связи с тем, что в ExtGWT реализовано много вариантов раскладки элементов, мы вернемся к их подробному рассмотрению в Главе 4, а на данном этапе просто сфокусируемся на основных функциях компонента LayoutContainer.
Далее предлагаю открыть редактор кода Eclipse и реализовать небольшой пример создания контейнера, добавления к нему нового виджета и подключения к основному объекту приложения RootPanel:
package com.myapp.client;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;
public class MyApplication implements EntryPoint {
@Override
public void onModuleLoad() {
// создание нового объекта LayoutContainer
LayoutContainer container = new LayoutContainer();
// создание и добавление в контейнер кнопок
container.add(new Button("Кнопка 1"));
container.add(new Button("Кнопка 2"));
// жесткая установка ширины и высоты контейнера в 300 пикселей
container.setSize(300,300);
// задание отображения рамки
container.setBorders(true);
// подключение элемента-контейнера к DOM-модели приложения
RootPanel.get().add(container);
// "выкладка" контейнера - отображение всех дочерних элементов и выстраивание по заданному алгоритму
container.layout();
}
}
По умолчанию компонент LayoutContainer не имеет никакого визуального изображения, поэтому бывает достаточно сложно определить его границы на экране. Для отладки вы можете попробовать использовать метод setBorders(true), который отобразит внешние границы контейнера. Помимо всего прочего, это даст возможность найти проблемы при отображении дочерних компонентов при той или иной раскладке.
Обратите внимание, что в нашем случае (когда компонент LayoutContainer помещается напрямую в DOM-модель, а не в другой существующий контейнер с заданным типом раскладки), необходимо явно задать ему размеры и вызвать метод layout(), который произведет операцию размещения и отображения его дочерних элементов. Это мы и сделали в последней строке примера.
HorizontalPanel и VerticalPanel
правитьКомпоненты HorizontalPanel и VerticalPanel реализуют свою функциональность путем использования стандартного в HTML разметке тега table. Эти контейнеры предназначены для упрощения раскладки элементов в совсем тривиальных случаях:
- HorizontalPanel: размещает дочерние элементы в один единственный ряд слева направо, используя раскладку TableRowLayout
- VerticalPanel: размещает дочерние элементы в одну единственную колонку сверху вниз, используя раскладку TableLayout
В следующем примере мы добавим два компонента Label (представляющего собой текстовый элемент) к горизонтальной панели, указав применительно ко второму дополнительные параметры, также называемые данные раскладки (layout data). Так как мы используем раскладку типа TableLayout, то дополнительные параметры должны содержаться в соответствующем ему объекте TableData. В нашем случае мы укажем необходимость выровнять текст во второй ячейке по правому краю.
// создание объекта - горизонтальной панели
HorizontalPanel hp = new HorizontalPanel();
// установка ширины в пикселях
hp.setWidth(300);
// установка ширины встроенной таблицы
hp.setTableWidth("100%");
// добавление к панели компонента Label
hp.add(new Label("Выровнено по центру"));
// создание объекта TableData (содержащего дополнительные параметры раскладки)
TableData td = new TableData();
// задание выравнивания текста по правому краю
td.setHorizontalAlign(HorizontalAlignment.RIGHT);
// добавление к панели компонента Label с дополнительными параметрами по раскладке
hp.add(new Label("Выровнено по правому краю"), td);
// подключение элемента-контейнера к DOM-модели приложения
RootPanel.get().add(hp);
Дополнительные данные о раскладке используются библиотекой в тот самый момент, когда происходит процесс их выстраивания. С их помощью можно задавать информацию о размере, границах вокруг элементов и их позиционировании. Далее мы более подробнее рассмотрим их использование в Главе 4.
ContentPanel
правитьПредставьте себе элемент — прямоугольную область, расположенную в определенном месте экрана браузера, которую можно использовать для помещения в нее текста, изображений, других визуальных компонентов. Таких элементов может быть много, и всех их будет удобно разместить так, чтобы они не только не перекрывали друг друга, а наоборот содержали разную по смыслу информацию, позволяя пользователю в любой момент времени обратиться к нужной.
Класс ContentPanel является классом-наследником контейнера SimpleContainer, но вдобавок ко всем его методам и свойствам имеет дополнительные элементы оформления: отдельный заголовок, подвал, функции сворачивания/разворачивания содержимого, возможность отображения рамки и подключения служебных областей панели соответственно вверху и внизу элемента. В заголовок мы можем поместить иконку, установить его текст и настроить отображение маленьких служебных кнопок. В подвал можно добавить кнопки с помощью метода addButton. Внутрь панели с помощью метода setWidget размещается один компонент, размерами которого панель начинает управлять. В том случае, если панель не помещается в другой контейнер с определенной раскладкой, компоненту ContentPanel должны быть явно заданы размеры.
Давайте засучим рукава и напишем код, который создаст новую панель и выведет в область окна браузера:
// Создание объекта - панели
ContentPanel cp = new ContentPanel();
// Установка заголовка
cp.setHeadingText("Заголовок");
// Явное задание размера панели
cp.setPixelSize(250, 140);
// Смещение панели на 10 пикселей к низу и вправо относительно
// родительского контейнера
cp.setPosition(10, 10);
// Активация функции сворачивания
cp.setCollapsible(true);
// Установка белого фона для тела панели
cp.setBodyStyle("backgroundColor: white;");
// Добавление к заголовку панели дополнительных кнопок
cp.addTool(new ToolButton(ToolButton.GEAR));
cp.addTool(new ToolButton(ToolButton.CLOSE));
// Добавление произвольной HTML разметки в тело панели
cp.setWidget(new HTML("Текст <b>жирный</b>"));
// Кнопка OK в нижней области панели
cp.addButton(new TextButton("OK"));
// Подключение панели к корневому элементу
RootPanel.get().add(cp);
Попробуем улучшить внешний вид панели и воспользуемся потомком ContentPanel - компонентом FramedPanel, той же самой панелью, но с закругленными углами. Немного изменим код:
// Создание объекта - панели
ContentPanel cp = new FramedPanel();
В панели можно динамически менять содержимое после ее первоначальной отрисовки методом setWidget. Измененный пример:
cp.setWidget(new HTML("Текст <b>жирный</b>"));
// Кнопка OK в нижней области панели
TextButton btn = new TextButton("OK");
cp.addButton(btn);
btn.addSelectHandler(new SelectHandler() {
@Override
public void onSelect(SelectEvent event) {
HTML html = (HTML)cp.getWidget();
html.setHTML(html.getHTML() + "<br />Новый текст");
}
});
В этом случае мы создали объект TextButton (кнопка), а затем воспользовались анонимным классом, реализующим интерфейс SelectHandler, в котором переопределили метод onSelect, вызывающимся при нажатии этой кнопки. Таким образом мы создали обработчик события, выполняющий добавление новой надписи "Новый текст" и производящий перекомпоновку содержимого компонента HTML, который содержит наша панель.
В классическом случае мы бы могли создать новый класс (называющийся например, BtnSelectHandler), а уже от него создать объект-обработчик события и привязать его к кнопке. Однако, так как в нашем случае логика обработки события тривиальна и нет необходимости привязывать обработчик в нескольким визуальным компонентам, использование анонимного класса сделало наш код компактным и более читаемым. Следует отметить, что среда разработки Eclipse всячески способствует быстрому написанию кода, в большинстве случаев "угадывая" нужные нам типы событий и подставляя целые шаблоны кода, что дает разработчику возможность сконцентрироваться на бизнес-логике приложения, не отвлекаясь на написание и отладку интерфейсного кода.
Viewport
правитьКомпонент Viewport - это потомок класса LayoutContainer, главной функцией которого является заполнение собой всего доступного пространства окна браузера и отслеживание изменений его размеров. При таком изменении автоматически происходит перекомпоновка дочерних элементов, положение которых просчитывается под новые размеры. Рассмотрим простой пример:
Viewport viewport = new Viewport();
viewport.add(new ContentPanel(), new MarginData(10));
RootPanel.get().add(viewport);
Так как Viewport является наследником от LayoutContainer, он вобрал весь функционал этого класса, и в нашем случае мы создаем дополнительный объект данных с параметрами MarginData, указывающий 10-пиксельный отступ от границы ContentPanel.
Но в отличии от LayoutContainer, компонент Viewport автоматически производит перекомпоновку положения своих объектов при запуске приложения. Вам достаточно добавить объект Viewport к базовой панели RootPanel и он возьмет на себя все остальное. Главное применение этого компонента - использование как центрального элемента окна приложения, которое может хорошо работать при различных размерах экрана пользователя. Вы можете попробовать изменять размер окна браузера, чтобы убедиться, что панель будет автоматически подстраивать размеры своего заголовка.
Окна (Window и Dialog)
правитьКомпонент Window реализует контейнер, который может свободно перемещаться в пределах окна браузера и своим поведением напоминает окна, широко использующиеся в обычном десктопном интерфейсе. Окна могут быть модальными (блокирующими остальные интерфейс при своей активации), видимыми и скрытыми, обычными и развернутыми на всю доступную область. Так как класс Window является наследником от ContentPanel, он имеет тот же самый визуальный вид и функционал, присущий панели (например, отобразить иконку сворачивания методом setCollapsible(true))
Однако, в отличии от ContentPanel, объект Window не нужно добавлять в контейнер (или RootPanel приложения). При вызове метода show() класс Window добавляет соответствующий код в DOM-модель и устанавливает его z-индекс таким, чтобы окно отображалось над всеми другими элементами. Далее производится вызов функции раскладки дочерних элементов и центровка нового окна относительно рабочей области.
Если вы после создания и завершения работы окна хотите в дальнейшем повторно его отобразить, то предпочтительно вместо полного уничтожения объекта-окна и освобождения зарезервированных под него ресурсов, выполнить его скрытие и повторное отображение. Рассмотрим далее пример использования Window:
// создание объекта окна
Window w = new Window();
// установка текста заголовка
w.setHeadingText("Информация о продукте");
// делаем окно модальным
w.setModal(true);
// установка размеров
w.setPixelSize(300, 200);
// Позволять пользователю закрывать окно нажатием кнопки X
w.setClosable(true);
// Разрешить пользователю растягивать окно
w.setResizable(true);
// кнопка разворота на всю доступную область
w.setMaximizable(true);
// задание всплывающей подсказки
w.setToolTip("Домашняя страница GXT...");
// добавляем в окно простой дочерний компонент
w.add(new HTML("Текст"));
// отображение окна
w.show();
Окно можно использовать для вывода информации с другого сайта, вместо того, чтобы размещать на нем визуальные компоненты:
// внутри содержимого окна будет появляется web-страница
w.setWidget(new Frame("http://www.sencha.com/products/gxt"));
Как вы обратили внимание, мы вызвали метод setWidget в нашем коде, который добавляет в приложение новый фрейм и отображает в нем содержимое по заданному URL.
Класс Dialog является наследником класса Window и добавляет к его функционалу дополнительные методы для работы со встроенными кнопками. Обычно этот компонент используется как удобный способ для создания окон с кнопками OK/Отмена/Да/Нет. Далее мы рассмотрим его применение, а также дополнительный метод HideOnButtonClick, который автоматически скрывает окно диалога после того, как пользователь нажмет одну из кнопок:
Dialog d = new Dialog();
d.setHeadingText("Предупреждение о выходе");
// текст предупреждения
d.setWidget(new HTML("Хотите записать изменения перед выходом?"));
// стиль тела диалога
d.setBodyStyle("fontSize:14px;fontWeight:bold;padding:13px;");
d.setPixelSize(300,120);
d.setHideOnButtonClick(true);
d.setClosable(false);
// установка нужного нам набора кнопок
d.setPredefinedButtons(PredefinedButton.YES, PredefinedButton.NO, PredefinedButton.CANCEL);
d.show();
Как вы видите, мы произвели всю работу по настройке кнопок диалога одним вызовом метода setPredefinedButtons, который к тому же автоматически провел корректное их позиционирование. Мы также установили жирное начертание текста диалога и отступ от его краев в 13 пикселей. Но несмотря на то, что в этом примере встроенные в код стили работают и даже неплохо справляются с этим, в реальном приложении крайне желательно все стилевые изменения выносить в отдельный CSS файл, что отделит его код от дизайна и не потребует перекомпилирования при внесении изменений в оформление.
MessageBox
правитьКомпонент MessageBox в работе очень похож на стандартную функцию языка JavaScript window.alert(): позволяет отобразить сообщение в отдельном модальном диалоге, указать требуемую иконку и, при необходимости, запросить у пользователя ввести какие-нибудь данные. Технически класс MessageBox является наследником компонента Dialog и содержит часто используемую для таких случаев логику работы.
Ранее мы уже рассматривали пример с MessageBox в Главе 2, и как вы видели, этот компонент довольно просто использовать. Самым интересным свойством MessageBox является то, что он не прерывает исполнение JavaScript-кода до вмешательства пользователя (также как и Window и Dialog), как это делает стандартная функция языка window.alert(). Это преимущество должно принести разработчику ряд возможностей в виде дополнительного удобства для пользователей в использовании интерфейсов новейших web-приложений.
Из-за этой особенности наиболее частозадаваемым вопросом является: как правильно отобразить сообщение "Вы уверены?" при закрытии окна - ведь когда программа перейдет к следующей строке после организации MessageBox, пользователь еще даже не успеет сделать какой-нибудь выбор. Мы может воспользоваться интересным решением - использовать метод Event.setCancelled(true) в коде обработчика закрытия окна, чтобы предотвратить его скрытие, после чего отобразить вопрос с MessageBox. Если пользователь подтвердит свое желание нажатием кнопки Yes, обработчик удалит сам себя из списка обработчиков, привязанных к этому событию и вызовет метод hide(). В этот раз окно будет безусловно закрыто, так как список обработчиков пуст.
В следующем примере мы практически рассмотрим это решение:
final Window w = new Window();
w.setHeading("Закрой меня...");
w.setSize(300, 200);
Listener wL = new Listener() {
@Override
public void handleEvent(BaseEvent be) {
be.setCancelled(true);
Listener<MessageBoxEvent> cb;
cb = new Listener<MessageBoxEvent>() {
@Override
public void handleEvent(MessageBoxEvent be) {
String id = be.getButtonClicked().getItemId();
if (Dialog.YES == id) {
w.removeAllListeners();
w.hide();
}
}
};
MessageBox.confirm("Закрыть?", "А Вы уверены?", cb);
}
};
w.addListener(Events.BeforeHide, wL);
w.show();
В дополнении к методу confirm класс MessageBox также имеет метод alert, который отображает единственное сообщение с кнопкой OK и prompt, которое реализует текстовое поле для пользовательского ввода с кнопками OK и Cancel. Вы также можете сделать это поле многострочным, что позволит получить от пользователя произвольный текст:
final MessageBox box = MessageBox.prompt("Заголовок", "Введите какую-нибудь фигню", true, new Listener<MessageBoxEvent>() {
@Override
public void handleEvent(MessageBoxEvent be) {
Dialog box = (Dialog)be.getComponent();
TextField<String> text = (TextField)box.getFocusWidget();
String userData = (String)text.getValue();
Info.display("MessageBox", userData);
}
});
Обратите внимание, что мы в обоих примерах использовали метод getComponent() объекта-события MessageBoxEvent для получения ссылки на объект класса Dialog, который представляет собой сконструированное нами окно сообщения. Альтернативно можно было бы воспользоваться методом getDialog() объекта MessageBox, который был создан в результате вызова программного кода.
Глава 4. Работа с данными
правитьПри создании современного web-приложения процесс разработки не ограничивается взаимодействием с компонентами и их визуальным расположением: ко всему прочему необходимо, что оно имело возможность быстро и гибко оперировать различными данными. Следует принять во внимание тот факт, что основной сложностью является необходимость содержать локальную копию данных, которая позволяет быстро и эффективно их отображать с учетом выполнения сортировок, фильтрации, группировки.
В этой главе описано, как библиотека ExtGWT оперирует данными, позволяя различным компонентам приложения получать к ним доступ. Используя объекты типа ModelData и BeanModel, а также локальную систему их хранения и загрузки, вы сможете получить максимум от потенциала таких мощных компонентов как Grid, Table, Tree и ListView.
Модели данных, хранилища и их загрузчики
правитьВ этом разделе мы подробно рассмотрим что представляют собой эти термины и их взаимодействие между собой.
Модели
правитьДавайте представим, что мы пишем сложное web-приложение с использованием возможностей Google Web Toolkit, состоящее из двух независимых частей: серверной, которая будет иметь доступ к некоей базе данных, и клиентской, выполняющейся в конечном виде на языке JavaScript в браузере. Очевидно, что с учетом всех достоинств GWT клиентскую часть можно сделать такой же "умной" как и серверную: добавить возможность хранить промежуточные результаты работы между обращениями приложения к серверу, манипулировать этими данными и даже умудряться строить отчеты. Вполне логично, что разработчик захочет иметь для клиентского и серверного кода единый механизм работы с такими данными, и например свести объекты данных в классы типа POJO (Plain Old Java Object - старый-добрый Java объект). Затем объекты этих классов, заполненные реальными данными, можно будет организовывать в коллекции, списки, хэши и так далее.
А теперь давайте посмотрим на это с точки зрения разработчика универсальной библиотеки, такой как ExtGWT. Мы пишем компоненты, которые независимо от типа объекта должны иметь возможность знать о количестве, типе, составе его полей и уметь динамически определять другие параметры. На стороне сервера мы воспользуемся отражением (технология Java Reflection), таким образом определив все поля нужного нам класса, однако на стороне браузера подобного механизма нет - ведь наш код кросскомпилирован в JavaScript!
Тут на выручку приходит интерфейс, называющийся моделью данных. Во многих фреймворках по существу, это класс, в котором описаны поля того или иного типа объекта, их границы и другие параметры бизнес логики. Для ExtGWT - это также интерфейс, который берет на себя работу по внутреннему отображению данных с учетом особенностей кросскомпилированного JavaScript-кода.
Рассмотрим пример. Допустим, в приложении нам требуется оперировать данными по акциям, для чего мы воспользуемся определением модели, заданной новым классом Stock. Определяем модель:
- Создаем новый класс Stock в пакете пакет_нашего_приложения.client.data, указав в качестве родительского класс com.extjs.gxt.ui.client.data.BaseModel
- Определяем конструктор по умолчанию без аргументов:
package com.myapp.client.data;
import com.extjs.gxt.ui.client.data.BaseModel;
public class Stock extends BaseModel {
public Stock() {
}
}
Обратите внимание, что важный шаг - для класса, у которого отсутствует конструктор без аргументов, сериализация (а значит передача данных между клиентом и сервером) будет невозможна.
- Для удобства определяем необходимое число реальных конструкторов модели с аргументами, являющимися начальными данными для объектов. Например, в нашем случае можно будет задать описание новой акции двумя способами:
// компания, тикер, цена открытия и цена закрытия
public Stock(String name, String symbol, double open, double last) {
}
// компания, цена открытия, изменение цены, процент изменения, дата и описание индустрии
public Stock(String name, double open, double change, double pctChange, Date date, String industry) {
}
- Далее в конструкторах напишем код, который будет помещать данные, указанные при создании нового объекта в его структуру. Любой класс-модель имеет два базовых метода установления новых типизированных значений полям модели и их извлечение: public <X> X set(java.lang.String property, X value) и public <X> X get(java.lang.String property).
Начнем с примера как не надо делать и рассмотрим измененный код конструкторов:
public Stock(String name, String symbol, double open, double last) {
set("name", name);
set("symbol", symbol);
set("open", open);
set("last", last);
// Текущая дата
set("date", new Date());
// Здесь это вычисляемое значение - изменение равно цена закрытия минус цена открытия
set("change", last - open);
}
public Stock(String name, double open, double change, double pctChange, Date date, String industry) {
set("name", name);
set("open", open);
set("change", change);
set("percentChange", pctChange);
set("date", date);
set("industry", industry);
}
Теперь где-нибудь у себя в коде мы можем обратиться к объекту класса Stock и получить значение его поля:
double stock1Open = (Double)stock1.get("open")
Почему ранее мы говорили, что это неправильный подход работы с моделями? Так как они по дизайну приложения являются объектами бизнес-логики, необходимо, чтобы при доступе к значениям полей имелась возможность провести их валидацию. Перепишем этот блок, вынеся установку и извлечение в отдельные геттеры-сеттеры (методы класса, которые являются обертками над функциями записи и получения значений внутренних членов класса):
public Stock(String name, String symbol, double open, double last) {
setName(name);
setSymbol(symbol);
setOpen(open);
setLast(last);
// Текущая дата
setDate(new Date());
// Здесь это вычисляемое значение - изменение равно цена закрытия минус цена открытия
setChange(last - open);
}
public Stock(String name, double open, double change, double pctChange, Date date, String industry) {
setName(name);
setOpen(open);
setChange(change);
setPctChange(pctChange);
setDate(date);
setIndustry(industry);
}
public String getName() {
return (String)get("name");
}
public void setName(String name) {
set("name", name);
}
public String getSymbol() {
return (String)get("symbol");
}
public void setSymbol(String symbol) {
set("symbol", symbol);
}
public double getOpen() {
return (double)get("open");
}
public void setOpen(double open) {
set("open", open);
}
public double getLast() {
return (double)get("last");
}
public void setLast(double last) {
set("last", last);
}
public double getChange() {
return (double)get("change");
}
public void setChange(double change) {
set("change", change);
}
public Date getDate() {
return (Date)get("date");
}
public void setDate(Date date) {
set("date", date);
}
public double getPctChange() {
return (double)get("pctChange");
}
public void setPctChange(double pctChange) {
set("pctChange", pctChange);
}
public String getIndustry() {
return (String)get("industry");
}
public void setIndustry(String industry) {
set("industry", industry);
}
Данные теперь можно получить как в примере выше:
double stock1Open = (Double)stock1.getOpen()
Что нам это дало? Теперь мы можем контролировать присваиваемые значения отдельным полям:
public void setName(String name) {
// тикер акции не может содержать более 4 символов
if (name.length() > 4) {
// в противном случае - обрезать до 4 символов
set("name", name.substring(1, 4));
} else {
set("name", name);
}
}
Мы рассмотрели лишь один из возможных в библиотеке ExtGWT способов создания объектов данных и далее еще вернемся к этому вопросу.
Хранилища (Store)
правитьБиблиотека ExtGWT имеет в своем составе достаточно удобные возможности по хранению однотипных объектов (которые в большинстве своем представлены моделями) на стороне браузера. Некоторые визуальные компоненты (такие как ListView, Grid, ComboBox) умеют напрямую обращаться к хранилищу и работать с находящимися там данными, другие же (как Table, Tree, DataList) требуют дополнительную функцию-прослойку. Класс Store и его наследники реализуют различные варианты локальных хранилищ с поддержкой кэширования данных какие только могут потребоваться приложению. Вы как разработчик можете воспользоваться их встроенными функциями сортировки, фильтрации и изменения локального набора данных. Кроме того, хранилище использует систему управления событиями, которая позволяет реагировать на появление новых данных или, например, изменения направления их сортировки.
Внимание! Вы должны отдавать себе отчет, что размер данных, хранимых в локальной части приложения, не может быть слишком велик. К примеру, работа с таблицей из 100000 записей практически не будет реализуема и приведет к невозможности работы даже на современном браузере. Всегда на этапе проектирования приложения следует помнить, что чем меньшими порциями оперирует клиентская часть, тем лучше. |
Хранилище содержит в себе фактически две коллекции объектов: модели (объекты Model) определенного типа, которые были получены в результате загрузки, например, из БД и записи (объекты Record), которые сопоставляются конкретным моделям и обозначают изменения, внесенные, но не закоммиченные во внешнее хранилище. Учтите, что коллекция записей очищается только в случае принятия данных (commit) или отката (rollback), но остается неизменной при загрузке новых данных. Разделение данных на модели (Model) и их записи (Record) имеет преимущество в плане возможности отслеживания вносимых изменений для каждой порции данных и позволяет составлять сколь угодно сложные алгоритмы синхронизации между локальным и удаленным хранилищем.
Основные типы хранилищей: ListStore (для хранения списка данных), GrouppingStore (список данных с возможностью группировки), TreeStore (древовидное хранилище данных).
Пример работы с хранилищем:
ListStore store = new ListStore();
BaseModel model1 = new BaseModel();
model1.set("id", 100);
// помещаем объект в хранилище
store.add(model1);
// выполняем поиск по любому полю модели
// будет возвращен первый объект, который удовлетворяет критериям поиска
BaseModel found = store.findModel("id", 100);
// полностью очищаем содержимое хранилища
store.removeAll()
Практическое применение - ComboBox
правитьКомпонент ComboBox - это элемент "выпадающий список", который позволяет пользователю приложения произвести выбор какого либо значения на основе заранее сформированного списка данных. Обычно он используется в формах, однако так как является самодостаточным элементом, никто не запрещает поместить его отдельно от других элементов ввода (например, в углу окна для быстрого выбора языка интерфейса).
Главной отличительной особенностью ComboBox от других элементов ввода данных является то, что его нельзя использовать без создания специального хранилища значений, из которого он их и подгружает: напрямую добавлять значения в список невозможно. Тип хранилища - ListStore.
Простой пример:
// создание нового объекта ComboBox
ComboBox combo = new ComboBox();
// создание нового объекта для хранения списка моделей
ListStore store = new ListStore();
// первый объект данных: свойство text со значением "N/A"
BaseModel model1 = new BaseModel();
model1.set("text", "N/A");
store.add(model1);
// второй объект данных: свойство text со значением "My Item"
BaseModel model2 = new BaseModel();
model2.set("text", "My Item");
store.add(model2);
// подключение хранилища к ComboBox
combo.setStore(store);
// добавление визуального элемента к DOM-модели приложения
RootPanel.get().add(combo);
По умолчанию в ComboBox отображается содержимое свойства text модели, это поведение можно изменить с помощью метода setDisplayField("name"). Результат выбора пользователем в текстовом виде можно получить через метод getRawData():
combo.addSelectionChangedListener(new SelectionChangedListener<BaseModel>() {
@Override
public void selectionChanged(SelectionChangedEvent<BaseModel> se) {
Info.display("Выбрано", combo.getRawValue());
}
});
Или как объект модели с получением ее необходимых свойств:
Info.display("Выбрано", (String)comb.getValue().get("text"));
Для получения выделенного значения можно использовать методы getValue() или getSelectedRecord().
добавить примеров
Из программного кода можно устанавливать выбранное значение по умолчанию с помощью методов setValue(объект_модели) или setRawValue("текст"). В первом случае присваиваемое значение будет проходить валидацию, а во втором - устанавливаться как есть.
добавить примеров
Глава X. Фишки для продвинутых товарищей
правитьРабота в Super Dev Mode
правитьНачиная с версии GWT 2.5 в дополнении к development и production mode (режим разработки, в котором происходит динамическая трансляция кода из Java в JavaScript, и режим выполнения, в котором наше приложение на отдельном сервере будет доступно конечным пользователям) появился Super Dev Mode.
Наверное многих из вас раздражала необходимость длительного ожидания трансляции после внесения изменений в файл проекта и нажатия кнопки F5 для его обновления, чтобы просто проверить, правильно ли отрабатывает новый код. Кроме того, многие заметили, что динамика компонентов в development mode гораздо хуже, чем в production варианте, когда ваш компьютер не занимается бесчисленными компиляциями.
https://developers.google.com/web-toolkit/articles/superdevmode
Алгоритм перехода на Super Dev Mode такой:
1. Добавляем в наш модуль .gwt.xml проекта несколько новых строчек до закрывающего тега </module>:
<add-linker name="xsiframe"/>
<set-configuration-property name="devModeRedirectEnabled" value="true"/>
<!-- enable source maps -->
<set-property name="compiler.useSourceMaps" value="true" />
2. Запускаем стандартный процесс компиляции всего модуля в JavaScript с помощью соответствующего мастера в Eclipse GWT Compile Project...
3. Запускаем приложение как обычно это делали на предыдущих этапах и переходим в наш web-браузер по ссылке http://127.0.0.1:8888/Remember.html?gwt.codesvr=127.0.0.1:9997. Сейчас мы видим наше приложение через development mode. Убираем из Url в браузере строчку ?gwt.codesvr=127.0.0.1:9997 (все параметры вместе со знаком вопроса) и кликаем по новому адресу. Теперь мы видим свое приложение в production mode.
4. Запускаем сервер SuperDevMode.
4a. Вариант запуска из командной строки: переходим в каталог нашего проекта и запускаем:
java -classpath $GWT_HOME/gwt-codeserver.jar:$GWT_HOME/gwt-dev.jar:$GWT_HOME/gwt-user.jar:app:./lib/*:$GXT_HOME/* com.google.gwt.dev.codeserver.CodeServer -bindAddress 127.0.0.1 -port 1234 -src src org.wikibooks.MyModule
Обратите внимание, что переменные среды GWT_HOME и GXT_HOME должны указывать на пути, по которым расположены библиотеки GWT и GXT соответственно.
4b. Вариант для запуска из Eclipse:
http://stackoverflow.com/questions/11356714/getting-started-with-the-superdevmode
5.
Приложение 1. Таблица соответствий CSS и JavaScript стилей
правитьстиль в CSS | стиль в JavaScript |
---|---|
background | background |
background-attachment | backgroundAttachment |
background-color | backgroundColor |
background-image | backgroundImage |
background-position | backgroundPosition |
background-repeat | backgroundRepeat |
border | border |
border-bottom | borderBottom |
border-bottom-color | borderBottomColor |
border-bottom-style | borderBottomStyle |
border-bottom-width | borderBottomWidth |
border-color | borderColor |
border-left | borderLeft |
border-left-color | borderLeftColor |
border-left-style | borderLeftStyle |
border-left-width | borderLeftWidth |
border-right | borderRight |
border-right-color | borderRightColor |
border-right-style | borderRightStyle |
border-right-width | borderRightWidth |
border-style | borderStyle |
border-top | borderTop |
border-top-color | borderTopColor |
border-top-style | borderTopStyle |
border-top-width | borderTopWidth |
border-width | borderWidth |
clear | clear |
clip | clip |
color | color |
cursor | cursor |
display | display |
filter | filter |
font | font |
font-family | fontFamily |
font-size | fontSize |
font-variant | fontVariant |
font-weight | fontWeight |
height | height |
left | left |
letter-spacing | letterSpacing |
line-height | lineHeight |
list-style | listStyle |
list-style-image | listStyleImage |
list-style-position | listStylePosition |
list-style-type | listStyleType |
margin | margin |
margin-bottom | marginBottom |
margin-left | marginLeft |
margin-right | marginRight |
margin-top | marginTop |
overflow | overflow |
padding | padding |
padding-bottom | paddingBottom |
padding-left | paddingLeft |
padding-right | paddingRight |
padding-top | paddingTop |
page-break-after | pageBreakAfter |
page-break-before | pageBreakBefore |
position | position |
float | styleFloat |
text-align | textAlign |
text-decoration | textDecoration |
text-decoration: blink | textDecorationBlink |
text-decoration: line-through | textDecorationLineThrough |
text-decoration: none | textDecorationNone |
text-decoration: overline | textDecorationOverline |
text-decoration: underline | textDecorationUnderline |
text-indent | textIndent |
text-transform | textTransform |
top | top |
vertical-align | verticalAlign |
visibility | visibility |
width | width |
z-index | zIndex |