Коротко о том, что такое Ajax

понедельник, 2 июля 2012 г.
Ajax уже достаточно зрелая и популярная технология. Для работы с ней создано множество библиотек и компонентов на столько качественных и удобных, что можно вполне успешно разрабатывать веб-проекты и не знать всех тонкостей ajax.



Но естественно, так дело не пойдет! Пришла пора заглянуть под капот ajax-компонентов и узнать, что на самом деле представляет из себя ajax.

Если верить вики, то Ajax это:

AJAX, Ajax (ˈeɪdʒæks, от англ. Asynchronous Javascript and XML — «асинхронный JavaScript и XML») — подход к построению интерактивных пользовательских интерфейсов веб-приложений, заключающийся в «фоновом» обмене данными браузера с веб-сервером. В результате, при обновлении данных, веб-страница не перезагружается полностью, и веб-приложения становятся более быстрыми и удобными.

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

 Сердцем ajax-а является объект XMLHttpRequest. Хотя есть варианты, позволяющие обходиться и без XMLHttpRequest, сегодня мало кто к ним прибегает в силу реализации этого объекта практически во всех существующих браузерах (интересно, как обстоят с ним дела у Links и прочих консольных браузеров?). XMLHttpRequest - светлое пятно  истории Microsoft, придумавшей его в 1998 году. Правда популярность его настигла гораздо позднее, после появления термина Ajax, которым мы обязаны Джесси Джеймсу Гарретту. Но об этом вы можете почитать более подробно в других источниках.

XMLHttpRequest обладает методами и свойствами для обмена данными с сервером по протоколу http. Это налагает некоторые трудности для изучения ajax, т.к. вынуждает прибегнуть к серверным технологиям, которые лично меня приводят в уныние - мало мне потенциальных проблем с клиентским кодом, так надо еще и с серверными проблемами бороться! Чтобы облегчить себе жизнь, я написал простейшую реализацию web сервера и оснастил его всеми примерами из данной статьи. Скачать jar файл вы можете здесь (о создании веб сервера вы можете почитать в соседней статье из цикла "http в java"). Надеюсь он вам пригодится.

Работа с XMLHttpRequest


Создается XMLHttpRequest следующей инструкцией: xhr = new XMLHttpRequest(); Это справедливо для всех актуальных браузеров. Но на несчастного IE 6, которому до сих пор не дают спокойно отправиться на пенсию,  это не распространяется. Для него существует инструкция xhr = new ActiveXObject("Msxml2.XMLHTTP");

После того, как XMLHttpRequest будет создан, необходимо его настроить. Прежде всего определить метод и адрес запроса: xhr.open("GET", url); Вообще, полная сигнатура метода open имеет следующий вид: open(method, URL, async, userName, password). Здесь помимо метода и адреса указываются имя пользователя(userName) и пароль(password) в случае, если сервер требует их указания, и ключ async указывающий, следует ли выполнять запрос асинхронно или надо дождаться ответа от сервера прежде чем продолжать работу. Потребность в синхронном запросе возникает крайне редко (я даже затрудняюсь представить когда?), а параметры имя и пароль я просто решил опустить, для простоты примера.

Помимо адреса запрашиваемого ресурса необходимо указать, что следует делать после ответа сервера. Вопреки ожиданиям, в XMLHttpRequest обработчик события ответа от сервера отсутствует, вместо этого предусмотрен обработчик событий связанных с изменением состояния самого объекта xhr.onreadystatechange. Всего, для XMLHttpRequest, определено четыре состояния : 0 — не инициализирован, 1 — открыт, 2 — отправка данных, 3 — получение данных и 4 — данные загружены. Из них наибольший интерес вызывает код 4. Таким образом, нужно указать функцию, которая будет реагировать на изменения состояния и при изменении кода состояния на 4 вызывать функцию обработки ответа. Выглядеть это может примерно так:

xhr.onreadystatechange = function() {
    if (xhr.readyState != 4) {
        return;
    }
    // Обрабатываем ответ или вызываем функцию обработки ответа
}

