>>>

Проектирование, разработка и оптимизация веб-приложений

Альтернативные AJAX-у методы передачи данных

Использование <IFRAME>

На наш взгляд, метод с использованием плавающих фреймов довольно неуклюж. Он использовался ранее, когда браузеры не поддерживали XMLHttpRequest.

В сердце технологии лежит функция создания скрытого фрейма:

function createIFrame() {
  var 
id 'f' Math.floor(Math.random() * 99999);
  var 
div document.createElement('div');
  
div.innerHTML '<iframe style="display:none" src="about:blank"'
    
+' id="'+id+'" name="'+id+'" onload="sendComplete(''
    +id+'')"></iframe>'
;
  
document.body.appendChild(div);
  return 
document.getElementById(id);
}

Для загрузки файла мы будем использовать HTML-форму, результат вызова которой направим в созданный IFRAME.

function sendForm(formurlfuncarg) {
  if (!
document.createElement) return; // not supported
  
if (typeof(form)=="string"form=document.getElementById(form);
  var 
frame=createIFrame();
  
frame.onSendComplete = function() { func(arggetIFrameXML(frame)); };
  
form.setAttribute('target'frame.id);
  
form.setAttribute('action'url);
  
form.submit();
}

function 
sendComplete(id) {
  var 
iframe=document.getElementById(id);
  if (
iframe.onSendComplete && typeof(iframe.onSendComplete) == 'function')
    
iframe.onSendComplete();
}

function 
getIFrameXML(iframe) {
  var 
doc=iframe.contentDocument;
  if (!
doc && iframe.contentWindowdoc=iframe.contentWindow.document;
  if (!
docdoc=window.frames[iframe.id].document;
  if (!
doc) return null;
  if (
doc.location=="about:blank") return null;
  if (
doc.XMLDocumentdoc=doc.XMLDocument;
  return 
doc;
}

Функция sendComplete будет вызвана по окончании загрузки фрейма. Её задача - обработка результата операции, либо просто уведомление пользователя о завершении загрузки. Для этого будет вызвана пользовательская программа func с двумя аргументами: пользовательский arg, плюс DOM-результат, возвращенный сервером. Мы подразумеваем, что сервер возвращает XML. Для его извлечения из фрейма служит довольно громоздкая кроссбраузерная функция getIFrameXML.

Приведем пример:

<script type="text/javascript">
// ... сюда необходимо скопировать все вышеописанные функции  ...var cnt=0;

function 
uploadComplete(elementdoc) {
  if (!
doc) return;
  if (
typeof(element)=="string"element=document.getElementById(element);
  
element.innerHTML='Результат запроса #'+(++cnt)
    +
': '+doc.documentElement.firstChild.nodeValue;
}

</
script>

<
form id="ajaxUploadForm" method="post" enctype="multipart/form-data"
onsubmit="sendForm(this,'uploadFile.php',uploadComplete,'resultDiv');return true;">
<
label>Файл: <input type="file" name="uploadFile" /></label>
<
input type="submit" value="Загрузить" />
</
form>
<
input type="button" value="Альтернативный вызов загрузки файла"
onclick="sendForm('ajaxUploadForm','uploadFile.php',uploadComplete,'resultDiv')" />
<
div id="resultDiv"></div>

Текст файла uploadFile.php:

header("Content-type: application/xml; charset=UTF-8");
echo '&lt;?xml version="1.0" encoding="UTF-8" ?>' ?>
<result>Получен файл [&lt;?php echo($_FILES['uploadFile']['name']); ?>]
 размером &lt;?php echo($_FILES['uploadFile']['size']); ?> байт</result>

Использование <SCRIPT>

Следует признать, что данный AJAX-метод является самым лаконичным на стороне клиента. Кроме того, по сравнению с методами XMLHttpRequest/IFRAME он имеет одно важное преимущество: AJAX-запросы можно направлять не только к собственному, но и к чужим серверам. Это преимущество, правда, может обернуться существенным недостатком, если вы обеспокоены вопросами безопасности и не хотите, чтобы к вашему серверу обращались пользователи других серверов. Если же вы наоборот хотите разместить этим способом у себя на странице чужой AJAX-виджет, также будьте бдительны: выбранный вами AJAX-провайдер сможет в любой момент внедрить на вашу страницу любой (в том числе вредоносный) код. Например, подсматривающий пароли ваших пользователей: В общем, подходить к использованию данного метода следует с осторожностью, осознавая все плюсы и минусы.

В своей основе лежит следующий метод:

function callServer() {
  var 
script document.createElement("script");
  
script.src 'http://domain.ru/dynamicDataScript.php';
  
script.type 'text/javascript';
  
document.body.appendChild(script);
}

Серверный скрипт dynamicData.php возвращает код JavaScript, который незамедлительно выполняется в браузере клиента. Данный код может, например, как минимум, загрузить в переменные (var) новые значения, как максимум - полностью перерисовать Web-страницу. Единственное, что он не может делать, это использовать функцию document.write() для вставки HTML-кода (данная функция доступна только при первичной загрузке страницы). Ограничение весьма условное, так как все то же самое можно реализовать через DOM и/или innerHTML.

<script language='Javascript' type="text/javascript">
//--создаем элемент script и присваиваем ему значение
function sendQ(url){
   var 
elem document.createElement("script");
   
obj=document.body.insertBefore(elemdocument.body.firstChild);
   
obj.setAttribute("id""js");
   
obj.setAttribute("language""Javascript");
   
obj.setAttribute("type""text/javascript");
   
obj.setAttribute("src"url);
   
setTimeout('del()'0);
}

function 
del(){    // удаляем только что созданный элемент script
   
var obj=document.getElementById('js');
   
document.body.removeChild(obj);
}
</
script>

<
p onclick="sendQ('js.php')"><b>кликни меня</b></p>
<
div id="strif">получи время сервера в секундах</div>

js.php:

document.getElementById('strif').innerHTML='< ?=time()?>';

Вся эта гибкость, конечно же, кажется нам чрезмерной - хочется как-то систематизировать процесс, ввести ряд ограничений, хотя бы на уровне договоренностей. Во-первых, стоит договориться о том, что никаких операций со страницей серверный JavaScript-код не производит, а лишь передает данные (это будет полезным и с точки зрения его переиспользования на других страницах сайта). В связи с этим весьма полезным представляется набор соглашений JSON (JavaScript Object Notation) по представлению данных в формате, удобном для обработки интерпретатором JavaScript.

JSON (JavaScript Object Notation)

По сути, JSON - это JavaScript-код, описывающий некую структуру данных. В нем используются две основные синтаксические конструкции:

// объявление массива:
var array = [ v1, v2, ... ];
// объявление ассоциативного массива:
var hash = { "key1" : v1, "key2" : v2, ... };

С их помощью можно описать структуру данных произвольной сложности. Например:

{
    "firstName": "Иван",
    "lastName": "Федоров",
    "address": {
        "street": "Ордынка",
        "city": "Москва",
        "postalCode": 127327
    },
    "phoneNumbers": [
        "495 765-1234",
        "916 123-4567"
    ]
}

Если предположить, что вышеприведенный текст находится в переменной JSON_text, то работать в JavaScript с ним становится очень удобно:

var p = eval("(" + JSON_text + ")");

div.innerHTML = p.firstName+" "+p.lastName+
	" живет в городе "+p.address.city;

Просто несравнимо по удобству с манипулированием моделью XML/DOM!

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

Для построения законченного AJAX-приложения нам не хватает лишь одного - уведомления клиента о том, что процесс загрузки данных завершен. Сам по себе JSON такой возможности не предоставляет. Можно конечно грузить JSON-данные через XMLHttpRequest.responseText, используя все возможности последнего по контролю за завершением соединения, однако, это не есть тема которая нам сейчас важна.

JSONP: JSON With Padding

Для устранения указанного выше недостатка была предложена концепция JSONP (JSON With Padding). Она состоит в том, что в запрос к серверу добавляется параметр callback, в котором клиент указывает имя функции, которую необходимо вызвать для обработки данных. Для иллюстрации приведем простой серверный PHP-скрипт (sample_ajax_script_json.php), возвращающий данные в формате JSONP:

echo($_REQUEST['callback']
.'({"result":"Данные из файла sample_ajax_script_json.php"})');

Если обратиться к скрипту с запросом: sample_ajax_script_json.php?callback=onComplete123, в ответ мы получим строку:

onComplete123({"result":"Данные из файла sample_ajax_script_json.php"})

Нам остается лишь обеспечить наличие функции onComplete123(), которая отобразит результат запроса.

Теперь постараемся сделать нашу AJAX-систему универсальной:

// в ассоциативном массиве callbacks мы будем динамически
// создавать и хранить до завершения запроса все
// callback-функции (ведь AJAX-запросы могут поступать
// одновременно от разных компонент на Web-странице)

var callbacks=new Object();

function callJSONP(url, func, arg) {
  var cbId;
  // генерируем уникальный callback-id:
  do cbId = 'c' + Math.floor(Math.random() * 99999);
      while (callbacks[cbId]);
  // создаем callback-функцию для данного запроса:
  callbacks[cbId] = function(obj)
          { func(arg, obj); delete callbacks[cbId]; };
  // создаем элемент script:
  var script = document.createElement('script');
  // сообщаем серверу имя нашей функции:
  script.src = url+(url.indexOf('?')>=0 ? '&' : '?')+
              'callback=callbacks.'+cbId;
  script.type = 'text/javascript';
  // делаем запрос к серверу:
  document.body.appendChild(script);
}

В заключение приведем пример использования:

<script type="text/javascript">
// ... сюда необходимо скопировать вышеописанную функцию callJSONP() ...
function showHTML(element, responseObject) {
  if (typeof(element)=="string") element=document.getElementById(element);
  element.innerHTML=responseObject.result;
}
</script>

<input type="button" value="Загрузить!"
 onclick="callJSONP('sample_ajax_script_json.php',showHTML,'targetDiv')"/>
<div id="targetDiv">Здесь появится
 результат вызова sample_ajax_script_json.php</div>

Размер шрифта:
А
А
А
Цвет сайта:
A
A
A
Изображение:
Вкл.
Выкл.
Обычная версия