Часть третья про ajax

Материал из DOM

Перейти к: навигация, поиск

[править] Ajax: прошлое, настоящее, будущее. Часть 3

Сегодня мы продолжаем и заканчиваем знакомство с технологией асинхронных вызовов – ajax. В прошлый раз я рассказал о том, что html-страница может подгружать данные с сервера в различных форматах: xml – мы говорим об ajax, в формате json – мы говорим об ajaj. Я рассказал о возможностях, которые представляет библиотека jquery, позволяя нам загружать асинхронно информацию, управлять форматом принимаемых данных, обрабатывать ошибки. Сегодня я смещу фокус рассмотрения материала с того “как бы хотя бы вызвать что-то” к тому “как бы это сделать удобным для пользователя”. Также я расскажу об том, как можно загружать на сервер файлы, как работать с историей браузера.

Начнем мы, однако, с рассмотрения простой и очевидной, но забываемой в бездумной погоне за высокими технологиями проблемы. Окно браузера состоит из множества различных “штуковин”, и самой главной является строка ввода адреса. Пользователь верит, что если он введет в эту строку некоторый адрес, нажмет кнопку ввод, то откроется специальная страница. В принципе, так все и было до того, как начали получать широкое распространение сайты сделанные целиком на flash и ajax. Особенность таких сайтов в том, что после того как flash-ролик был загружен, он реализует собственную логику обработки действий пользователя. Так, когда пользователь жмет на ролике кнопку “о компании”, то ему flash-ролик подгружает содержимое из внешнего ресурса, создает определенный gui, в который помещается информация - но и ни одно из этих действий не отображается в адресной строке браузера. Для ajax-основанных сайтов все то же самое: мы подгружаем внешнее содержимое, вид страницы меняется – но адресная строка остается без изменений. Полгода назад мне попался на глаза сайт сделанный веб-студией Сами_Знаете_Кого, сайт этот представлял некоторый реестр юридической информации, или финансовой – не важно. Главное в том, что информации было много, и она была организована в виде иерархии разделов. Т.е. на странице сбоку было меню в форме дерева, можно было раскрывать узлы – категории документов, и, в конце концов, добраться до собственно хранимых документов. Подгрузка содержимого узлов дерева делалась с помощью ajax, равно как и загрузка содержимого документа в центральную часть страницы. В адресной строке при этом ничего не менялось и представляете себе удовольствие, которое получал безымянный сотрудник, которому нужно было послать по почте ссылку на документ, хранящийся в этом реестре. Вопрос в лоб: можно ли изменить содержимое адресной строки с помощью javascript, flash или как-то еще. Нет, к счастью нет. Знатоки javascript скажут, что можно вызвать метод: “window.location.href = ‘новый_адрес_для_перехода’ ”. Отлично, но какой тут ajax? Если вы при этом покинете страницу, перейдя по новому адресу. Надеяться на то, что адресную строку можно менять без перехода по другому адресу бессмысленно – в Интернете широко распространилось такое явление как “фишинг” – подделка сайта, выдача собственного сайта за другой. Так что, ни один браузер не оставит такую потенциально опасную функцию. Все что нам доступно - это изменять якорь. Ту самую часть адреса, которая расположена после имени документа и отделена от него символом “#”. Соответственно, когда страница загружается, вы внутри javascript-кода страницы должны проверить значение этого якоря, и выполнить загрузку необходимых ресурсов. Аналогично, на каждое действие пользователя загружающее в страницу новую информацию должно изменять значение этого якоря. Например, в примере ниже я создал две страницы php с информацией – они называются contacts.php и documents.php. Также есть главная страница html, содержащая две кнопки, загружающие предыдущие файлы во внутрь тега div. Загрузка идет с помощью метода load, который можно применить к любому узлу документа найденного с помощью вызова $(‘то_что_надо_найти’). В качестве параметров этому вызову я передаю адрес документа для загрузки - некоторый ассоциативный массив с переменными, и функцию, срабатывающую в том случае, если загрузка была успешна. В теле функции я изменяю значение переменной window.location.hash. Когда страница загружена, анализирую чему равна переменная window.location.href и вызываю функции загрузки нужного содержимого. Ниже пример кода главного html-файла:

<script type="text/javascript" src="jquery.js"> </script>
 
