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