
Лабораторная №6
Тема: «Изучение технологии AJAX»
Цель: Изучить подход к созданию динамических web-страниц на основе технологии AJAX
Теоретические основы:
Технология AJAX (Асинхронный JavaScript + XML), являющаяся одной из основных технологий, лежащей в основе, так называемого, WEB 2.0, была известно еще, чуть ли не, со времен каменного века. Однако, благодаря появлению термина AJAX, который ввел Джис Джеймс Гаррет (Jesse James Garrett), она стала необычайно модной. И если раньше, о ней могли говорить только наиболее продвинутые программисты, то теперь, благодаря появлению специального термина, сказать о ней может каждый, кому не лень. Достаточно просто произнести «AJAX», и ты уже о ней говоришь.
Суть технологии AJAX заключается в изменении содержимого загруженной веб-страницы без ее полной перезагрузки, благодаря чему достигается высокая динамичность сайтов. Технология основывается на разделении данных и подзагрузки тех или иных компонентов по мере необходимости.
Рис. 9. Общая схема работы web-сервера на AJAX
Поскольку AJAX – это по сути, только разнообразное применение компонента XMLHttpRequest, то чтобы понять работу AJAX, требуется разобраться с синтаксисом XMLHttpRequest. Сделаем это на примере:
<script type="text/javascript" language="JavaScript">
function doLoad() {
req=false;
try { // определить метод поддержки
req=new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
req=new ActiveXObject('Microsoft.XMLHTTP'); // сработает в Internet Explorer
} catch (e) {
if(window.XMLHttpRequest){ // сработает в Mozilla и Safari
req=new XMLHttpRequest(); } } }
if (req){// если какой-то из вариантов поддерживается
req.onreadystatechange = readystate; // назначим обработчик событию объекта
req.open("GET", document.getElementById('edit1').value, true); // задать параметры методу open
req.send(null); // отправить запрос
}}
function readystate() {
if (req.readyState == 4){// если запрос завершен
if (req.status == 200) { // если он завершен без ошибок
document.getElementById('content').innerHTML ='<pre>'+req.responseText+'</pre>';
} else {
alert("Произошла ошибка "+ req.status+":\n" + req.statusText);
} } }
</script>
<input type="TEXT" size="50" id="edit1">
<input type="button" value="Через ActiveX" onclick="doLoad()">
<div id=content style="white-space:pre"></div>
И так:
Условие window.XMLHttpRequest определяет, каким образом работает объект в данном браузере (можно добавить проверку, работает ли он вообще и если window.ActiveXObject так же дает ложный результат, данный метод вообще не поддерживается браузером). Затем создается объект одного из поддерживаемых типов, после чего событию onreadystatechange (изменение состояния) назначается обработчик. Так же, назначаются параметры методу open. Первый из них определяет тип запроса: POST или GET, второй определяет адрес (в данном случае адрес берется из значения текстового поля), третий, если истинен, определяет что, запрос должен выполнятся асинхронно, то есть, отправив запрос не ждать ответа сервера, а продолжать работу, ожидая ответа в фоновом режиме.
После всего этого методом send происходит фактическая отправка запроса и, как только от сервера приходит ответ, срабатывает обработчик события onreadystatechange.
При обработке события onreadystatechange нужно проверить код состояния объекта (свойство readyState). Определено 4 состояния:
0 – объект не инициализирован
1 – идет загрузка
2 – объект уже загружен
3 – Загружен частично
4 – завершение загрузки
Как только код становится равен 4, данные сервера можно использовать и выводить в браузер. Однако, не мешало бы проверить, что именно вытащили наши сети, используя код ответа сервера, сохраняющийся в req.status. Если код равен 200 , данные действительно можно выводить, иначе, можно вывести сообщение об ошибке, сохраняющийся в свойстве req.statusText. Вместо стандартного текста ошибки, можно вывести свой, что удобно сделать, создав массив определений, ключи которого будут соответствовать номерам ошибок:
err[400]=”Запрос содержит синтаксическую ошибку”;
err[401]=”Для доступа требуется авторизация”;
err[403]=”Доступ запрещен”;
и т. д.
Все довольно просто. Однако данным методом можно обращаться только к ресурсам того же сайта, с которого закачана запрашивающая страница. Чтобы обратиться к другим интернет-ресурсам, придется использовать серверный скрипт, вроде такого:
<?php
if (! @readfile ($_GET['file'])){
echo '<font color=red>Файл не доступен</font>';
}
?>
И тут задумаешься: А может послать этот AJAX к чертям и использовать более простой и всеядный IFRAME? Но IFRAME нельзя использовать непосредственно внутри форм. В этом случае, применение AJAX окажется вполне разумным, и будет выглядеть примерно так:
<script type="text/javascript" language="JavaScript">
function doLoad2 () {
req=false;
try { // определить метод поддержки
req=new ActiveXObject('Msxml2.XMLHTTP');
} catch (e) {
try {
req=new ActiveXObject('Microsoft.XMLHTTP'); // сработает в Internet Explorer
} catch (e) {
if(window.XMLHttpRequest){ // сработает в Mozilla и Safari
req=new XMLHttpRequest(); } } }
if (req){// если какой-то из вариантов поддерживается
req.onreadystatechange = readystate; // назначим обработчик событию объекта
req.open("GET", 'myscript.php?file='+document.getElementById('edit2').value, true); // myscript.php – это приведенный выше серверный скрипт
req.send(null);
} }
function readystate() {
if (req.readyState == 4){// если запрос завершен
if (req.status == 200) { // если он завершен без ошибок
document.getElementById('content').innerHTML ='<pre>'+req.responseText+'</pre>';
} else {
alert("Произошла ошибка "+ req.status+":\n" + req.statusText);
} } }
</script>
<input type="TEXT" size="50" id="edit2">
<input type="button" value="Через ActiveX и серверный скрипт" onclick="doLoad2()">
<div id=content style="white-space:pre"></div>
Методы класса XMLHttpRequest
abort() отменяет текущий запрос;
getAllResponseHeaders() возвращает полный список HTTP-заголовков в виде строки;
getResponseHeader( headerName ) возвращает значение указанного заголовка;
open( method, URL, async, userName, password ) определяет метод, URL и другие опциональные параметры запроса; параметр async определяет, происходит ли работа в асинхронном режиме;
send( content) отправляет запрос на сервер;
setRequestHeader( label, value ) добавляет HTTP-заголовок к запросу.
Свойства класса XMLHttpRequest
Onreadystatechange обработчик события, которое происходит при каждой смене состояния объекта;
readyState возвращает текущее состояние объекта (0 - неинициализирован, 1 - открыт, 2 – отправка данных, 3 – получение данных и 4 – данные загружены);
responseText текст ответа на запрос;
responseXML текст ответа на запрос в виде XML, который затем может быть распарсен посредством DOM;
status возвращает HTTP-статус в виде числа (404 – «Not Found», 200 – «OK» и т.д.)
statusText возвращает статус в виде строки («Not Found», «OK» и т.д.)
Преимущества
Экономия трафика
Использование AJAX позволяет значительно сократить трафик при работе с веб-приложением благодаря тому, что часто вместо загрузки всей страницы достаточно загрузить только изменившуюся часть, как правило, довольно небольшую.
Уменьшение нагрузки на сервер
AJAX позволяет несколько снизить нагрузку на сервер. К примеру, на странице работы с почтой, когда вы отмечаете прочитанные письма, серверу достаточно внести изменения в базу данных и отправить клиентскому скрипту сообщение об успешном выполнении операции без необходимости повторно создавать страницу и передавать её клиенту.
Ускорение реакции интерфейса
Поскольку нужно загрузить только изменившуюся часть, пользователь видит результат своих действий быстрее.
Недостатки
Отсутствие интеграции со стандартными инструментами браузера
Динамически создаваемые страницы не регистрируются браузером в истории посещения страниц, поэтому не работает кнопка «Назад», предоставляющая пользователям возможность вернуться к просмотренным ранее страницам, но существуют скрипты, которые могут решить эту проблему.
Другой недостаток изменения содержимого страницы при постоянном URL заключается в невозможности сохранения закладки на желаемый материал. Проблему можно успешно решить с помощью History.pushState.
Динамически загружаемое содержимое недоступно поисковикам (если не проверять запрос, обычный он или XMLHttpRequest)
Поисковые машины не могут выполнять JavaScript, поэтому разработчики должны позаботиться об альтернативных способах доступа к содержимому сайта.
Старые методы учёта статистики сайтов становятся неактуальными
Многие сервисы статистики ведут учёт просмотров новых страниц сайта. Для сайтов, страницы которых широко используют AJAX, такая статистика теряет актуальность.
Задание: Создать web-страницу с двумя таблицами:
должна быть предусмотрена возможность сортировки по одному — двум столбцам в каждой таблице;
должна быть предусмотрена возможность изменения содержимого (удаление, редактирование и добавление) в одной таблице;
таблицы должны изменяться динамически: без перезагрузки всей страницы;
при заполнении данных должны учитываться их тип (определённый в физической модели IDEF1x) и формат (наличие первой заглавной буквы, только символом кириллицы, количества нулей после запятой, запрет на пустую строку и т.д.);
выделение неверно заполненного поля (например, изменением цвета подписи) и предоставление примера правильного заполнения;
в качестве источника данных может выступать как реляционная база данных, так и xml–файл
Задачи:
1. Изучить основные подходы к созданию динамических web–страниц
2. Изучить способ реализации технологии AJAX
3. Изучить назначение, методы и свойства класса XMLHttpRequest
4. Изучить проблемы построения кроссбраузерных приложений на основе класса XMLHttpRequest
5. Изучить вопросы передачи информации между «клиентом» и «сервером» (кодировка,безопасность)
Структура отчёта:
1. Титульный
2. Содержание
3. Описание целей и задач
4. Описание системы
5. Исходные коды всех файлов, необходимые для развёртывания работоспособной версии работы
Пример лабораторной работы №6:
XML файл: остается без изменений
XSLT файл: остается без изменений, только между тегами head добавляется ссылка на JavaScript <script src="sort.js"></script>
PHP добавление client: остается без изменений
PHP добавление employee: остается без изменений
PHP удаление: остается без изменений
PHP замена: остается без изменений
JavaScript:
Описание: добавляем скрипт для того, чтобы производить сортировку значений по столбцам.
var TableSort = function (idTbl, defSortCol, firstRow, classes) {
var curSortCol = defSortCol; // номер колонки, по которой выполнена текущая сортировка, считаем с 0
var curImgCol = defSortCol; // номер колонки, у которой отрисовать картинку, показывающую направление сортировки, считаем с 0
var curSortUp = true; // направление сортировки вверх
var curIdTbl = idTbl; // id таблицы, в которой производим сортировку
var numColTr = (firstRow == null) ? 1 : firstRow; // номер строки, с которой идут данные, считаем с 0
if (classes == null) { // нужно ли учитывать классы для строк
var style = false;
} else {
var style = true;
var needClasses = classes[0] // список классов, которые нужно сохранить
var listClasses = classes[1]; } // список классов, которые чередуются
var tbl = document.getElementById(curIdTbl);
var allImgs = new Array(); // all imgs-arrow
allThs = tbl.getElementsByTagName('tr').item(0).getElementsByTagName('th');
for (i=0; i<allThs.length; i++){
if (allThs.item(i).getElementsByTagName('img') != null) {
allImgs[i] = allThs.item(i).getElementsByTagName('img').item(0);
} else {
allImgs[i] = null; } }
var tblData = new Array();
this.initSort = function (newCol, imgCol) { // начинаем сортировку по колонке newCol, картинку рисуем у колонки imgCol
if (newCol == curSortCol) {
curSortUp = !curSortUp; // кликнули на отсортированную колонку, меняем сортировку на обратную
} else {
curSortCol = newCol; // сортируем по новой колонке
curImgCol = (imgCol == null) ? newCol : imgCol;
curSortUp = true; }
showArrow();
getDataTable();
showSortTable();
if (style) {
doStyle(); } };
function showArrow(){ // показать / изменить стрелку
for (i=0; i<allImgs.length; i++){
if (allImgs[i] != null) {
if (i == curImgCol) {
allImgs[i].style.visibility = "visible";
if (curSortUp) {
allImgs[i].src = "./img/up.png";
} else {
allImgs[i].src = "./img/down.png"; }
} else {
allImgs[i].style.visibility = "hidden"; } } } }
function getDataTable() { // получить новые данные из таблицы
allTrs = tbl.getElementsByTagName('tr');
for (i=numColTr; i<allTrs.length; i++){
tblData[i-numColTr] = new Array();
for (j=0; j<allTrs[i].getElementsByTagName('td').length; j++) {
tblData[i-numColTr][j] = allTrs[i].getElementsByTagName('td').item(j).innerHTML; }
if (style) {
tblData[i-numColTr] [allTrs[i].getElementsByTagName('td').length]=allTrs[i].className; } }
tblData.sort(_sort);
if (!curSortUp) {
tblData.reverse(); } }
function _sort(a1, b1) { // правила сортировки
var a = a1[curSortCol];
var b = b1[curSortCol];
if (parseFloat(a) && parseFloat(b)) {
return parseFloat(a) - parseFloat(b);
} else {
if (a.toLowerCase() < b.toLowerCase()) {
return -1;
} else if (a.toLowerCase() > b.toLowerCase()) {
return 1;
} else {
return 0; } } }
function showSortTable() {
allTrs = tbl.getElementsByTagName('tr');
for (i=numColTr; i<allTrs.length; i++){
for (j=0; j<allTrs[i].getElementsByTagName('td').length; j++) {
allTrs[i].getElementsByTagName('td').item(j).innerHTML = tblData[i-numColTr][j]; }
if (style) {
allTrs[i].className=tblData[i-numColTr][allTrs[i].getElementsByTagName('td').length]; } } }
function doStyle(){
allTrs = tbl.getElementsByTagName('tr');
for (i=numColTr; i<allTrs.length; i++){
if (allTrs[i] == null) {
continue; }
if(needClasses.indexOf(allTrs[i].className) != -1) {
continue; }
allTrs[i].className = listClasses[(i % listClasses.length)]; } } }
Рис. 10. Сортировка таблицы client по столбцу name