<script>
function onLoadDocuments (){
 // здесь выполняется ajax-вызов 
 // а тут мы меняем значение адресной строки
 $("#doc_zone").load("documents.php",
   {year: 2007, month: 1, day: 12},// некоторые параметры нужные для работы php-файла
   function() { 
     window.location.hash = 'documents';
   } 
  );
}
 
function onLoadContacts (){
 // здесь также идет ajax-вызов 
 // а тут мы меняем значение адресной строки
 $("#doc_zone").load("contacts.php",
  {street_no: 125},
  function() { 
    window.location.hash = 'contacts';
  }	 
 );
}
 
$(document).ready(
 function(){ 
   if (window.location.hash == '#documents'){
    // при загрузке страницы смотрим чему равно значение якоря 
    // и выполняем загрузку нужного содержимого
    onLoadDocuments ();
   }
   if (window.location.hash == '#contacts'){
    onLoadContacts ();
   }
 }
);
</script>

<body>
 <div id="doc_zone" style="border: 2px solid black; margin: 10px; padding: 10px;">
  DocZone ...
 </div>
 
 <input type="button" onclick="onLoadDocuments ()" value="load documents"/>
 <input type="button" onclick="onLoadContacts ()" value="load contacts"/>
 
</body>

Результат работы скрипта показан на рис. 1.

Изображение:ajax_c_1.png

На этом про адресную строку все, последнее, о чем упомяну перед новой темой – это как быть, если вы flasher и решили озаботиться поддержкой адресации роликов. Вовсе необязательно изобретать очередной велосипед. Метод работы с window.location.hash, который я вам показал, лег в основу достаточно известной библиотеки swfaddress, ее домашний сайт: http://www.asual.com/swfaddress/.

