Майкл Галпин
В данной статье, мы немного переделаем приложение, созданное в предыдущей части. Как вы помните, приложение позволяет управлять списком исполнителей и их альбомами, и в смысле функциональности, мы ничего менять не будем. Мы лишь поменяем реализацию путем смешивания GWT и XForms на одной странице. Вы увидите насколько просто начать использовать GWT на уже существующей Web-странице. Вы сможете динамически загружать данные на страницу, используя вызовы Ajax через GWT, а затем также динамически создавать модель XForms через интерфейс JSNI, поддерживаемый GWT. Все это позволит упростить нашу страницу. Более того, в целях дальнейшего упрощения даже элементы управления XForms можно будет создать через GWT JSNI. Кроме этого, дополнительным преимуществом использования GWT на страницах XForms является уменьшение размера страниц, что ускоряет их загрузку и отображение в браузере.
В данной статье используется GWT версии 1.4 и подключаемый модуль Mozilla XForms версии 0.8. Модуль Mozilla XForms работает с любым браузером на основе Mozilla, например, Firefox или Seamonkey. Использование GWT предполагает определенные знания Java, а так же Web-технологий, таких как HTML и CSS. Кроме этого, в статье широко используется JavaScript. Наконец, поскольку технология XForms разработана в соответствии с парадигмой MVC (Model-View-Control), желательно понимание принципов MVC. В то же время опыт использования GWT или XForms хотя и будет полезным, но не является обязательным.
До этого момента мы использовали XForms для отображения данных, хранящихся в модели внутри страницы. Модель загружалась динамически через скриптлет на JSP, который запрашивал и фильтровал данные, а также записывал их на страницу в виде XML. Теперь же мы сделаем саму XForms-страницу еще более динамической, а именно: вместо записи экземпляров данных внутрь страницы, мы будем асинхронно запрашивать их с сервера, а затем добавлять внутрь существующей модели на странице. Ajax-вызовы будут делаться с помощью GWT, а последующее изменение модели XForms - через JSNI.
Теперь мы возьмем нашу страницу, отображающую альбомы с помощью XForms, и начнем использовать на ней GWT. Первый вопрос заключается в том, как применить GWT к уже существующей странице. На самом деле, это очень просто. Все, что требуется - это добавить на страницу ссылку на JavaScript-библиотеку, созданную компилятором GWT при преобразовании вашего кода на Java в JavaScript. Подобная библиотека создается одна на каждый модуль. Иными словами, надо просто добавить строчку, как показано в листинге 1.
Листинг 1. Использование GWT на странице для показа альбомов.
<xhtml:script language="text/javascript"
src="org.developerworks.rockstar.RockStarMain.nocache.js"></xhtml:script>
|
Зачастую разработчики удивляются простоте добавления GWT к существующим страницам. Эта легкость была одной из задач при создании GWT. Несмотря на то, что большинство примеров использования GWT демонстрируют "свежие" приложения, т.е. изначально написанные на GWT, унаследованные (legacy) проекты встречаются на практике ничуть не реже, и от GWT изначально требовалось быть полезной в обоих случаях. Не забывайте, что весь код на GWT - это по сути JavaScript, так что интеграция и должна быть простой. Теперь, после добавления GWT к странице, можно начинать писать GWT-код, а точнее - Java-код, который будет скомпилирован в JavaScript.
Во второй части серии мы использовали GWT для асинхронной загрузки списка исполнителей. Другими словами, мы создали страницу, а затем запрашивали список исполнителей, используя Ajax-запросы. Теперь мы проделаем то же самое, но с альбомами. Для этого нам понадобится сервис для загрузки альбомов через Ajax в GWT.
Для начала создадим интерфейс сервиса, как показано в листинге 2.
Листинг 2. Интерфейс AlbumService
package org.developerworks.rockstar.client;
import com.google.gwt.user.client.rpc.RemoteService;
public interface AlbumService extends RemoteService{
public Album[] getAlbumsForArtist(int artistId);
public void addAlbum(Album newAlbum);
}
|
Этот сервис очень похож на тот, что мы разработали во второй части серии. Как и в том случае, нам понадобится асинхронная версия интерфейса, показанная в листинге 3.
Listing 3. Асинхронная версия интерфейса сервиса
package org.developerworks.rockstar.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface AlbumServiceAsync {
public void getAlbumsForArtist(int artistId, AsyncCallback callback);
public void addAlbum(Album newAlbum, AsyncCallback callback);
}
|
И наконец, надо создать серверную реализацию сервиса для обработки клиентских вызовов через HTTP. Реализация должна наследовать стандартный GWT-класс RemoteServiceServlet
, как показано в листинге 4.
Листинг 4. Реализация AlbumService
package org.developerworks.rockstar.server;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.developerworks.rockstar.client.Album;
import org.developerworks.rockstar.client.AlbumService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class AlbumServiceImpl extends RemoteServiceServlet implements AlbumService {
private static final long serialVersionUID = -2706402745094297460L;
private Map<Integer,List<Album>> albumCache;
private AlbumDao dao;
public AlbumServiceImpl(){
this.dao = new AlbumFileDao();
List<Album> allAlbums = this.dao.getAllAlbums();
// initialize cache
int size = allAlbums.size();
this.albumCache = new HashMap<Integer, List<Album>>(size);
for (Album album : allAlbums){
int artistId = album.getArtistId();
List<Album> albums = this.albumCache.get(artistId);
if (albums == null){
albums = new ArrayList<Album>();
this.albumCache.put(artistId, albums);
}
albums.add(album);
}
}
public void addAlbum(Album newAlbum) {
int artistId = newAlbum.getArtistId();
List<Album> albums = this.albumCache.get(artistId);
if (albums == null){
albums = new ArrayList<Album>();
this.albumCache.put(artistId, albums);
}
albums.add(newAlbum);
List<Album> all = this.getAllAlbums();
this.dao.saveAlbums(all);
}
public Album[] getAlbumsForArtist(int artistId) {
List<Album> albums = this.albumCache.get(artistId);
if (albums == null){
return null;
}
Album[] array = new Album[albums.size()];
array = albums.toArray(array);
return array;
}
private List<Album> getAllAlbums(){
List<Album> allAlbums = new ArrayList<Album>();
for (int artistId : this.albumCache.keySet()){
List<Album> albums = this.albumCache.get(artistId);
allAlbums.addAll(albums);
}
return allAlbums;
}
}
|
Как и ранее, мы использовали объект доступа к данным (Data Access Object - DAO) для абстрагирования от особенностей физической организации данных, таких, как доступ к файловой системе, разбор XML и т.д. В будущем это позволит легко заменить нашу упрощенную реализацию, работающую с файлами XML, на более распространенный вариант, использующий базу данных. Теперь можно начинать вызывать новый сервис через GWT.
Весь код, находящийся в пакете org.developerworks.rockstar.client
, будет скомпилирован в JavaScript и доступен на любой странице, как показано в листинге 1. Таким образом, надо просто создать Java-класс для дальнейшего использования на странице альбомов. Исходный код класса показан в листинге 5.
Листинг 5. Класс AlbumLib
package org.developerworks.rockstar.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
public class AlbumLib {
public void loadAlbums(int artistId){
AlbumServiceAsync albumService = this.getAlbumService();
AsyncCallback callback = new AsyncCallback(){
public void onFailure(Throwable caught) {
// TODO Auto-generated method stub
}
public void onSuccess(Object result) {
Album[] albums = (Album[]) result;
for (int i=0;i<albums.length;i++){
addAlbumToModel(albums[i]);
}
refreshXformsModel();
}
};
albumService.getAlbumsForArtist(artistId, callback);
}
private AlbumServiceAsync getAlbumService(){
AlbumServiceAsync albumService = (AlbumServiceAsync)
GWT.create(AlbumService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) albumService;
String moduleRelativeUrl = GWT.getModuleBaseURL() + "albumService";
endpoint.setServiceEntryPoint(moduleRelativeUrl);
return albumService;
}
private native void addAlbumToModel(Album album)/*-{
var model = $doc.getElementById("albums");
var instance = model.getInstanceDocument("albumData");
var dataElement = instance.getElementsByTagName("Data")[0];
// create the new album node
var newAlbumElement = instance.createElement("Album");
var titleElement = instance.createElment("Title");
titleElement.appendChild(instance.createTextNode(album.getTitle()));
newAlbumElement.appendChild(titleElement);
var yearElement = instance.createElement("Year");
yearElement.appendChild(instance.createTextNode(album.getYear()));
newAlbumElement.appendChild(yearElement);
dataElement.appendChild(newAlbumElement);
}-*/;
private native void refreshXformsModel()/*-{
var model = $doc.getElementById("albums");
model.rebuild();
model.recalculate();
model.refresh();
}-*/;
}
|
В этом классе много интересного. Во-первых, он предоставляет два метода для удаленного вызова ранее созданного сервиса AlbumService
, аналогично тому, как в предыдущей части вызывался сервис ArtistService
. Как и ранее, для обработки ответов сервиса на асинхронные запросы, используются функции обратного вызова. В данном случае, они вызывают два других метода - addAlbumToModel()
и refreshXformsModel()
.
Последние являются собственными (native) методами JavaScript, как и те, что вы видели в примерах в первой части серии.
Метод addAlbumToModel()
работает непосредственно c объектами в JavaScript, представляющими модель XForms, позволяя таким образом получить доступ к экземплярам данных в XML. В первой статье мы делали похожие вещи, но через JavaScript, используя методы вроде document.getElementById(...)
. Теперь же мы работаем через GWT, поэтому необходимо использовать переменную $doc для обращения к объекту документа, который неявно определен в JavaScript. Получив ссылку на этот объект, можно вызывать стандартные методы DOM для добавления элементов типа Album в XML-документ. Таким образом, метод addAlbumToModel()
вызывается в цикле, по разу на каждый добавляемый альбом, полученный с сервера. После добавления всех альбомов вызывается метод refreshXformsModel()
. Аналогично предыдущему, это собственный метод, имеющий доступ к модели данных XForms и использующий ее API для обновления элементов управления, привязанных к модели.
Все что осталось сделать - это убедиться, что метод loadAlbums вызывается при загрузке страницы. Для этого надо чуть изменить страницу Albums.jsp, как показано в листинге 6.
Листинг 6. Вызов GWT JavaScript из JSP
<?xml version="1.0" encoding="UTF-8"?>
<xhtml:html xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xhtml:head>
<xhtml:title>Albums</xhtml:title>
<xhtml:script language="text/javascript"
src="org.developerworks.rockstar.RockStarMain.nocache.js">
</xhtml:script>
<xforms:model id="albums" xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<xforms:instance id="albumData" xmlns=""/>
</xforms:model>
</xhtml:head>
<xhtml:body onload="loadAlbums(<%=request.getParameter("artistid") %>)">
<xhtml:div id="albumList">
<xforms:repeat id="repeatItem"
nodeset="instance('albumData')/Data/Album"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<xhtml:div>
<xforms:output ref="Title"
xmlns="http://ww.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<xforms:label
xmlns:xforms="http://www.w3.org/2002/
xforms">Title:</xforms:label>
</xforms:output>
<xforms:output ref="Year"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<xforms:label
xmlns:xforms="http://www.w3.org/2002/
xforms">Year:</xforms:label>
</xforms:output>
</xhtml:div>
</xforms:repeat>
</xhtml:div>
</xhtml:body>
</xhtml:html>
|
Как видите, достаточно просто вызвать GWT-метод после окончания загрузки страницы. К тому же этот метод позволил нам избавиться от большей части ранее использовавшегося скриптлета, оставив только крошечный фрагмент для передачи параметра artistId. Большая часть оставшегося на странице кода служит исключительно для создания элементов интерфейса. Далее посмотрим, как и их можно создавать программно через GWT.
Вы уже видели, как получить доступ к модели XForms и ее данным, используя GWT и JSNI. Теперь мы сделаем следующий шаг и начнем использовать GWT и JSNI для динамического создания элементов управления XForms. Первым делом заменим элемент xforms:repeat на фрагмент кода на JavaScript, показанный в листинге 7.
Листинг 7. Динамическое создание элементов управления XForms через интерфейс JSNI в GWT
private native void createControls()/*-{
var xfNs = "http://www.w3.org/2002/xforms";
// get the container div
var container = $doc.getElementById("albumList");
var repeater = $doc.createElementNS(xfNs,"xforms:repeat");
repeater.setAttribute("id", "repeatItem");
repeater.setAttribute("nodeset", "instance('albumData')/Data/Album");
var titleOut = $doc.createElementNS(xfNs, "xforms:output");
titleOut.setAttribute("ref", "Title");
var titleLabel = $doc.createElementNS(xfNs, "xforms:label");
titleLabel.appendChild($doc.createTextNode("Title:"));
titleOut.appendChild(titleLabel);
repeater.appendChild(titleOut);
var yearOut = $doc.createElementNS(xfNs, "xforms:output");
yearOut.setAttribute("ref", "Year");
var yearLabel = $doc.createElementNS(xfNs, "xforms:label");
yearLabel.appendChild($doc.createTextNode("Year:"));
yearOut.appendChild(yearLabel);
repeater.appendChild(yearOut);
container.appendChild(repeater);
}-*/;
|
Как и ранее, создание элементов управления XForms сводится к простой работе с DOM через собственные методы JavaScript. Теперь мы можем просто добавить код создания элементов UI в метод loadAlbums()
, вызываемый при загрузке страницы. Таким образом, сначала будут созданы элементы интерфейса, а затем вызван метод для загрузки альбомов. После этого будут созданы экземпляры данных XForms на основе полученных альбомов, и, наконец, модель XForms будет обновлена для показа новых данных пользователю. В листинге 8 показано насколько простой стала наша JSP.
Листинг 8. Упрощенный вариант JSP (без элементов управления XForms)
<xhtml:body onload="loadAlbums(<%=request.getParameter("artistid") %>)">
<xhtml:div id="albumList">
</xhtml:div>
</xhtml:body>
|
Теперь JSP-страница на XForms выглядит практически так же, как и страница для показа списка исполнителей, которая была написана ранее с использованием GWT. Весь пользовательский интерфейс создается программно через собственные методы JavaScript в Java-классах, а данные по-прежнему запрашиваются через Ajax.
В третьей части мы взяли нашу JSP-страницу, использующую скриптлет для размещения данных, и упростили ее, избавившись от скриптлета и используя GWT. Сами данные асинхронно запрашиваются с сервера через Ajax. Экземпляры данных в модели XForms создаются динамически с помощью интерфейса JSNI в GWT. И, наконец, даже элементы управления XForms для показа данных об альбомах теперь генерируются динамически через JSNI. В четвертой части серии мы покажем, как использовать элементы XForms для асинхронных Ajax-запросов к GWT-сервисам по требованию пользователя.
Файлы для загрузки