Получив ответ от сервера, первым делом необходимо проверить с каким кодом ответ вернулся. Код ответа от сервера заносится в поле status. Коды успешного обращения к серверу, согласно протоколу HTTP, лежат в диапазоне от 200 до 299 включительно. Очевидно, что только если код входит в этот диапазон, следует предпринимать шаги для обработки содержимого ответа, иначе надо как-то реагировать на сложившуюся неприятную ситуацию.

if (xhr.status >= 200 && 
    xhr.status < 300) {
    // Обрабатываем тело ответа
} else {
    alert('Error: ' + xhr.statusText);
}

Содержимое ответа от сервера размещается в полях responseText и responseXML. Первое поле содержит ответ целиком, как строку. А вот поле responseXML актуально только в случае, если ответ от сервера представляет собой XML, но об этом чуть позже.

Чтож, с тем, как указывать что нужно получить и что с этим делать, кажется разобрались. Где искать сам ответ тоже знаем. Теперь можно наконец выполнить непосредственно запрос: xhr.send(null); В качестве аргумента в метод send передаются параметры запроса, но это актуально только при использовании метода POST для обращения к серверу, в остальных случаях достаточно передать null.

 Возможно все выглядит несколько запутанно, но все наверняка прояснится, если посмотреть на код целиком:

/*
 * Выполняет асинхронный GET запрос к серверу по указанному URL.
 * В случае успешного ответа вызывает функцию callback и передает 
 * ей экземпляр объекта XMLHttpRequest содержащий ответ от сервера.
 */
function invokeAjax(url, callback) { 
 /* 
  * Создание объекта XMLHttpRequest 
  */
 var xhr;
 // Проверяем, существует ли стандартный объект XMLHttpRequest
 if (window.XMLHttpRequest) {
  xhr = new XMLHttpRequest();
 // Проверяем, существует ли ActiveX реализация
 } else if (window.ActiveXObject) {
  alert("Самое время сменить броузер!"); // ... и это правда ;)
  xhr = new ActiveXObject("Msxml2.XMLHTTP");
 // Если создать экземпляр объекта XMLHttpRequest не удалось,
 // выбрасываем исключение
 } else {
  throw new Error("Ajax is not supported.");
 }
 /* 
  * Конфигурирование 
  */ 
 xhr.open("GET", url);
 xhr.onreadystatechange = function() {
  if (xhr.readyState != 4) {
   return;
  }
  if (xhr.status >= 200 && 
   xhr.status < 300) {
   // Объект xhr передается целиком, 
   // тк может понадобиться ответ как в форме текста, 
   // так и в форме Xml
   callback(xhr);     
  } else {
   throw new Error('HTTP exception: ' + xhr.statusText);
  }
 } 
 /*
  * Выполнение запроса
  */ 
 xhr.send(null);
}

Обработка ответа от сервера


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

HTML код
CSS стили


Это простой пример условной корзины  в электронном магазине. Слева размещен список товаров (элемент select), а справа отведено место под описание выбранного товара (div контейнер description). На получении описания выбранного товара через асинхронный запрос мы и продолжим изучение ajax.

Text

Начнем с простого - с ответа в виде кусочка html. У контейнеров в html, таких как div, есть свойство innerHtml доступное для записи и содержащее вложенный в контейнер код. Самое простое решение - отправлять серверу информацию о выбранном товаре и получать от него контент для контейнера description:
function insertContent(xhr) {
    var obj = document.getElementById("description");
    obj.innerHTML = xhr.responseText;
}

Запрос к серверу привяжем к клику мышкой по товару:
<option onclick="invokeAjax('innerhtml/monitor.html', insertContent)">
    Монитор
</option>

Естественно, в реальной ситуации гораздо удобнее передать идентификатор выбранного товара с помощью аргументов запроса, но т.к. это учебный пример,подразумевающий использование сервера описанного выше (который не умеет работать с аргументами запроса), то для каждого товара создана отдельная страница с уникальным содержимым:
<img src="../index_files/monitor.png"/>
Модель: Радуга-203, Цена: 12 500 р

Из недостатков такого подхода можно заметить излишне большой объем передаваемого контента, а главное, при таком решении разделение представления данных смешивается с их значением, что может серьезно усугубить жизнь в более сложных примерах (например, если получаемые от сервера данные понадобится разместить в разных местах, используя различное их оформление).

XML

