|
Последняя модификация: 10.08.2014 г Страница загружена с адреса: http://webdesign.site3k.ru/ajax/index.html AJAX с точностью до наоборот, или как научить AJAX сохранять историю в браузереВ давние времена, когда Microsoft только придумала свой Microsoft.XMLHTTP, который является компонентом ActiveX, которые, в свою очередь, в те времена были запрещены по умолчанию, а браузеры других разработчиков еще не догадались ввести XMLHttpRequest, сайты грузились от страницы к странице, являя собой образец статичности и медлительности. Использовать Microsoft.XMLHTTP приходило в голову только самым отчаяным и, на своих сайтах они писали "Извините, в вашем браузере сайт не может нормально отображаться. Используйте, пожалуйста браузер Internet Explorer версии не ниже 5.5" (или что-то вроде того). В эти самые времена я сидел на медленном модеме и успевал сходить на перекур, прежде чем загрузится новая страница (шучу, а может и нет - не помню). В попытке найти способ ускорить загрузку страниц своего сайта образца 2003 года я не пытался использовать объекты ActiveX, так как хотел, чтобы сайт отображался нормально во всех браузерах. Простейшим способом было поместить сайт во фреймы (frame), и я сделал это. Благо, тогда браузеры не понимающие фреймы (нипример, IE 4.0) более ни кем не применялись. Однако меня постоянно ругали за некрасивость сайта (хотя хвалили за скорость). Поэтому я начал искать другие способы ускорения: то использование iframe, то эксперименты с загрузкой статического контента яваскриптом... Да, это был AJAX наоборот. Если современный AJAX подгружает на страницу изменяемые данные, позволяя менять содержимое, не перезагружаясь, то я, в виде эксперимента, предлагал решение наоборот: страницы перезагружаются, но статические данные по максимуму кешируются. В результате, при переходе со страницы на страницу грузится только изменяемая часть. Идея была не сильно популярной, интернет постепенно ускорялся и, в 2005 году я прекратил эксперименты, довольный тем, что на загрузку моих компактных страниц тратиться гораздо меньше времени, чем на загрузку страниц с других, аналогичных сайтов. К тому же, тогда появились первые известия об AJAX... Услышав об AJAX, я подумал что, действительно, негоже посетителю, заполнившему регистрационную форму, выдать сообщение "Извините, такой юзер уже есть, придумайте себе новый логин и введите пароль заново"... или что-то вроде того. Поэтому, я тут же применил свои iframe и javascript в различных формах и с тех пор поуспокоился (эти нововведения практически не коснулись моей домашней страницы, которая мало эволющионировала со времен динозавров). Классический AJAX или AJAX через iframe у меня применяется практически на каждом сайте и дальше экспериментировать в этом направлении, вроде незачем: переходы со страницу на страницу происходят классическим образом, а при необходимости, какая-то часть страницы меняется аяксом. Сделанные мной сайты работают быстрее любых аналогов и все путем. Но... Но некоторых людей тревожит, как жешь все-таки заставить AJAX переходить со страницы на страницу и фиксировать это в истории браузера? Я долго не заморачивался на эту тему, но недавно на просторах интернета столкнулся с людьми, которые заморачиваются, и призадумался. Все их методы кривые и ни на что не годятся. А вот старые, добрые iframe... И так, простейший случай: перемещение от страницы к странице, с сохранением истории в браузере:
Поскольку речь о навигации по сайту, приведу структуру: Предполагаем, что, как и положено, шаблон сайта хранится в одном файле, в другом хранится меню, а сама информация хранится в php-скриптах.
Это пример, и я не буду писать под него специальную CMS, чтобы быстрее его подготовить. Да и вам будет проще понять, если я упрощу его до предела. Поэтому, пусть каждый php-файл отвечает за себя сам. HTML-шаблон представляет собой полностью сверстанную страницу без титула, метатегов, меню и контента. Вместо них вставлены специальные метки, в виде HTML-комментариев: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=windows-1251"> <!--METADATA--> <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> <link rel="stylesheet" href="/include/style.css" type="text/css"> </head> ... сокращено ... <table align="center" width="100%" border="0" cellpadding="5" cellspacing="0"> <tr><td valign="top"> <!--MENU--> </td></tr> ... сокращено ... <TD vAlign=top width=400>'; <!--CONTENT--> </td></tr> </table> </td></tr> ... сокращено ... </html> Скрипт меню представляет собой 2 массива с пунктами меню и скрипт-обработчик: <?php // это ссылки $link=array('index.php','index1.php','index2.php','index3.php','index4.php','index5.php','index6.php','index7.php','index8.php'); // это их названия $name=array('Главная','первая страница','вторая страница','третья страница','четвертая страница','пятая страница','шестая страница','седьмая страница','восьмая страница'); $a=sizeof($link); for($i=0;$i<$a;$i++){ if($_SERVER['SCRIPT_NAME']=='/'.$link[$i]){ @$MENU.='<li class="doc">'.$name[$i]; $HEAD=$name[$i]; }else{ @$MENU.='<li class="doc"><a href="'.$link[$i].'">'.$name[$i].'</a>'; } } ?> Теперь очередь самих документов. Так как для упрощения примера, это скрипты, то пусть их код будет таким: <?php $METADATA='<title>AJAX с точностью до наоборот, или как научить AJAX сохранять историю</title> <meta name="Description" content="AJAX с точностью до наоборот, или как научить AJAX сохранять историю"> <meta name="Keywords" content="AJAX, история">'; $CONTENT='<p>В давние времена, когда Microsoft только придумала свой Microsoft.. <p>Да, это был AJAX наоборот. Если современный AJAX подгружает на страницу изменяемые данные... '; $template=file_get_contents('template.html'); include('menu.php'); $search=array('<!--METADATA-->','<!--HEAD-->','<!--MENU-->','<!--CONTENT-->'); $replace=array(@$METADATA,@$HEAD,@$MENU,@$CONTENT); echo str_replace($search,$replace,$template); ?> Естественно, хранение документов в таком виде не очень удобно, но для примера пойдет. В нормальном варианте мои SMC данные в таком виде не хранят :) Код уже рабочий и его можно загрузить на сервер и получить действующий сайт. Но пока без AJAX. Теперь, когда ясно, как это работает вообще, добавим немного кода, чтобы получить из этого AJAX. Для этого усложним скрипты страниц, добавив в них проверку параметра ajax и, сответствующее поведение, если параметр указан. Код $search=array('<!--METADATA-->','<!--HEAD-->','<!--MENU-->','<!--CONTENT-->'); $replace=array(@$METADATA,@$HEAD,@$MENU,@$CONTENT); echo str_replace($search,$replace,$template); Заменим на if(@$_GET['ajax']){ echo '<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1251"></head><body>'; echo '<div id=menu>'.$MENU.'</div>'; echo '<div id=head>'.$HEAD.'</div>'; echo '<div id=content>'.$CONTENT.'</div>'; echo '</body></html>'; }else{ $search=array('<!--METADATA-->','<!--HEAD-->','<!--MENU-->','<!--CONTENT-->'); $replace=array(@$METADATA,@$HEAD,@$MENU,@$CONTENT); echo str_replace($search,$replace,$template); } То есть, если в запросе страницы есть параметр ajax, вместо страницы, сформированной по шаблону, выдать упрощенный вариант в котором меню, заголовок и основной текст находятся в соответствующих блоках. Конечно, такие вещи принято делать в виде XML, но зачем усложнять код парсером XML? Подошла очередь яваскрипта. В шаблон документа, сразу после </body> встявляем код: <script type="text/javascript" language="JavaScript" src="script.js"></script> Сам javascript при этом имеет такое содержание: // добавим в документ ифрейм document.body.innerHTML=document.body.innerHTML+'<iframe onLoad="init();" frameborder="0" width="0" height="0" name="myframe" id="myframe"></iframe>'; function setsearch(){// поменяем таргет ссылок и добавим к ним параметр ajax a=document.links.length; for(i=0;i<a;i++){ hl=document.links[i].host.split(':'); // уберем номер хоста, если есть hd=document.location.host.split(':'); // номер хоста добавляется IE if(hl[0]==hd[0]){// обработать только ссылки, ссылающиеся на этот же сайт document.links[i].setAttribute('target','myframe'); if(document.links[i].search){// если у ссылки есть параметры, добавим еще document.links[i].search=document.links[i].search+'&ajax=1'; }else{// иначе создадим document.links[i].href=document.links[i].href+'?ajax=1'; } } } } function setvalue(element){// помняем значения элементов document.getElementById(element).innerHTML=myframe.document.getElementById(element).innerHTML;// присвоить значение } function init(){ if(a=myframe.document.body.innerHTML){// если у фрейма есть данные setvalue('menu'); setvalue('head'); setvalue('content'); } setlink(); } Ифрейм добавляется скриптом. Это сделано для того, чтобы сократить вес страницы, поместив в кеш то, что можно поместить. Кроме того, в ифрейме есть невалидный момент: onLoad="init();". Событие onLoad не закреплено за iframe спецификацией. Но работает во всех браузерах и позволяет сократить код (иначе в упрощенном варианте, выдаваемом при запросе с параметром ajax, пришлось бы вставить скрипт, срабатывающий при загрузки ифрейма, а добавление скрипта в код, подгружаемый аяксом явно не ускорит работу сайта). Так что, ну её в баню, эту валидность. Скорость прежде всего! Ведь именно ради скорости мы используем AJAX. Обратите внимание, параметр ajax=1 задается именно яваскриптом, чтобы поисковики даже не знали о нем и не пытались индексировать страницы по такому адресу. При желании можно еще и запретить такие страницы к индексированию, но ради упрощения задачи, не будем этого делать. Теперь можно смотреть, что получилось. В данный момент мы имеем переходы по страницам с сохранением истории в браузере. По большому счету, ничего революционного, ведь то же самое можно было сделать и обычным XMLHttpRequest, хотя и поморочились бы с историей... А теперь произведем настоящую революцию: будем не просто перемещаться от страницы к странице, сохраняя историю перемещений, но и... МЕНЯТЬ АДРЕСНУЮ СТРОКУ! При этом, я не собираюсь подделывать адрес, заменяя его на похожий, НО СЕВЕРШЕННО НЕ ТОТ. Я собираюсь организовать реальные перходы со страницы на страницу, но закешировав все, что только можно закешировать. Это и будет AJAX наоборот. Вместо сохранения страницы и вставки ее некоторых динамических элементов, мы будем переходить от одного динамического элемента к другому... ВСТАВЛЯЯ НЕИЗМЕНЯЕМОЕ СОДЕРЖИМОЕ ИЗ КЕША. То есть, раз уж такая потребность есть, возвращаемся к тому, с чем я экспериментировал в 2003 году. Теперь яваскрипт будет включаться в сами файлы данных. Из шаблона скрипт удаляется и шаблон принимает первоначальный вид, какой имел в классическом виде, без AJAX, а у iframe появится постоянный адрес, указывающий на шаблон. Чтобы не путать пример с предудущим, и поскольку это не тот AJAX, о котором мы привыкли думать, назовем его 3kJAX. И так, что мы имеем?.
Поскольку посетитель видет ссылки с параметром ?ajax=1 и может поместить такую ссылку где-то в интернете, страницы с таким адресом имеет смысл закрыть от индексации. Но если в первом случае, iframe нам требовался для перемещения по истории, то теперь перемещение происходит и без него. А значит, можно использовать не iframe, а XMLHttpRequest (если кому-то так больше нравится). С iframe код получается компактней, но есть люди, которым тяжело работать с окнами или фреймами. По крайней мере, я не раз сталкивался с высказываниями вроде "Мне проще взять готовую библиотеку с костылями для XMLHttpRequest, чем разобраться, что происхрдит в этих iframe". Вобщем, специально для тех, кто в танке, корректируем наш яваскрипт: // iframe не добавляем. Используем XMLHttpRequest function setlink(){// поменяем таргет ссылок и добавим к ним параметр ajax a=document.links.length; for(i=0;i<a;i++){ hl=document.links[i].host.split(':'); // уберем номер хоста, если есть hd=document.location.host.split(':'); if(hl[0]==hd[0]){// обработать только ссылки, ссылающиеся на этот же сайт if(document.links[i].search){// если у ссылки есть параметры, добавим еще document.links[i].search=document.links[i].search+'&ajax=1'; }else{// иначе создадим document.links[i].href=document.links[i].href+'?ajax=1'; } } } } function setvalue(element){ return document.getElementById(element).innerHTML; } try { // определить метод поддержки req=new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { req=new ActiveXObject('Microsoft.XMLHTTP'); // сработает в Internet Explorer } catch (e) { if(window.XMLHttpRequest){ // сработает в Mozilla и Safari req=new XMLHttpRequest(); } } } req.open("GET",'template.html',true); req.onreadystatechange=function(){ if (req.readyState==4){ a=req.responseText; a=a.replace(/<!--MENU-->/,setvalue('menu')); a=a.replace(/<!--HEAD-->/,setvalue('head')); a=a.replace(/<!--CONTENT-->/,setvalue('content')); document.body.innerHTML=a+'<link rel="stylesheet" href="/include/style.css" type="text/css">'; setlink(); } }; req.send(null); Сразу бросается в глаза. что хотя код и придельно компактен, танковая версия скрипта получилась потолще... Ну да бог с ней. Теперь для полноты эффекта скроем текст до полной загрузки, заменив его индикатором. Для этого изменим блок, выдаваемый при наличии ajax, добавив в него табличку с рисунком и поставив на остальное display:none (когда документ загрузится, все содержимое заменится и никакого display:none не останется): echo '<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1251"></head><body>'; echo '<table style="width:100%; height:100%"><tr><td style="text-align:center; vertical-align:middle"><img src="loader.gif" alt="загрузка"></table><div style="display:none">'; echo '<div id=menu>'.@$MENU.'</div>'; echo '<div id=head>'.@$HEAD.'</div>'; echo '<div id=content>'.@$CONTENT.'</div>'; echo '</div></body></html>'; Для полноты ощущений для примера беру 3 самые большие страницы своего сайта (чтоб успевали видеть индикатор даже те, у кого очень большая скорость линии). Смотрим. Но пусть любители XMLHttpRequest не сильно радуются: данные, полученные через запросы XMLHttpRequest могут НЕ кешироваться. И на последок невиданное чудо - 3kJAX без добавления параметра ajax в адреса страницы. То есть, как живой посетитель, так и поисковый робот, ходят по одним и тем же адресам, но видят каждый свое: робот видит полный вариант, а человек видит вариант оптимизированный по скорости. В чем же тут фокус? Фокус в куках, устанавливыемых яваскриптом: поисковики не будут интерпретировать скрипты и куки не подцепят. Смотрите. Вот яваскрипт: // добавим в документ ифрейм document.body.innerHTML=document.body.innerHTML+'<iframe onLoad="init();" src="template.html" frameborder="0" width="0" height="0" name="myframe" id="myframe"></iframe>'; document.preload = new Image();// выполним предварительную загрузку рисунка, document.preload.src = "loader.gif";// чтоб быстрее отобразился при загрузки следующей страницы function setlink(){// поменяем таргет ссылок и добавим к ним параметр ajax if(document.cookie){// если куки разрешены в браузере document.cookie = "ajax=1; path=/";// время не указываем. Кука будет сессионной и удалится при закрытии браузера }else{// иначе работаем, как и раньше добавляя ajax в адресную строку a=document.links.length; for(i=0;i<a;i++){ hl=document.links[i].host.split(':'); // уберем номер хоста, если есть hd=document.location.host.split(':'); if(hl[0]==hd[0]){// обработать только ссылки, ссылающиеся на этот же сайт if(document.links[i].search){// если у ссылки есть параметры, добавим еще document.links[i].search=document.links[i].search+'&ajax=1'; }else{// иначе создадим document.links[i].href=document.links[i].href+'?ajax=1'; } } } } } function setvalue(element){// значения устанавливаются у фрейма myframe.document.getElementById(element).innerHTML=document.getElementById(element).innerHTML; } function init(){ if(a=myframe.document.body.innerHTML){// если у фрейма есть данные setvalue('menu'); setvalue('head'); setvalue('content'); document.body.innerHTML=myframe.document.body.innerHTML+'<link rel="stylesheet" href="/ajax/include/style.css" type="text/css">'; } setlink(); } Ну и, соответственно, там, где в скриптах было
Тут хотелось бы обратить внимание на 1 момент: если в шаблоне содержаться какие-то некешируемые элементы (например, счетчики с рейтингов), то событие load НЕ наступит, пока они не загрузятся. Соответственно, помещение страницы в дизайн произойдет только после загрузки всех элементов. Для ускорения процесса можно сделать несколько вещей:
XMLHttpRequest требует особого внимания. Считается, что данные, полученные через него, не кешируются. На практике я вижу, что это не так и, когда вношу новый сайт в портфолио своей дизайн-студии, сделанное на XMLHttpRequest, мне приходится вручную обновлять кеш браузера, чтобы увидеть новые данные. Но на всякий случай подскажу, как попытаться уговорить браузер кешировать данные, получаемые AJAX через XMLHttpRequest, чтобы старания по кешированию статического содержимого не пошли на смарку из-за капризов браузера. Думаю, отправка таких заголовков php-скриптом: header('Cache-Control:public');// кешировать везде header('Expires:'.gmdate('D, d M Y H:i:s',time()+3600).' GMT');// ранее чем через час не проверять header('last-modified:'.gmdate('D, d M Y H:i:s',filemtime('template.html')).' GMT');// дата изменения файла Убедит браузер сохранять результаты работы AJAX в кеш и, при необходимости, брать их оттуда. Напоследок о классических обманках вида mypage.html#hash. Не хочу расписывать код, просто намекну для тех, кто поймет: При шелчке на ссылку вида mypage.html#hash, браузер делает переход на этот якорь и сам добавляет этот переход в историю. Далее, при загрузке страницы, скрипт анализирует, есть ли якорь в адресе страницы (такие якоря, чтоб не путались с другими, лучше делать с именами вроде ajax_action). Если есть, подгружает нужный фрагмент в нужное место. Таким образом, посетитель прийдя по ссылке с якорем увидит то же, что и человек, давший ему эту ссылку. Единственный минус - роботы не будут обращать внимание на якоря и видеть страницы только как mypage.html. О том, что по адресу mypage.html#hash страница может быть совсем другой, они и знать не будут. Пример. Правда в Internet Explorer не работает возвращение назад по истории. С одной стороны он не добавляет в историю, если нет ссылки-якоря с нужным именем (но это поправимо, его можно создавать динамически, меняя имя у единственного якоря в документе). С другой стороны, при нажатии кнопки "назад" (если якоря поставить), document.location.hash возвращает одно и то же значение, хотя значение в адресной строке меняется (проверено на IE6). Как объяснить ишаку, что срока изменилась, я не знаю. Но гемороится на эту тему не стану, так как мне данный метод не нужен впринципе - я делаю подобные вещи через iframe и не нуждаюсь в костылях. Вобщем-то, проблема решается с помощью маленькой библиотеки unFocus.History Keeper. Вот пример ее применения. Но для своей работы она создает ифрейм, так что, мы возвращаемся к тому, от чего пришли: для сохранения истории AJAX требует iframe. Напоминаю еще раз: я не предлагаю хранить данные в php-скриптах, в виде переменных. Здесь это сделано только для упращения примера, чтобы не описывать движок, который мог бы выполнять обработку данных. Кому интересно, описание движка на текстовых файлах может глянуть на странице способы хранения веб-страниц: документы FrontPage или базы данных.
|