Небольшое вступление.Каждый программист на JS рано или поздно
сталкивается с понятием «асинхронный яваскрипт», а сокращенно AJAX. На самом
деле в этой аббревиатуре нету ничего страшного, как могли бы подумать новички. В
общем — это технология, позволяющая асинхронно (одновременно, независимо)
обмениваться (получать и отправлять) информацию(ей) с другими ресурсами сервера
(файлами).
В javascript это всего-навсего объект, реализующий «транспорт»
для этой технологии. Это означает, что мы можем без обновления загруженной
страницы, по действию пользователя (событию JS), например, обратиться к файлу,
лежащему на нашем сервере, получить от него ответ (то что будет выводиться на
экран, если бы мы просто зашли по адресу на этот файл), проанализировать ответ в
JS и выполнить дальнейшие необходимые нам действия уже на странице с помощью
того же яваскрипта.
Итак, рассмотрим совсем небольшой кусок кода, самое
основное, что используется при работе с аяксом, а ниже уже разберем всё
поподробней.
try {
xhr = new XMLHttpRequest();
xhr.open('POST', '/AJAX_handler.php', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() { if( xhr.readyState == 4 ) alert( xhr.responseText ) }
xhr.send('var1=1&var2=Hello world');
}
catch(e) { alert(e.message) }
Что же означает вышеприведенный код
внутри блока try-catch?
- Первой строкой мы непосредственно создаем AJAX-объект, который организует
весь транспорт и вокруг которого всё «крутится».
- Вторая строка — мы инициализируем обращение (по указанному адресу) к методу
xhr.open, аргументами которого служат: метод передачи данных (POST/GET), адрес,
тип передачи (асинхронный или нет).
- Третья строка — устанавливаем заголовок (тип содержимого), который также
будет отправлен при AJAX-запросе.
- Четвертой строкой мы устанавливаем обработчик события, при получении статуса
или ответа по запрошенному адресу (для простоты понимания: это что-то вроде
window.onload только для аякса). В этом методе, после получения ответа, мы
проверяем статус в свойстве xhr.readyState и если ответ был получен
благополучно, просто выводим его на экран при помощи alert( xhr.responseText
)
- Последней строкой идет
запуск ракет на СШП, отправка всех
подготовленных данных объекта, заголовков, итд.
Как бы не
показалось это всё просто, но на самом деле нас поджидает множество
опасностей и приключений «подводных камней», основные из которых я привел
ниже:
- Объект XMLHttpRequest, как бы мы сразу заметили, совсем не существует в
Internet Explorer меньше 8-й версии. Но это совсем не означает, что
мы все
умрем мы не сможем работать с аяксом в IE. Microsoft решили эту проблему с
помощью их любимого ActiveXObject, написав необходимый «костыль», благодаря
которому стало возможным получить необходимый объект при помощи создании объекта
через xhr = new ActiveXObject('MSXML2.XMLHTTP.3.0');
- Еще один «камень в огород» Internet Explorer: асинхронные GET-запросы
оказывается вдруг кэшируются и в итоге мы получаем не совсем то, что ожидали.
Благо эту проблему можно решить простым добавлением любого уникального
идентификатора при обращении по нужному нам URL. Например, при помощи
Math.random()
- Для того чтобы правильно отправлять и получать данные аяксом, перед
отправкой (метод xhr.send) нам нужно будет их закодировать при помощи
encodeURIComponent(), чтобы никакие спецсимволы не помешали правильной обработке
данных по указанному адресу
- Также одна из первых проблем, с которой сталкиваются при работе с AJAX - это
кодировка. На сервере мы отдаем русскоязычный текст - а на выходе имеем
«кракозябры». И первоочередной ваш вопрос будет вроде «ВОТЗЕФАК»!? Как обычно,
нет причин для паники, чтобы спокойно работать с русскоязычными данными нам
надо, всего навсего, работать только с кодировкой UTF-8. Везде. Т.е данные
отдаваемые сервером (например AJAX_handler.php) и отображаемые на странице,
показываемой пользователю (например в index.html) - все должны быть сохранены в
кодировке UTF-8 (NO-BOM).
- Из-за ограничений в связи с безопасностью, объект XMLHttpRequest может
работать с адресами только внутри одного локального домена (т.е AJAX-запрос с
http://site1.ru на http://site2.ru просто так не прокатит, для этого есть одна
хитрость, которая уже не имеет отношения к объекту XMLHttpRequest и она будет
описана ниже)
- Для того чтобы загружать файлы на сервер (Content-type уже должен быть
«multipart/form-data» вместо «application/x-www-form-urlencoded») нам также не
обойтись объектом XMLHttpRequest. Для этого будет ниже описана вторая хитрость,
с помощью которой можно организовать подобное, но она будет иметь несколько
недостатков.
Кросс-доменный AJAX? Итак, чтобы попытаться организовать асинхронный
обмен информацией с файлами, лежащими на другом сервере мы будем динамически
создавать и загружать в DOM HTML-документа тег «script». Рассмотрим код
ниже:
script = document.createElement('script'); // создаем HTML-элемент (тег) script.
script.src = form.action + '?'+ Date.parse(new Date); // устанавливаем адрес подгружаемого скрипта
script.type = 'text/javascript'; // устанавливаем тип содержимого
if( script.onload !== undefined ) script.onload = handler; // выставляем обработчик при загрузке скрипта для основной массы браузеров
else script.onreadystatechange = handler; // выставляем обработчик для IE 6(7)
document.body.appendChild( script ); // добавляем созданный HTML-объект (тег) внутрь DOM-дерева
Обработчик
для фиксирования загрузки скрипта для большинства браузеров срабатывает на
событие «onload», когда в IE 6(7) — требуется «onreadystatechange». Попытки
получить содержимое текста скрипта не увенчались успехом (кроме оперы 10, в ней
его можно получить через свойство «text» у созданного в примере объекта
«script»). Поэтому внутри подгружаемого файла нужно инициализировать имена
переменнных, чтобы в дальнейшем иметь возможность с ними работать. В итоге
примерный вид подгружаемого файла с другого сервера будет выглядеть примерно
так:
json = {"x": "1", "y": "2", "z": "3", "greet": "Hello from http://my-cross-domain-site.ru"}
loaded = true;
Основным минусом такого подхода является то, что мы
можем работать только с методом отправки данных GET. Также стоит отметить, что
добиться кросс-доменности аякса можно и средствами XMLHttpRequest, но в в этом
случае нам придется в дополнение к нему использовать серверный скрипт (например
PHP) для организации проксирования запросов на другие домены. Т.е например,
ajax.html обращается на ajax.php (на одном домене), после чего ajax.php при
помощи сокетов или curl отправляет запрос ну нужный сайт, получает ответ и
отдает его обратно в ajax.html
Загрузка файлов на сервер при помощи AJAX? Для того, чтобы выбранный
файл в поле ввода input type="file" был загружен и обработан на сервере,
аттрибут формы «enctype» должен быть установлен в «multipart/form-data». Но
при использовании стандартного объекта для работы с аяксом XMLHttpRequest,
ничего такого не происходит и никакие файлы не будут загружены на сервер. Что же
делать в этом случае? И как обычно, на любую проблему найдется решение. Хоть
и не идеальное. Для этого способа, как и вышеописанном способе про
кросс-доменный аякс, нам понадобиться дополнительный HTML-элемент. И первое
что приходит в голову — это IFRAME. Мы будем создавать динамически или
использовать уже готовый созданный на странице невидимый ифрейм, и в при
отправке формы данные будут уходить в него. Для этого на форме аттрибут
«target» должен совпадать с именем ифрейма (name). Также не стоит забывать, в
целях безопасности, данные загруженного ифрейма могут быть получены только в
пределах одного домена.
Примерный код для загрузки файла на сервер без
перезагрузки основной страницы приведен ниже (пример приведен в случае, если
ифрейм еще не существует на странице):
iframe = document.createElement('iframe'); // создаем HTML-элемент (тег) iframe.
iframe.name = form.target = Date.parse(new Date); // создаем уникально имя для iframe и одноименное значение target для нашей формы
iframe.style.display = 'none'; // скрываем отображение ифрейма на странице через CSS
document.body.insertBefore( iframe, form ); // добавляем созданный HTML-объект (тег) внутрь DOM-дерева
iframe.onload = iframe.onreadystatechange = function(){ handler( this.contentWindow.document.body.innerHTML ) } // выставляем обработчик при загрузке страницы внутри ифрейма
Первый
минус этого метода — при загрузке файла в IE, если в винде включены звуки, то
будет слышен характерный стандартный щелчок, как при загрузке страницы.
Второй минус это то, что кнопка браузера «Назад» будет возвращать назад
страницу, которая была загружена в ифрейме до загрузки файла.
Что мы имеем в итоге? В итоге, подытожив все разобранные выше
способы асинхронной передачи данных, я собрал их все в одну функцию для работы с
AJAX, в первый аргумент которой мы «скормим» непосредственно объект формы
(например document.forms[0]) или же его симуляцию через JSON-объект, в случае
если мы работаем с аяксом без использования формы: {'action': '/test.php',
'method': 'post', elements:[{'name': 'var1', 'value': '1'}, {'name': 'var2',
'value': '2'}]} Вторым аргументом будет выступать функция обработчик, которая
будет выполняться только после получения окончательного ответа от AJAX-запроса
(первым её аргументом будет содержимое полученного файла).
Итак, готовая
к употреблению внутрь функция для работы с AJAX приведена ниже, вместе с
примерами по каждому из трех способов работы с аяксом.
function simpleAjax() {
var xhr, form, handler, method, enctype, action_url, same_domain;
form = arguments[0];
handler = arguments[1];
method = (form.method || arguments[2] || 'GET').toUpperCase();
enctype = (form.enctype || 'application/x-www-form-urlencoded').toLowerCase();
action_url = form.action.replace(RegExp(location.protocol +'//'+ location.host, 'gi'), '');
same_domain = action_url.match( RegExp('http://([a-z0-9-.]+)/.*', 'i') ) == null ? true : false;
try {
if( enctype.indexOf('multipart') != -1 )
{
// AJAX-iframe (loading files on server)
if( typeof iframe == 'undefined' )
{
if( !form.target ) {
iframe = document.createElement('iframe');
iframe.name = form.target = Date.parse(new Date);
iframe.style.display = 'none';
document.body.insertBefore( iframe, form );
}
else {
iframes = document.getElementsByTagName('iframe');
for(i = 0; i < iframes.length; i++) if( iframes[i].name == form.target ) iframe = iframes[i];
}
iframe.onload = iframe.onreadystatechange = function(){ handler( this.contentWindow.document.body.innerHTML ) }
}
}
else {
// AJAX-XMLHttpRequest (same domain only)
if( same_domain )
{
// create
xhr = window.XMLHttpRequest ? new XMLHttpRequest() : ( window.ActiveXObject ? new ActiveXObject('MSXML2.XMLHTTP.3.0') : null );
// prepare
datasend = '';
data = form.elements;
for(i = 0; i < data.length; i++)
{
item = data[i];
item.value = item.type == 'checkbox' ? item.checked * item.checked : item.value;
datasend += (item.name +'='+ encodeURIComponent( item.value ) + '&');
}
// processing
request = form.action;
xhr.open(method, request + ( method == 'GET' ? (request.indexOf('?') == -1 ? '?' : '&')+ Math.random()*10E25 : '' ), true);
xhr.setRequestHeader('Content-type', enctype);
xhr.onreadystatechange = function() { if(xhr.readyState == 4 && handler != null) handler( xhr.responseText ) }
xhr.send(datasend);
}
// AJAX-script (cross-domain, get method only)
else
{
script = document.createElement('script');
script.src = form.action + '?'+ Date.parse(new Date);
script.type = 'text/javascript';
if( script.onload !== undefined ) script.onload = handler;
else script.onreadystatechange = handler; // IE 6,7
document.body.appendChild( script );
}
}
}
catch(e) { alert( e.message ) }
}
|