Часть третья про 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.
На этом про адресную строку все, последнее, о чем упомяну перед новой темой – это как быть, если вы 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.
Далее, при загрузке файлов интерес представляет задача мониторинга за процессом загрузки: какой процент этой операции уже выполнен, а результаты отображать в виде растущей полоски 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-основанных решений не возможно. Я рекомендую использовать браузер firefox с установленным плагином firebug (возможно применять и livehttpheaders). Затем, открыв окно firebug (меню Tools->Firebug->Open firebug), вы переходите на закладку “Net” и видите перечисление всех асинхронных запросов, которые были сделаны со страницы, также видите какие данные вы отправили на сервер и которые получили, видите и значения служебных заголовков. Пример окна firebug показан на рис. 4.
Я снова не успел рассказать об xajax – библиотеке создающей некоторый слой-посредник сглаживающий различия между javascript-кодом и серверным кодом (php, asp.net, …) – наверное это и к лучшему. Я решил, что стоит собрать больше материала и написать отдельную серию статей посвященных интересным для php cmf-ам – наборам функций, библиотекам, методикам написания кода. Там найдется место и для xajax и symfony и cakphp. Ждите.
|
|
Subscribe Now! |
|