Гораздо удобнее было бы выделить на странице специальные контейнеры под информацию о товаре, и обновлять их содержимое. Здесь, как нельзя кстати, на помощь придет Xml, для работы с которым и создавался XMLHttpRequest. Главное отличие, при использовании xml, от предыдущего решения, заключается в том, что ответ от сервера обязательно должен быть в формате xml. Тогда ответ можно получить в качестве объекта document и работать с ним, как с xml документом:

Пример содержимого ответа сервера
<?xml version="1.0"?>
<!-- xml/monitor.html --?>
<ware>
    <model>Радуга-203</model>
    <price>12 500 р</price>
    <imgsrc>../index_files/monitor.png</imgsrc>
</ware>

Код реакции на выбор товара
<option onclick="invokeAjax('xml/monitor.xml', insertContent)">
    Монитор
</option>

Html код контейнера для описания
<div id="description">
    <img id="picture" />
    <p />
    <b>Модель: </b><span id="model"></span>
    <p />
    <b>Цена: </b><span id="price"></span>
</div>

Код функции обработки ответа от сервера
function insertContent(xhr) {
 var doc = xhr.responseXML;
 document.getElementById("picture").src = doc
   .getElementsByTagName("imgsrc")[0].firstChild.data;
 document.getElementById("model").innerHTML = doc
   .getElementsByTagName("model")[0].firstChild.data;
 document.getElementById("price").innerHTML = doc
   .getElementsByTagName("price")[0].firstChild.data;
}

Xml позволил разделить логику и представление данных, но что касается объема ответа, то здесь все даже усугубилось! Вообще, современные каналы интернет стали достаточно широки, чтобы не обращать внимание на лишние байты, но иногда эти лишние байты могут складываться в существенный объем информации. В таких ситуациях на помощь приходит другой формат представления данных - JSON.

JSON

JSON (JavaScript Object Notation) строится на двух структурах: набор пар ключ/значение и пронумерованный набор значений. В JSON используются их следующие формы:
  • Объект — это неупорядоченное множество пар имя/значение, заключённое в фигурные скобки { }. Между именем и значением стоит символ ": ", а пары имя/значение разделяются запятыми.
  • Массив (одномерный) — это множество значений, имеющих порядковые номера (индексы). Массив заключается в квадратные скобки [ ]. Значения отделяются запятыми.
  • Значение может быть строкой в двойных кавычках, числом, значением true или false, объектом, массивом, или значением null. Эти структуры могут быть вложены друг в друга.
  • Строка — это упорядоченное множество из нуля или более символов юникода, заключенное в двойные кавычки, с использованием escape-последовательностей начинающихся с обратной косой черты (backslash). Символы представляются простой строкой.
JavaScript может преобразовать строку, описывающую объект в формате JSON, в обычный объект JavaScript: var obj = eval('(' + JsonString + ')'); Но использование функции eval не безопасно, т.к. позволяет выполнить зловредный код. К счастью, на практике решение этой проблемы берут на себя специальные библиотеки, такие как json или jQuery. Использование таких библиотек - это отдельная и обширная тема, поэтому здесь мы все таки рискнем и применим функцию eval.


Пример содержимого ответа сервера
{  
    "model": "Радуга-203",
    "price": "12 500 р",
    "imgsrc": "../index_files/monitor.png" 
}

Код реакции на выбор товара
<option onclick="invokeAjax('json/monitor.js', insertContent)">
    Монитор
</option>

Html код контейнера для описания
<div id="description">
    <img id="picture" />
    <p />
    <b>Модель: </b><span id="model"></span>
    <p />
    <b>Цена: </b><span id="price"></span>
</div>

Код функции обработки ответа от сервера
function insertContent(xhr) {
 var ware = eval('(' + xhr.responseText + ')');
 document.getElementById("picture").src = ware.imgsrc;
 document.getElementById("model").innerHTML = ware.model;
 document.getElementById("price").innerHTML = ware.price;
}

Весь приведенный код доступен внутри jar файла с веб сервером, о котором упоминалось выше. После запуска сервера (java -jar webserver.jar) будут доступны страницы:

Что еще почитать:

1 комментарий :

Ваше мнение мне искренне интересно. Смелее!

Технологии Blogger.