Вторая функция, которой привык пользоваться типовой посетитель сайта: кнопки “назад” и “вперед”. Предполагается, что, нажав “назад”, клиент увидит предыдущую страницу. Увы и опять, ничего подобного не происходит. Вообще до тех пор, пока не будет реализована поддержка ajax-юзабилити на уровне браузера говорить о промышленном внедрении ajax-идей просто бессмысленно. Снова попробуем имитировать отсутствующую функциональность. Неприятность в том, что разные браузеры ведут себя по разному. Так opera и firefox при изменении свойства “window.location.hash” или же “window.location.href” (в этом случае отличия только в части после “#”) сохраняют адрес в историю как НОВЫЙ. Т.е. при нажатии на кнопку “вперед”|”назад” будет меняться значение адресной строки, а физически мы будем оставаться на той же странице. Как отследить момент изменения адресной строки я не нашел. Так попытка установить обработчик события: “window.onunload = то_что_ будет_вызвано_при_ выгрузке_страницы;” и “window.onload = то_что_будет _вызвано_при_загрузке _страницы;” ни к чему не привели. События не генерируются. В объекте history нет никаких событий или свойств позволяющих реагировать на изменении адреса страницы. Максимум, до чего я додумался – это создать функцию, вызываемую по таймеру, которая бы с интервалом, скажем, в 1000 миллисекунд проверяла бы значение адресной строки и вызывала бы соответствующую функцию загрузки содержимого. Например, так:

function testIfAddChanged (){
 // проверяем, какой адрес сейчас текущий и выполняем загрузку нужного содержимого
 if (window.location.hash == '#contacts')
  onLoadContacts ();
 if (window.location.hash == '#documents')
  onLoadDocuments ();
}
// запускаем таймер
window.setInterval ("testIfAddChanged()" , 1000);

Это почти завершенный пример, не хватает только пары проверок позволяющих избежать дублирующиеся загрузки содержимого, но это, я уверен, вы сделаете и сами. Что касается самого “замечательного” в мире браузера internet explorer, то он снова показал свой норов. Изменяй “window.location.hash” или “window.location.href” – ему без разницы - в историю никаких новых записей не вносится. Следовательно, никакой поддержки кнопок “назад”/”вперед” сделать нельзя, хотя я и не проверял как ведет себя седьмая версия ie.

Теперь разберем вопрос загрузки на сервер файлов. Здесь все ужасно плохо. Родной поддержки отправки файлов ajax не имеет. Нам придется имитировать данный процесс с помощью flash или хитроумных хаков. Начнем с вопроса: что может отправить на сервер файл? Очевидно, только форма (тег “form”). Очевидно, что прямой программный доступ к файловой системе из javascript - это страшная дырка в безопасности браузера и ее быть не должно. Итак, форма, но отправка ее приводит к полной перезагрузке страницы – не подходит. Или все же подходит, но если сделать так, что форма будет отправляться не с нашей веб-страницы, а с чего-то другого. Например, внедрим в страницу невидимый плавающий фрейм, содержащий форму, заполним ее информацией и скажем submit и форма отправилась. Или еще проще, форма расположена в главной файле html, а ее свойство target (имя окна в котором должна открыться страница) указывает на тот самый невидимый фрейм.

<form target="upload_frame" action="upload_files.php" enctype=” multipart/form-data>
 <input type="file" name="upload_file" /><br />
 <input type="submit" />
</form>
<!-- а вот невидимый frame в котором и будет выполняться загрузка данных -->
<iframe name="upload_frame" style="display: none"></iframe>

Можно использовать библиотеку Д. Котерова JsHttpRequest, в ней поддержка отправки форм делается прозрачно. В первой статье серии я уже упоминал об этой библиотеке, но напоминаю, что загрузить ее и прочитать примеры использования можно по адресу: http://dklab.ru/lib/JsHttpRequest/. В следующем примере я создам форму(без нее никак ведь нам нужно отобразить диалог выбора файла, а сделать это без формы или flash не возможно) в форме можно выбрать файл с картинкой, а также указать имя клиента в текстовом поле. После отправки данных на сервер, возвращаются три переменные: одна строковая содержит приветствие, вторая - признак того, что файл удалось успешно загрузить, третья переменная содержит имя файла с картинкой после загрузки на сервер. Это имя используется для установки значения src тега “пустой” картинки расположенной сразу после формы. Общее замечание: важно для формы указать значение onsubmit="return false" – для того чтобы форма не отправилась на самом деле. И вот пример кода на стороне клиента:

<!-- подключаем библиотеку -->
<script src="koterov/lib/JsHttpRequest/JsHttpRequest.js"></script>
<script language="JavaScript">
 function say_hello() {
  JsHttpRequest.query(
  'make_load_img.php', // вызываемый файл
  { // передаем простые текстовые значения
   'username': document.getElementById("username").value, 
   // а также файл картинки для загрузки
   'img_file': document.getElementById("img_file") 
  },
  // Эта функия вызвается когда данные от сервера пришли
  function(result, errors) {
    if (result.file_was_uploaded){
     document.getElementById("img_foto").src = result.nname; 
     document.getElementById("hello_div").innerHTML = result.greetings; 
    }
    else 
      document.getElementById("hello_div").innerHTML = 'Файл не был загружен, ошибка'; 
  },
  false  
  ); 
}
</script>
<form method="post" enctype="multipart/form-data" onsubmit="return false">
 Укажите ваше имя: <input type="text" id="username">
 <br>
 И укажите картинку с вашей фото: <input type="file" id="img_file">
 <br>
 <input type="button" value="Представиться" onclick="say_hello()">
</form>
<div id="hello_div" style="border:4px dotted green;">
   Hello Tag Zone
</div>
<img src="" id="img_foto" />

А вот пример кода на стороне сервера (php-код).

<?php
 require_once "koterov/lib/JsHttpRequest/JsHttpRequest.php";
 // создаем объект JsHttpRequest и указываем кодировку входных данных
 $JsHttpRequest =& new JsHttpRequest("windows-1251");
 // После чего мы можем обращаться к входным данным как к обычным перемнным
 // Результаты работы оформаленные в виде множества переменных 
 // мы помещаем внутрь специального массива _RESULT из библиотеки JsHttpRequest
 
 $status = true;
 // проверяем то был ли успешно загружен файл или нет
 $nname2 = dirname (__FILE__) . '/img/' . $_FILES['img_file']['name'];
 $nname =  'img/' . $_FILES['img_file']['name'];
 if (move_uploaded_file ($_FILES['img_file']['tmp_name'], $nname2)){
  //и если да, то копируем его в папку img - хранилище загружаемых на сервер картинок
  $status = true;
 }
 else  
  $status = false;
 
$GLOBALS['_RESULT'] = array(
 "greetings"   => 'Привет ' . $_REQUEST ['username'],
 "file_was_uploaded"   => $status,
 "nname"   => $nname // возвратим имЯ этого загруженного файла с картинкой на сервере
);
?>

Результат работы скрипта показан на рис. 2.

Изображение:ajax_c_2.png

Далее, при загрузке файлов интерес представляет задача мониторинга за процессом загрузки: какой процент этой операции уже выполнен, а результаты отображать в виде растущей полоски progressbar. Сразу скажу, что применять методику описанную далее имеет смысл только если размер файла достаточно велик, иначе будет достаточно просто показать некоторую анимированную картинку (например, часы) – мол ждите, загрузка идет. Итак если файл велик и надо следить за тем как она загружается, то … прежде всего эта задача не так и проста и однозначна. Когда вы вызываете некоторый php-скрипт, то он не получает управления до момента полной, я подчеркиваю, полной загрузки всех передаваемых ему данных и файлов. Но все загружаемые файлы помещаются в некоторую временную папку, общую для всех php-скриптов на сервере. Очевидно, что по мере загрузки данных, файл будет расти. Итак файл, растет, затем вызвается php-скрипт, и по окончанию работы скрипта, файл будет удален, так что программисту в общем случае следует позаботиться об его сохранности (в предыдущем примере я использовал для этого функцию move_uploaded_file). Само названии технологии ajax – асинхронные вызовы, говорит нам что можно запустить один процесс, который будет загружать файл, а также с некоторым интервалом запускать процесс, который будет отслеживать размер растущего временного файла и возвращать это число в код javascript, там мы его уже можем использовать в роли progressbar. Увы, увы, это будет работать только в идеальной ситуации. Например, у вас на локальном сервере, или даже в Интернете, если нагрузка на сервер не велика. Проблема в том, как узнать какой файл является временным? Хорошо если ваш хостинг (виртуальный – наиболее типичный и дешевый вариант хостинга – предполагает что на одном физическом компьютере выполняется несколько веб-сайтов) настроен таким образом, что у каждого сайта (клиента) собственная папка для временных файлов. Но даже если это так, то вам нужно надеяться только на то, что в одно и тоже время не будет запущен еще один (любой скрипт) загружающий на сервер файлы. Например, по адресу http://www.sql.ru/forum/actualthread.aspx?tid=299200, находится подобное почти рабочее решение - рискуйте. Лучший вариант - использовать flash. Например, библиотека swfupload, ее можно загрузить на сайте http://swfupload.mammon.se/. Вот пример кода с комментариями:

<script type="text/javascript" src="SWFUpload.js"></script>
<script type="text/javascript">
 var swfu;
 
 window.onload = function() {
  // Создаем объект-загрузчик
  swfu = new SWFUpload({
  target:"zed",
  // здесь указывается блок div внутрь которого будут помещены кнопки выбора файла и кнопки отправки
  create_ui : true,// создать ли эти кнопки автоматически
  upload_script : "upload.php",// файл которому будет передан файл
  flash_path : "SWFUpload.swf",// имя к flash¬-части библиотеки
  upload_progress_callback : 'uploadProgressFunction',// фукнция вызываемя во время загрузки
  flash_loaded_callback : "swfu.flashLoaded"			
  }
 );
}
 
function uploadProgressFunction(file, bytesloaded, bytestotal) {
 var progress = document.getElementById("yourprogressid");
 // нам передается размер файла, и количество уже выгруженных на сервера байт
 var percent = Math.ceil((bytesloaded / bytestotal) * 100);
 progress.innerHTML = percent + "%";}
 // кусок html куда будут выводиться результаты работы
</script>
 
<div id="zed"></div>
<div id="yourprogressid"> %</div>

Результат работы скрипта показан на рис. 3.

Изображение:ajax_c_3.png Закончить эту статью не упомянув пару слов про отладку ajax-основанных решений не возможно. Я рекомендую использовать браузер firefox с установленным плагином firebug (возможно применять и livehttpheaders). Затем, открыв окно firebug (меню Tools->Firebug->Open firebug), вы переходите на закладку “Net” и видите перечисление всех асинхронных запросов, которые были сделаны со страницы, также видите какие данные вы отправили на сервер и которые получили, видите и значения служебных заголовков. Пример окна firebug показан на рис. 4.

Изображение:ajax_c_4.png

Я снова не успел рассказать об xajax – библиотеке создающей некоторый слой-посредник сглаживающий различия между javascript-кодом и серверным кодом (php, asp.net, …) – наверное это и к лучшему. Я решил, что стоит собрать больше материала и написать отдельную серию статей посвященных интересным для php cmf-ам – наборам функций, библиотекам, методикам написания кода. Там найдется место и для xajax и symfony и cakphp. Ждите.

Subscribe Now!

 

ObMachine projects & articles (java, flash, flex, php, ...)  -- black-zorro.com