Майкл Галпин
XForms - это стандартизированная технология, однако все еще не реализованная по умолчанию в большинстве браузеров. В данной статье используется подключаемый модуль Mozilla XForms. Этот подключаемый модуль может применяться в любых основанных на механизме Mozilla Gecko браузерах, таких как Firefox, Seamonkey и Flock. Весь используемый здесь XForms-код является стандартным, поэтому все совместимые XForms-реализации должны быть аналогичными, но в данном случае я тестировал только подключаемый модуль Mozilla XForms версии 0.8, установленный на Mozilla Firefox 2.0. Аналогично, JavaScript-код тестировался только на Firefox.
Все Ajax-приложения должны запрашивать данные с какого-либо сервера. В данной статье в качестве серверной технологии используется Java. Следовательно, необходимо наличие Java 1.5+ вместе с Java Web-контейнером, реализующим спецификацию Servlet 2.4. Это приложение тестировалось с Apache Tomcat 5.5.
В данной статье вы разработаете поле формы с автозаполнением. Это прототип функциональности Ajax. Одним из приложений, популяризовавших эту функциональность, является Google Suggest. В Google Suggest вы начинаете вводить ключевое слово для поиска, а приложение предлагает (подсказывает) поисковые выражения, основываясь на их популярности. Это полезная функция для пользователей, поскольку им не надо вводить поисковое выражение полностью. Тем самым предотвращаются ошибки ввода. Данный технический прием сейчас широко используется в Web-приложениях. Давайте рассмотрим, как эта функциональность обычно реализуется при помощи Ajax.
С точки зрения функциональных возможностей Ajax поле с автозаполнением является довольно понятным. Вы будете использовать JavaScript для перехвата события ввода пользователем данных в поле формы. Затем выполните асинхронный запрос на сервер для получения подсказок, основанных на информации, введенной пользователем на данный момент. Сервер возвратит подсказки. JavaScript-обработчик обработает их и изменит HTML DOM, чтобы отобразить подсказки и дать возможность конечному пользователю выбрать их из списка. Рассмотрим каждый из этих шагов по созданию поля с автозаполнением при помощи Ajax. Начнем с HTML для этого поля.
HTML для поля с автозаполнением довольно прост. Он приведен в листинге 1.
Листинг 1. HTML для поля с автозаполнением
<input type="text" id="tBox" onkeyup="suggest();" autocomplete="off"/>
<div id="suggest"></div>
|
Здесь все понятно. Имеется обычное текстовое поле, но его атрибуты очень важны. Прежде всего, атрибут onkeyup
используется для объявления того, что при каждом возникновении события onkeyup
должна вызываться JavaScript-функция под названием suggest
. Мы рассмотрим эту функцию в следующем разделе. Отметим также, что атрибут autocomplete
установлен в Off. Это предохраняет поля формы от автозаполнения браузером. Наконец, обратите внимание на то, что мы создаем пустой элемент div. Мы заполним этот div подсказками. Рассмотрим, как запросить эти подсказки.
Вы видели, что текстовое поле вызывает функцию suggest
при вводе в него символа пользователем. Эта функция приведена в листинге 2.
Листинг 2. JavaScript-функция suggest
//Это не работает в IE 6
var suggestReq = new XMLHttpRequest();
function suggest() {
if (suggestReq.readyState == 4 // suggestReq.readyState == 0) {
var str = escape(document.getElementById('tBox').value);
suggestReq.open("GET", '/xfsuggest/suggest?root=' + str, true);
suggestReq.onreadystatechange = handleSuggestions;
suggestReq.send(null);
}
}
|
Функция suggest
использует объект XMLHttpRequest
для формирования запроса к URL /xfsuggest/suggest. Приведенный в листинге 2 код не работает в Internet Explorer 6 или более ранних версиях Internet Explorer. Для Internet Explorer 6 необходимо пренебречь браузером и вызвать новый ActiveXObject("Microsoft.XMLHTTP")
для получения объекта XMLHttpRequest
. После этого использовать JavaScript для извлечения информации, введенной пользователем в поле. Она становится параметром root
вашего запроса HTTP GET
на сервер. Наконец, обратите внимание на то, что обработчик ответа настраивается на JavaScript-функцию handleSuggestions
. Мы рассмотрим ее позднее. Теперь посмотрим, как ваш сервер будет обрабатывать этот запрос.
Одной из замечательных особенностей таких технологий на стороне клиента как Ajax (и XForms) является то, что они независимы от технологий на стороне сервера. Вы можете использовать PHP, Ruby или еще что-нибудь. Здесь мы будем использовать Java-сервлет. Код сервлета приведен в листинге 3.
Листинг 3. Сервлет Suggest
package org.developerworks.xfsuggest;
import java.io.IOException;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.http.*;
public class SuggestServlet extends HttpServlet {
private final int listSize = 15;
private final Random gen = new Random(Calendar.getInstance().getTimeInMillis());
protected void doGet(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
String root = request.getParameter("root");
List<String> suggestions = this.getSuggestions(root);
request.setAttribute("suggestions", suggestions);
request.getRequestDispatcher("/suggestions.jsp").forward(request, response);
}
private List<String> getSuggestions(String str){
if (str.length() >= listSize){
return Collections.emptyList();
}
int size = listSize - str.length();
List<String> suggestions = new ArrayList<String>();
for (int i = 0; i<size; i++){
StringBuilder sb = new StringBuilder(str);
for (int j = 0; j < size; j++){
char ch = (char) ('a' + gen.nextInt(26));
sb.append(ch);
}
suggestions.add(sb.toString());
}
return suggestions;
}
}
|
Этот сервлет просто извлекает параметр root запроса и вызывает метод getSuggestions()
для получения списка подсказок. Приведенная в листинге 3 реализация создает случайный список строк подсказок. Эта фиктивная реализация создает меньше подсказок, при увеличении длины параметра root, имитируя сужение возможностей выбора при продолжении ввода данных в поле с автозаполнением. Затем список помещается в объект request. Сервлет передает управление JSP для визуализации данных. JSP-страница приведена в листинге 4.
Листинг 4. suggestions.jsp
<?xml version="1.0" encoding="ISO-8859-1"?>
<%@ page language="java" contentType="text/xml; charset=ISO-8859-1"
pageEncoding="ISO-8859-1" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<suggestions>
<c:forEach items="${suggestions}" var="str">
<word>${str}</word>
</c:forEach>
</suggestions>
|
JSP создает XML-документ для передачи обратно клиенту. Это делается просто путем итерации по списку подсказок, созданных сервлетом. Теперь давайте рассмотрим JavaScript-код на стороне клиента, обрабатывающий эти данные.
При вызове сервера с использованием объекта XMLHttpRequest
регистрируется обработчик ответа. То есть, когда сервер возвращает XML-документ, приведенный в листинге 4, этот JavaScript-обработчик активизируется. Такого рода асинхронная работа является одной из самых типичных ловушек Ajax-программирования. Давайте рассмотрим JavaScript-функцию handleSuggestions
, которая обрабатывает ответ сервера (см. листинг 5).
Листинг 5. Обработчик ответа: JavaScript-функция handleSuggestions
function handleSuggestions() {
if (suggestReq.readyState == 4) {
var ss = document.getElementById('suggest')
ss.innerHTML = '';
var xml = suggestReq.responseXML;
var root = xml.getElementsByTagName('suggestions').item(0);
var suggestions = new Array();
var cnt = 0;
for (var i=0; i < root.childNodes.length; i++){
var node = root.childNodes.item(i);
if (node.childNodes.length > 0){
suggestions[cnt++] = node.childNodes.item(0).data;
}
}
for(i=0; i < suggestions.length; i++) {
var val = suggestions[i];
if (val.length > 0){
var suggest = '<div
onmouseover="javascript:suggestOver(this);" ';
suggest += 'onmouseout="javascript:suggestOut(this);" ';
suggest += 'onclick="javascript:setValue(this.innerHTML);" ';
suggest += 'class="suggest_link">' + val + '</div>';
ss.innerHTML += suggest;
}
}
}
}
|
Эта функция состоит из двух основных частей. Сначала обрабатывается XML путем прохождения по документу responseXML
и извлечения подсказок. Они сохраняются в массиве suggestions. Затем код динамически создает список подсказок для выбора пользователем и помещает этот список внутри suggest div, созданного в HTML. На рисунке 1 показано, как выглядит поле с автозаполнением.
Рисунок 1. Автозаполнение с использованием Ajax
Имеется отдельный CSS и JavaScript-код для создания "эффекта подсказки". Их можно найти в полном исходном коде. А сейчас давайте рассмотрим, как XForms упрощает код.
Двумя ключевыми компонентами Ajax являются возможность выполнения запросов на сервер без изменения просматриваемой в браузере страницы, а также возможность получения XML-данных от сервера. Первая характеристика (асинхронные запросы) - это буква "A" в Ajax. Последняя характеристика - это буква "X" в Ajax. Обычно используется объект XMLHttpRequest
, но это определенно не единственный способ. XForms может выполнить ту же задачу и быть представленным буквами "A" и "X" в Ajax.
Парадигма XForms
XForms отделяет модель от вида (view) в Web-приложении. Данные разделяются так, чтобы их легче можно было изменять. Все данные и действия (запросы серверу) являются частью модели, что присуще типичным архитектурам Модель Вид Управление (Model View Control - MVC). В нашем приложении с автозаполнением присутствуют данные запроса (root, передаваемый на сервер), данные ответа (подсказки от сервера) и действие (HTTP-запрос серверу) как части модели. Давайте их рассмотрим.
Данные, которые мы собираемся использовать для запроса на сервер, являются первой частью нашей модели. Она довольно проста. Необходим только параметр root
. Взгляните на листинг 6.
Листинг 6. Модель XForms: Данные запроса
<xf:instance id="xfsuggestQuery">
<query xmlns="">
<root/>
</query>
</xf:instance>
|
Это всего лишь простой XML-документ для хранения параметра root
, который мы собираемся передать на сервер. Позже вы увидите его связь с видом. А сейчас рассмотрим, как включить данные ответа в нашу модель.
Данные для ответа несколько более сложны. Мы сохраним структуру, использовавшуюся с Ajax-версией, поскольку она грамматически правильна и полностью совместима с XForms. Однако в начале работы приложения у нас нет подсказок, поэтому пока необходимо пустое место для них (см. листинг 7).
Листинг 7. Модель XForms: Данные ответа
<xf:instance id="xfsuggestResults">
<suggestions xmlns=""/>
</xf:instance>
|
Мы полностью заменим узел suggestions при получении ответа от сервера. А сейчас просто оставим suggestions пустым. Это данные, необходимые в нашей модели, а теперь для модели нужно действие.
Данные являются частью модели, но действия с этими данными также важны. В данном случае действием является HTTP-запрос на сервер. В листинге 8 показано, как легко это можно сделать при помощи узла XForms submission.
Листинг 8. Модель XForms: submission
<xf:submission id="get_suggest" action="/xfsuggest/suggest"
method="get" separator="&" ref="instance('xfsuggestQuery')"
replace="instance" instance="xfsuggestResults"/>
|
Действием в нашем узле submission является URL HTTP-запроса, который мы собираемся выполнить. Параметр ref
указывает submission, что передавать. В данном случае указывается передача данных из объекта xfsuggestQuery
. Это наши данные запроса, определенные в листинге 6. Атрибут method установлен в get
, а разделитель - амперсанд (&). Это указывает XForms, как добавлять данные в HTTP-запрос. Будет создаваться параметр root
, поскольку это элемент узла xfsuggestQuery/query, а значение этого параметра будет значением дочернего узла text узла "root" XML-данных. Если бы имелось несколько параметров, они разделялись бы при помощи амперсанда для создания правильно сформированного запроса HTTP GET
. Мы определили модель XForms, а теперь нужно просто определить вид.
Вид в нашей XForm достаточно прост - это всего лишь текстовое поле, хотя и не совсем обычное, поскольку оно будет иметь Ajax-функциональность автозаполнения. Его объявление приведено в листинге 9.
Листинг 9. Вид XForms
<xf:input ref="instance('xfsuggestQuery')/root" incremental="true" id="tBox">
<xf:action ev:event="xforms-value-changed">
<xf:send submission="get_suggest" />
</xf:action>
</xf:input>
|
Давайте проанализируем, что здесь происходит. Сначала определяется поле input. Оно связывается с моделью при помощи атрибута ref
. Это говорит XForms о том, что при вводе значения в поле input оно также должно вводиться в узел "root" XML-документа xfsuggestQuery
. Затем определяется действие (action) при вводе. Данное действие вызывает "send" для передачи в представление (submission), созданное в модели. Все это очень типично для XForms и должно быть хорошо знакомо разработчикам, имевшим дело с XForms.
Впрочем, здесь используется также несколько необычных возможностей XForms. Поле input имеет атрибут incremental
, установленный в значение true, что активизирует событие при любом инкрементном изменении, выполненном в поле input. По сравнению с типом события onkeyup
поля input (см. листинг 1) такой подход более совершенен. Этот атрибут позволяет XForms проявлять большую избирательность при активизации событий, так как активизация подавляется при быстром вводе данных пользователем. Многие Ajax-приложения часто должны добавлять собственные механизмы подавления для реализации функциональности, которую в XForms вы получаете автоматически.
Определенное нами событие имеет атрибут event, установленный в значение "xforms-value-changed". Он указывает, как прослушивать действие для инкрементных событий, активизируемых полем ввода. Теперь, когда бы эти события ни активизировались, будет вызываться действие submission, и, следовательно, выполняться запрос на сервер о получении подсказок для поля. Давайте рассмотрим, как серверный код обрабатывает запрос.
Итак, как изменить сервер для обработки запросов от XForm? Ничего не надо менять! Ваш сервер уже посылает XML обратно Ajax, что отлично работает и с XForms. Это демонстрация того, как XForms дополняет Ajax. Службы на стороне сервера, используемые в одной технологии, могут использоваться и с другой. Теперь нужно просто обработать ответ сервера на клиенте.
В Ajax-приложении мы использовали JavaScript-функцию для обработки responseXML, полученного с сервера, а также для изменения HTML DOM с целью отображения подсказок. При помощи XForms можно сделать аналогичные вещи (но значительно проще), связав ответ непосредственно с данными. Когда данные передаются на сервер, наш вид обновляет себя автоматически. Поэтому вместо включения пустого элемента suggest div мы просто добавляем некоторые связанные данные, как показано в листинге 10.
Листинг 10. Отображение результатов со связанными XForm-данными
<div id="suggest">
<xf:repeat id="results" nodeset="instance('xfsuggestResults')/word">
<div onmouseover="javascript:suggestOver(this);"
onmouseout="javascript:suggestOut(this);"
onclick="javascript:setValue(this.innerHTML);" class="suggest_link">
<xf:output ref="."/>
</div>
</xf:repeat>
</div>
|
Здесь реализовано связывание узлов "word" в документе xfsugggestResults
. Естественно, сначала нет никаких слов, но это пока нет ответа от сервера. После появления узлов "word" по ним выполняется итерация для создания div со словом. Этот div аналогичен элементу div, создаваемому динамически в JavaScript в чистой Ajax-версии. JavaScript-функции suggestOver
и suggestOut
, на которые этот элемент ссылается, остаются неизменными. Единственное, что нужно поменять, - это функция setValue
. Данная функция необходима для работы с XForms-данными вместо простого изменения данных поля HTML input. Взгляните на setValue в листинге 11.
Листинг 11. Новая функция setValue
function setValue(value) {
var model = document.getElementById("xfsuggestModel");
var instance = model.getInstanceDocument("xfsuggestQuery");
var queryElement = model.getElementsByTagName("query")[0];
var rootElement = queryElement.getElementsByTagName("root")[0];
if (rootElement.childNodes.length == 0){
var textNode = document.createTextNode(value);
rootElement.appendChild(textNode);
} else {
rootElement.firstChild.nodeValue = value;
}
model.rebuild();
model.refresh();
document.getElementById('suggest').innerHTML = '';
}
|
В этой функции мы обращаемся к модели XForms, добираемся до экземпляра xfsuggestQuery
, а затем идем до его элемента root
и либо добавляем к нему текстовый узел, либо изменяем его значение, если он уже существует. Данный технический прием можно использовать для прямого доступа к данным xfsuggestResults
и отображения подсказок точно так же, как это делалось ранее. JavaScript и XForms очень хорошо работают совместно.
Вы увидели, как можно использовать XForms для дополнения Ajax. Он использует много тех же принципов, что и Ajax, но в определенной степени упрощает и оптимизирует работу. Используя XForms, легче передавать и получать данные от сервера, а еще в XForms формируются более "умные" события. Также упрощается необходимый JavaScript-код, так как предоставляется функциональность "шаблонов". В заключение можно сказать, что часто лучше доверить именно XForms быть буквами "A" и "X" в Ajax.