gwt

Пример Fast Container

Автор: Ренат Ахмеров

Этот пример демонстрирует работу FastContainer, описанного в нашей статье “GWT: улучшаем производительность”. Цель примера – рассмотреть свойства setInnerHtml в деталях, и дать временную оценку результата (в миллисекундах). Помимо этого, я остановлюсь на некоторых важных аспектах реализации FastContainer. Работающий пример можно найти на http://www.stravati.com/examples/fastcontainer и скачать с http://www.stravati.com/examples/sources/fast-container-demo-src.zip.

Страница примера состоит из 2 частей – левой и правой. Здесь отображаются тестовые компоненты. Для отрисовки компонентов в левой части мы использовали FlexTable в качестве контейнера для мелких элементов, что является наиболее часто используемым способом при разработке на GWT. Этот тип компонента был позаимствован из нашего прошлого проекта, где он отвечал за несколько критических задач. В простейшем случае он состоит из чекбокса (завершено / не завершено), заголовка (описания) задачи, и двух дат – начального и конечного сроков. Всё это можно увидеть на рисунке ниже.

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

private FlexTable getFrame() { if (frame == null) { frame = new FlexTable(); frame.setStyleName(“item”); frame.setCellSpacing(0); frame.setWidget(1, 1, getLayout()); frame.setWidget(2, 2, new Label(“”)); FlexCellFormatter formatter = frame.getFlexCellFormatter(); formatter.setStyleName(0, 0, “item-leftTop”); formatter.setStyleName(0, 1, “item-top”); formatter.setStyleName(0, 2, “item-rightTop”); formatter.setStyleName(1, 0, “item-leftMiddle”); formatter.setStyleName(1, 1, “item-contentCell”); formatter.setStyleName(1, 2, “item-rightMiddle”); formatter.setStyleName(2, 0, “item-leftBottom”); formatter.setStyleName(2, 1, “item-bottom”); formatter.setStyleName(2, 2, “item-rightBottom”); } return frame; } private FlexTable getLayout() { if (layout == null) { layout = new FlexTable(); layout.setWidth(“100%”); layout.setCellSpacing(0); layout.setWidget(0, 2, getKickOffLabel()); layout.setWidget(1, 2, getDoByLabel()); } return layout; }

Скругленные углы здесь создаются как фоновые изображения для ячеек таблицы в соответствующих классах CSS. Полные таблицы стилей можно найти в исходном коде. А здесь приведён один из методов управления компонентом:

public void setTitleWidget(Widget widget) { getLayout().setWidget(0, 1, widget); }

Этот метод, по сути, описывает, где внутри контейнера будет располагаться заглавный виджет. Также существуют методы и для других элементов (чекбокса и дат). В нашем простейшем приложении такие понятные компоненты выведены наружу и управляются entry-point классом FCEntryPoint следующим способом:

StandardComponent aComponent = new StandardComponent(); Label title = new Label(“Some title here”); title.setStyleName(“item-title-label”); aComponent.setTitleWidget(title); aComponent.setCheckboxWidget(new CheckBox()); aComponent.setKickOffWidget(new Label(“Today”)); aComponent.setDoByWidget(new Label(“Tomorrow”));

Естественно, для управления компонентами (обработчики событий, стили, управление фокусом) будет задействован более сложный код, и скорее всего он будет инкапсулирован внутри класса, обеспечивающего интерфейс для работы с компонентами на высоком уровне (напр. значение Date напротив Label, значение boolean напротив Checkbox,и т.д). Но в данном случае мы преследуем цель создать простое приложение с упором на FastContainer, не перегружая код добавочными классами.
Существует очень много занимательных моментов, связанных с компонентом на основе FastContainer, играющем ключевую роль в жизненном цикле компонента. В этом классе не так уж много кода, поэтому приведу его полностью:

public class FastContainer extends Widget { private Context context; public FastContainer(Pattern pattern) { this.context = new Context(); Element creatorElement = DOM.createDiv(); DOM.appendChild(RootPanel.getBodyElement(), creatorElement); DOM.setInnerHTML(creatorElement, pattern.generatePattern(context)); Element elem = DOM.getFirstChild(creatorElement); setElement(elem); } public void setWidget(String fieldName, Widget widget) { Parameter param = context.getParameter(fieldName); if (param == null) { throw new RuntimeException(“There is no such parameter in context!”); } Element element = DOM.getElementById(param.getId()); if (element != null) { DOM.appendChild(element, widget.getElement()); DOM.setEventListener(widget.getElement(), widget); } } }

Для обеспечения возможности работы с обычным HTML в компоненте FastContainer объединяется с Pattern. Последний отвечает за генерацию исходного HTML-кода и создание набора шаблонных параметров внутри Context. Если присмотреться к коду повнимательнее, можно заметить весьма сложно организованный алгоритм, инициализирующий DOM-элемент компонента. Если делать это, просто направляя внутренний HTML внутрь элемента, а потом вызывая DOM.getElementById(param.getId()) , оно отказывается работать по невыясненной причине. Поэтому нужно использовать обходной метод для того, чтобы обеспечить возможность прямого присоединения исходного HTML-кода. Также лучше закомментировать последнюю строку в вышеуказанном коде (DOM.setEventListener(widget.getElement(), widget)). Этот вызов нужен для восстановления всех обработчиков событий, установленных для виджета. Все панели GWT, по сути, делают то же самое, инчае новые виджеты теряли бы обработчики событий. Теперь взглянем на код ItemPattern, чтобы понять его роль в общей структуре.

Класс ItemPattern реализует интерфейс Pattern только одним способом. Этот способ:
String generatePattern(Context context);
Несложно догадаться, что результатом этого метода должен являться исходный HTML-код компонента. Занимательная вещь: параметр context. Основной класс FastContainer перекладывает на Pattern работу по созданию параметров, относящихся к исходному HTML-коду. Каждый из этих параметров может быть рассмотрен как способ доступа к некоему элементу в DOM.

public class ItemPattern implements Pattern { public String generatePattern(Context context) { StringBuffer html = new StringBuffer(); html.append(“<table class=”item” cellspacing=”0″>”); html.append(“<tbody><tr>”); html.append(“<td class=”item-leftTop”> </td>”); html.append(“<td class=”item-top”> </td>”); html.append(“<td class=”item-rightTop”> </td>”); html.append(“</tr>”); html.append(“<tr>”); html.append(“<td class=”item-leftMiddle”> </td>”); html.append(“<td class=”item-contentCell”>”); int id = Parameter.nextId(); Parameter checkBoxParam = new Parameter(“checkBox”, id); Parameter titleParam = new Parameter(“title”, id); Parameter kickOffParam = new Parameter(“kickOff”, id); Parameter doByParam = new Parameter(“doBy”, id); context.addParameter(checkBoxParam); context.addParameter(titleParam); context.addParameter(kickOffParam); context.addParameter(doByParam); html.append(“<table cellspacing=”0″>”); html.append(“<tbody><tr>”); html.append(“<td> </td>”);

Этот фрагмент кода класса ItemPattern показывает суть процесса генерации исходного HTML. Сам по себе HTML генерируется с помощью класса StringBuffer , входящего в состав GWT. Каждый элемент HTML (превращающийся в DOM-элемент после присоединения), предназначенный для слияния с виджетом, ассоциируется с соответствующим Parameter, принимающим имя элемента (значение, необходимое для доступа к элементу методом setWidget(String fieldName, Widget widget) в FastContainer) и числовое значение, уникальное для каждого экземпляра FastContainer. В нашем случае это реализуется статическим методом nextId в классе Parameter, но в реальном приложении возможен и более сложный подход. Важно заметить, что Parameter генерирует уникальный идентификатор для поля. Например, если у нас Parameter описан как new Parameter(“title”, 12), то метод getId() возвратит “title_12”, что и является уникальным значением для этого поля в DOM. Число 12 в данном случае является номером экземпляра FastContainer. Т.о., когда мы вызываем getParameter(“title”) из экземпляра Context, то получаем этот самый уникальный идентификатор.

Это были важные аспекты реализации FastContainer. Для ещё более детального ознакомления Вы можете взять полный исходный код.

Ниже приведена таблица результатов теста для трёх популярных браузеров.

браузер стандартно, ms FastContainer, ms
пустые элементы с управлением пустые элементы с управлением
Firefox 2.0.0.12 (Windows) 730 1300 265 935
Internet Explorer 6 702 1100 171 686
Safari 3.0.3 (Windows) 280 436 94 250

Все результаты получены на одном и том же ПК. Вдаваться в его технические характеристики нет смысла, поскольку наша цель – продемонстрировать скоростные различия двух способов создания компонентов в GWT при прочих равных. При взгляде на результаты несложно убедиться, что время, затраченное на создание и присоединение элементов управления в обоих случаях практически одинаково. 570 и 670 мсек для Firefox, 398 и 515 мсек для IE, 156 мсек в обоих случаях для Safari. Незначительная разница (в пределах 100 мсек) может быть списана на использование DOM.getElementById(param.getId()) в FastContainer для поиска целевого элемента. Предположим, что этот метод начинает работать медленнее с ростом числа элементов в DOM. Значит, должны быть способы улучшить ситуацию, начиная поиск целевого элемента с компонентных элементов. Интересным в таблице результатов представляется то, что пустые компоненты (только фреймы, без управляющих элементов) создаются в несколько раз быстрее. Особенно это справедливо для IE (4.1 раза). Это означает, что если у нас будут компоненты со сложной внутренней структурой (которая может быть реализована с помощью FastContainer как HTML-код), то разница в скорости рендеринга будет ещё более впечатляющей.

The results of this simple test showed performance benefits of using setInnerHtml for building GWT components. As we already said before, rendering speed can be essentially increased by using this approach. FastContainer pattern is one of the concrete techniques for building components based on this phenomenon that we can offer to use in real applications development. Of course, it is possible to use setInnerHtml as is to build something you may want, but talking about the pattern we emphasize that it is exactly a container supposed to have an ability to contain GWT widgets inside. It also allows accessing these widgets during the component’s life.

В завершение хочется заметить, что идея FastContainer не так уж и нова. Уже существуют такие вещи, как HTMLTemplateWidget, собирающий виджеты из исходного HTML-кода. В будущих релизах команда GWT также анонсировала GWT Declarative UI. Ждём, когда можно будет опробовать это всё вживую. Цель этого примера – осветить вопрос выигрыша в производительности и предложить работающее техническое решение, годное для использования в разработке на GWT.

Вам понравилась статья, и Вы хотите заказать у нас разработку Вашего проекта? Свяжитесь с нами прямо сейчас!

GWT: улучшаем производительность

Авторы: Ренат Ахмеров, Алексей Харламов

С момента своего появления в августе 2006 года Google Web Toolkit (GWT) снискал значительную популярность в среде веб-разработчиков. Эта технология позволяет создавать интерактивные и функциональные приложения при помощи удобных и технологичных инструментов (Eclipse, Maven, SVN). Более того, GWT нацелен на создание кроссбраузерных приложений при помощи изоляции платформо-зависимых элементов.

Наша команда работает с GWT начиная с версии 1.1. Несмотря на общее позитивное впечатление, мы столкнулись с рядом проблем производительности. Эти ограничения по своей сути унаследованы от алгоритмов HTML и реализации браузеров. В этой статье мы попытаемся отыскать способы обхода таких ограничений при помощи манипулирования кодом HTML.

Прежде всего, вкратце опишу наше приложение. Система предназначена для управления рабочим графиком и командной работой. Большую часть времени пользователь видит дерево заданий. Компоненты GWT идеально справляются с задачей до тех пор, пока дерево видимых элементов не начинает разрастаться. Некоторые пользователи имеют в своем пространстве до нескольких тысяч заданий. И рендеринг дерева занимает в этом случае десятки секунд.

Другая проблема – обновление рабочей области браузера. Большинство браузеров не обновляет HTML-основу при выполнении JavaScript. Когда приложение обращается к GWT-компонентам – браузер зависает. Запросы пользователя при этом перестают выполняться. Таким образом медленный рендеринг компонентов становится настоящей проблемой. К счастью, существует простое и широко известное решение.

Любое длительное задание должно быть разбито на фрагменты меньшего размера при помощи DeferredCommand. Таким образом достигается подобие параллельного выполнения.

До тех пор, пока время выполнения одного DeferredCommand не превышает 0,1-0,2 сек., задержки и скорость ответа системы комфортны для пользователя. Но это решение не всемогуще. Оно смягчает проблему, но рендеринг сложных данных всё ещё может ощутимо “тормозить”. Проблема заключается во внутренней компонентной модели фреймворка.

В составе GWT много виджетов, работающих как контейнеры. Часто контейнеры используются, чтобы форматировать другие компоненты атрибутами. Выяснено, что для присоединения к документу своих дочерних объектов контейнеры используют DOM.appendChild(). Таким образом поступают, к примеру,FlexTable и VerticalPanel. Также стандартные компоненты активно используют вызовы DOM.createElement(). Эти операции достаточно быстры при небольшом числе компонентов, но при возрастании их числа наблюдается значительное замедление.

Наши тесты показали, что DOM.setInnerHtml() работает намного быстрее по сравнению со связкой DOM.createElement()/DOM.appendChild(). Это вполне объяснимо: innerHtml использует оптимизированный нативный код сборки DOM внутри браузера. Мы решили переделать компонент Tree под использование innerHtml. Обновленное дерево стало быстрее в сотни раз! По видимому, динамические операции DOM внутри браузеров весьма несовершенны. Для решения этой проблемы мы нашли стандартный путь реализации компонентов интерфейса, критичных к быстродействию, и назвали его FastContainer.

Структура FastContainer

Идея изначально проста: использовать setInnerHhtml для создания фрагментов DOM и присоединять компоненты GWT к соответствующим элементам. Но представлять объекты DOM в строковом виде не очень-то удобно, да и setInnerHtml более эффективен на больших фрагментах HTML-кода. Поэтому имеет смысл вставлять группу элементов/компонентов за раз.

На рисунке выше приведен пример такой группы. Компонент может быть вирутально поделён на HTML-структуру и набор элементов управления (чекбокс и текстовые метки).

Под структурой здесь мы понимаем фрейм с серой заливкой и скругленными углами. Она также ответственна за выравнивание элементов. Чтобы реализовать всё это стандартными средствами – потребуется немало потрудиться. Вначале нужно создать экземпляр структуры – например, с помощьюFlexTable или другой панели. Затем полностью сконфигурировать объекты и расположить их по строкам/ячейкам (включая ячейки с закругленными углами), задать стили ячеек, вставить вкругления углов и элементы управления. Всё это должно выполняться динамически одно за другим. Каждый этап вызывает один или несколько методов createElement()/appendChild(). Но то же самое можно сделать в текстовом представлении одним вызовомDOM.setInnerHtml(). А HTML-структура может быть быстро создана в любом редакторе.

В рамках такого подхода FastContainer был создан для обеспечения комфортной работы с компонентами, представленными как HTML-фрагменты. Другими словами, он позволяет создать Widgetиз фрагмента HTML-текста и предоставляет интерфейс для взаимодействия с виджетами внутри. Исходная идея принадлежит Sergey Dryganets , который предложил способ абстрагирования и создал первую реализацию, названную LightWidget. Позже мы усовершенствовали её и создали FastContainer.

Основная идея – изоляция HTML-источника внутри Pattern. В него войдёт всё, ответственное за построение HTML-части. Класс FastContainer является предпочтительным компонентом, создающим себя из исходного контекста. Также он предоставляет интерфейс для операций с дочерними объектами.

На этапе рендеринга каждый экземпляр FastContainer использует Pattern для генерации HTML-фрагмента, который затем передается в браузер. В процессе генерации экземпляр ConcretePattern наполняет Context данными о связывании элементов. Таким образом, контейнер может быстро найти нужные элементы.

Очень важно понимать, что Context является мостом между контейнером и его представлянием в DOM браузера. Он позволяет проводить динамическое обновление внутренней структуры и быстро находит места для вставки дочерних компонентов.

Т.к. внутри документа может быть несколько экземпляров одного и того же элемента, для генерации уникальных номеров мест компонентов HTML нужно использовать IdManager. Но этот процесс спрятан внутри элемента, клиенту выводятся только имена (строковые или иные). Используя эти имена, клиент может:

·вставить текст или HTML-код в нужное местоr

·вставить виджет в назначенную для него позицию

·создать дочерний элемент Widget, используя внутренний элемент

За и против

В использовании такого подхода есть 2 преимущества. Во-первых, есть возможность реализовывать часть компонентов в виде HTML-кода. Это проще и понятнее, чем использовать объектную модель GWT. Во-вторых, благодаря использованию DOM.setInnerHtml (и присоединению сразу нескольких структур за раз вместо их пошагового “сшивания”) значительно возрастает скорость рендеринга.

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

Также несколько неудобным представляется управление HTML-кодом внутри Java-источников. Существуют методы ослабления этого неудобства при помощи внешних HTML-шаблонов, но все они снижают скорость рендеринга и загрузки.

На данный момент мы видим только один компромисс – поддержка создания HTML-структур внутри компилятора GWT по типу “предподготовленных” ресурсов. Это позволит:

·разрабатывать HTML-шаблоны в отдельных файлах, но хранить их внутри основного кода JavaScript-приложения

·избегать ручного труда при генерации структур и работе с id

·увеличить производительность

Мы будем рады обсудить способы усовершенствования компилятора GWT с сообществом.

Вам понравилась статья, и Вы хотите заказать у нас разработку проекта? Свяжитесь с нами прямо сейчас!

Подводные камни в планировании разработки на GWT

В последнее время к Google Widget Toolkit приковано внимание веб-разработчиков со всего мира. GWT – это прекрасная технология для AJAX-разработки. Она позволяет избавиться от многих проблем, связанных с кросс-браузерной разработкой, взаимодействием с пользователем и циклом разработки.

Библиотека предоставляет невиданные ранее возможности для разработки Web 2.0 – приложений с высокой степенью интерактивности. Но в этом кроется подвох.

Зачастую интерфейс веб-приложения более лёгок для разработки, чем интерфейс десктопных приложений, и тому есть 2 причины. Во-первых, система HTML/CSS предоставляет большое число инструментов для визуального воплощения дизайнерской мысли. Нет нужды многократно проходить цикл сборка/запуск/отладка для каждой страницы. Во-вторых, HTML обеспечивает намного меньше интерактивности, чем заключено в браузере. Поэтому на разработчика ложится меньше задач по отладке.

 

Необычность AJAX-разработки на GWT заключается, в том числе и в том, что оба эти фактора претерпевают изменения. GWT-приложения очень интерактивны и имеют компонентную структуру. Поэтому исходные ожидания лёгкости изменения вида веб-приложений вряд ли оправдаются. Теперь для изменения внешнего вида потребуются значительные усилия. К тому же дизайн компонентов ограничен рамками имеющегося набора настроек. Навороченное поле для ввода текста, созданное веб-дизайнером, может причинить кучу неудобств команде разработчиков. Прямо как в старые добрые времена разработки десктопных интерфейсов.

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

Другое свойство GWT-приложений – интерактивность. Это одновременно и хорошо, и проблемно.
Как было сказано выше, приложения Web 1.0 практически не обеспечивали интерактивного взаимодействия с пользователем. И первым шагом веб-дизайнеров была отрисовка статических частей интерфейса. Здесь такое уже невозможно. Разработчикам нужно конкретное поведение приложения. А поведение приложения – это часть кода. Его не так-то просто изменить. Поэтому нужно иметь в запасе всё необходимое для отладки и доводки интерфейса.

Подытожим вышесказанное. Держите разработчиков и дизайнеров как можно ближе друг к другу. Может, поэтому Google Apps и работают так добротно, что интерфейс создавался в близком сотрудничестве с разработчиками.

Об авторе:

Alexey Kharlamov. Photo Алексей Харламов – эксперт в области разработки приложений на Java. Работает с GWT-проектами с самого начала развития GWT.