Хороший AJAX

17 июня 2008, 22:06 Павел Марковнин JavaScript рейтинг +47-

Большинство веб-разработчиков ограничивают себя в использовании аякса, потому что он, как известно, обладает некоторыми распространенными недостатками:

1. Неиндексируемость динамически загружаемого содержимого поисковиками;
2. Невозможность работать с URL (к примеру, добавлять в закладки динамически загруженный материал);
3. Динамически создаваемые страницы не регистрируются в истории браузера, поэтому невозможно использовать клавиши «назад» и «вперед»;

Но, на другой стороне весов имеются неопровержимые преимущества AJAX:

1. Экономия трафика;
2. Уменьшение нагрузки на сервер;
3. Ускоренная работа веб-приложения (это результат первых двух пунктов).

В этом уроке мы расскажем, как преодолеть минусы и полноценно пользоваться плюсами AJAX.

Идеи

1. Все страницы сайта получаем через AJAX, т. е. обновляем только заголовок и содержимое страницы. Это увеличивает скорость работы сайта и снижает трафик между сервером и браузером.

2. У каждой «аяксовой» страницы свой адрес. Например, вы кидаете другу ссылку в аське на такую страницу и он видит именно то, что нужно. Или человек приходит по ссылке на какую-то страницу (скажем, из поисковика) и тоже видит нужную страницу.

3. Понятная навигация — страницы сайта не ссылаются сами на себя. В нашем случаем мы будем просто выделять ссылки на текущую страницу.

4. Рабочие кнопки «назад» и «вперед». Чтобы пользователь работал с нашим сайтом в привычной ему форме.

5.Работа сайта без Javascript. Содержимое сайта будет индексироваться поисковиками и приверженцы текстовых браузеров тоже не останутся в обиде.

Серверная часть

Для начала нам нужен обычный сайт. Кроме того, у нас должна быть возможность получать одну и ту же страницу в форматах HTML (полный код страницы) и XML (только заголовок и содержимое страницы). При этом будет удобно, если html и xml версии будут различаться только одним параметром в адресе. Например, так:

Мы сделали это на PHP, вы же можете выбрать любой другой серверный язык программирования.

Клиентская часть

Для простоты работы возьмем библиотеку jQuery (с ее помощью будем реализовывать AJAX). Для работы с историей браузера понадобиться библиотека unFocus History.

Для начала нам нужно переписать адреса у ссылок и на событие click привязать выполнение AJAX-запроса, отметку ссылок с тем же адресом, что и у текущей, и добавление этого клика в историю посещений. Обратите внимание, что мы не будем переписывать следующие типы ссылок:

function setupNewLinks(obj)
{
var links = obj.getElementsByTagName("a"); // все ссылки внутри объекта obj
// перебираем ссылки
for (i = 0; i < links.length; i ++)
{
// проверяем, можно ли переписывать адрес текущей ссылки
//(проверка по имени класса, расширению и "внешности" ссылки :-)
// если можно, то переписываем
tmp = links[i].href;
tmp = tmp.replace("http://"+site, "");
indexOfDot = 0;
if(tmp.lastIndexOf(".") > 0) indexOfDot = tmp.lastIndexOf(".");
if(forbiddenTypes.match(tmp.substring(indexOfDot)) == null && tmp.substring(0, 7) != "http://" && links[i].className != forbiddenClass)
{
// прописываем, что происходит при клике на ссылку
$(links[i]).bind("click", function()
{
markLinksWithHref(this.href);
historyVar.getNewHistoryState(this.href.replace("http://"+site+"/#", ""));
this.className = "curPage";
});
// заменяем адрес
links[i].href = "#" + tmp;
}
}
}

Параметром функции указываем объект страницы, внутри которого надо искать ссылки.Вначале указываем весь документ (setupNewLinks(document)), при каждом обновлении контента — только этот контент (setupNewLinks(document.getElementById(’’content’’))).

Сейчас при клике на ссылку ее класс меняется на curPage. Но нам нужно отметить все такие ссылки внутри страницы. Для этого используем вот такую функцию. Ее аргумент — адрес текущей ссылки.

// с помощью этой функции мы отмечаем все ссылки 
// с одинаковым значением href
function markLinksWithHref(linkHref)
{
var links = document.getElementsByTagName("a");
if(navigator.appName == 'Microsoft Internet Explorer') linkHref = linkHref.replace("http://"+site+"/", "");
for (i = 0; i < links.length; i ++)
{
if(links[i].href == linkHref || links[i].href == linkHref+"/") links[i].className = "curPage";
else links[i].className = "";
}
}

Мы получили данные в формате XML и теперь нам нужно их распарсить и заменить заголовок и содержание текущей страницы. Аргументом функции будет полученный XML.

function parseGetData(data)
{
//получаем заголовок и содержание страницы из XML
title = data.getElementsByTagName('title');
cnt = data.getElementsByTagName('content');

//вставляем это в нашу страницу
$('#content').html(cnt[0].firstChild.nodeValue);
document.title = title[0].firstChild.nodeValue;

// обновляем ссылки внутри страницы
setupNewLinks(document.getElementById('content'));
markLinksWithHref(window.location.href);
}

Итак,мы реализовали пункты 1 и 3. Сделаем пункт 2. При каждой загрузке страницы (или клике на кнопки «назад» или «вперед») мы будем проверять адрес и показывать пользователю нужную ему страницу. Заметим, что проверка при первой загрузке и переходе из истории немного отличаются.

function checkAnchorOnload()
{
// определяем полный путь и якорь страницы
var urlPathName = location.pathname;
var urlAnchor = location.hash;

// проверяем адрес, если пользователь первый раз
//вошел на сайт. Если в адресе есть якорь, то
// подгружаем страницу
if(start == 'yes')
{
if(urlPathName != "/")
{
location.href = "http://"+site+"/#"+urlPathName;
}
else if(urlAnchor != "" && urlAnchor.indexOf("#/") != -1)
{
getDataAjax(urlAnchor.replace("#", ""));
}
}
// примерно такая же проверка, но только при нажатии
// "назад" или "вперед" в браузере
else if(start == '')
{
if(urlPathName == "/")
{
if(urlAnchor == "" || (urlAnchor == "#" && navigator.appName == 'Microsoft Internet Explorer'))
{
getDataAjax("http://"+site+"/");
}
else if(urlAnchor.indexOf("#/") != -1)
{
getDataAjax(urlAnchor.replace("#/", ""));
}
}
}
}

Рассмотрим функцию, которая будет «слушать» изменение истории и реагировать на нажатия кнопок «назад» и «вперед».

function historyHandler() {
var stateVar = "nothin'", displayDiv = document.getElementById("content");

this.getNewHistoryState = function(currentState) {
var newVal = currentState;
unFocus.History.addHistory(newVal);
};

this.historyListener = function(historyHash) {
stateVar = historyHash;
// при каждом изменении истории мы будем
// выдавать соответствующий контент
checkAnchorOnload();
};
unFocus.History.addEventListener('historyChange', this.historyListener);

this.historyListener(unFocus.History.getCurrent());
};
var historyVar;

Мы написали все необходимые функции, осталось только «привязать» их к странице.

function init()
{
setupNewLinks(document); // переписываем адреса ссылок
historyVar = new historyHandler(); // запоминаем историю
start = '';
}

Лучше всего вызывать эту функцию либо при загрузке DOM, либо в конце самого документа.

В итоге у нас получился вот такой сайт.

Рекламное место, которое может стать вашим

Понравилась статья?

Тогда подпишись на обновления через RSS или воспользуйся
другими способами подписки.

Читать в Яндекс.Ленте Добавить в Google Добавить в Netvibes
  •  

Комментарии — 45

RSS
  • Аватарка
    18 июня 2008 в 9:07 SerjS
    Приятная статья, вполне полезная.
  • Аватарка
    22 июня 2008 в 15:59 AztEK
    Все-таки, в 95% случаях XML в AJAX ничем неоправдан. Т.ч. самая первая оптимизация, которую стоить проводить нереходить на JSON.
  • Аватарка
    3 августа 2008 в 16:39 ]]>Жека]]>
    Ура. Теперь гмэйл стал отображать "хорошие" адреса и в опере.
  • Аватарка
    11 сентября 2008 в 21:46 Хер
    Странно как то. С одной стороны подключение Jquery, с другой - использование классического джаваскрипта.Хз чо и думать)
  • Аватарка
    21 сентября 2008 в 20:47 sheff88
    jQuery - отличная библиотек. Соглашусь с предыдущим комментом - используете jQuery - пользуйтесь им на протяжении всего кода - это правила хорошего тона коддинга.
  • Аватарка
    1 октября 2008 в 8:58 аноним
    в Опере не работает такой способ
  • Аватарка
    8 октября 2008 в 15:25 taran2L
    Здравствуйте автор, очень полезная статья, пытаюсь воспользоваться Вашим примером для написания своего сайта, но есть одна проблема. В примере Вы используете метод мод.реврайт для каждой отдельной станици, у меня страниц очень много и это будет не столько объективно приписывать в .htaccess код для каждой страници. Пэтому я пользуюсь одной записью RewriteRule ^(.*)$ index.php [L] и внутри index.php определяю URL браузера и соответвенно реагирую на разные записи, но вот проблема в том, что такой url /#/page1... я не могу определить, перменная $_SERVER[REQUEST_URI] не хочет работать, может подскажите, как решить эту проблему? моя ящик pavel.taran/gmail.com . Если вы уже сталкивались с этим и знаете ответ, помогите пожалуйста.
  • Аватарка
    18 октября 2008 в 23:54 Grey
    Пример почему-то отказался работать.
  • Аватарка
    3 ноября 2008 в 18:31 ]]>diktator]]>
    Интересная идея, но, думаю, что вряд ли ajax приживётся на сайтах в таком качестве.
  • Аватарка
    12 ноября 2008 в 11:38 Кос
    А можно ли эту вещь реализовать динамически? то есть, построить cms на основе этого материала? или все равно нужно прописывать все вручную?
  • Аватарка
    27 ноября 2008 в 18:33 App
    Видел сайт сделанный по подобной (а может и именно по этой) схеме. Работает нормально, но не индексируется поисковиками... Только главная страница. Актуальна ли эта проблема или просто у автора того сайта кривые руки ?
  • Аватарка
    27 ноября 2008 в 18:41 ]]>Grin]]>
    App, по нашей схеме все будет индексироваться поисковиками
  • Аватарка
    27 ноября 2008 в 19:43 zzz
    Пример на локальном сервере пробовал, но не запускается. Запустил кто нибудь?
  • Аватарка
    27 ноября 2008 в 20:24 ]]>Grin]]>
    zzz, вы в корень файлы положили?
  • Аватарка
    28 ноября 2008 в 14:30 App
    Grin, спасибо
  • Аватарка
    28 ноября 2008 в 20:31 zzz
    На локальном сервере я положил в папку www. Вот путь localhost/www/site. В корневой вы имеете в виду непосредственно в localhost/ ?
  • Аватарка
    28 ноября 2008 в 20:44 zzz
    Все, заработало! Теперь понял, спасибо
  • Аватарка
    10 декабря 2008 в 10:53 Zoltan
    В Opera нужно два раза нажимать на Back чтоб вернутся. Кто сталкивался? Как исправили?
    Спасибо.
  • Аватарка
    15 декабря 2008 в 14:27 astons
    в опере 9.62 не работает почему то
  • Аватарка
    12 февраля 2009 в 12:12 ]]>Виктор]]>
    Отлично.
    Но недоработок пока еще много: Конпка бэк в опере та же. Поисковикам придется обьяснять почему ваш javascript (а они его уже парсят, хоть и не выполняют) так смахивает на "серый" :)
  • Аватарка
    12 февраля 2009 в 14:16 ]]>Grin]]>
    Виктор, а чем он смахивает на серый?
  • Аватарка
    10 апреля 2009 в 17:14 Андрей
    Зачем изобретать велосипед если уже есть готовые решения
    например тут http://fullajax.ru/
  • Аватарка
    10 апреля 2009 в 17:18 ]]>Grin]]>
    Андрей, мы же написали, что хотели просто показать принципы хорошего аякса. Естественно, наш пример не является полноценным решением, а всего лишь показывает, как правильно реализовывать технологию
  • Аватарка
    13 апреля 2009 в 19:26 wavebvg
    На счёт истории, очень понравилось, только есть одно но... В опере заработало только после некоторых волшебных махинаций, если кому будет интересно - отметьте письмом на почту - напишу, что да как тама...
  • Аватарка
    13 апреля 2009 в 19:30 wavebvg
    Оо, это у меня надо было изобретать велосипед, тут попроще - надо запретить "переход" на страницу через ссылку и позволить это делать только через js, т.е. посылать return false
    Для оперы необходимо прописать страницу, сразу после первого посещения, если этого не происходит по первому срабатыванию события (у меня не совсем так, как в примере, тут должно работать без этого)...
  • Аватарка
    13 апреля 2009 в 19:32 ]]>Grin]]>
    wavebvg, напиши нам на pisem@vremenno.net
  • Аватарка
    23 апреля 2009 в 20:37 Vladimir
    Здравствуйте!
    А Как правильно Нужно Обращаться из flash!

    У Меня Два Меню Flash, которое находится вверху страницы и html которое находится в низу страницы! Все Вроде Работает Только в Explorer почему то нет!

    Обращаюсь я так:
    Flash: getURL("javascript:GetFlashAjax('overview/story.html')");
    HTML:
    var SetUrl = 'http://'+site+'/' + '#/'+item;
    markLinksWithHref(SetUrl);
    historyVar.getNewHistoryState(SetUrl.replace("http://"+site+"/#", ""));

  • Аватарка
    24 апреля 2009 в 15:07 Vladimir
    Можете ответить на мой вопрос Очень срочно нужно!
  • Аватарка
    24 апреля 2009 в 15:11 ]]>Grin]]>
    Vladimir, я ни разу не делал подобного на флеше, поэтому не могу ничего посоветовать
  • Аватарка
    24 августа 2009 в 22:45 ]]>ionian.wind]]>
    И все-таки в Опере 2 раза "назад" надо нажимать чтобы вернуться на предыдущую страницу.
    Не могли бы вы написать решение?
  • Аватарка
    8 октября 2009 в 11:49 Артур
    Полностью согласен со вторым постом. Сериализация форматов XML и JSON почти одинаковая, а десериализация у формата JSON в несколько раз больше десериализация.
  • Аватарка
    19 ноября 2009 в 0:41 Константин
    А вам не кажется, что вы бредите? Историкипер в архиве занимает больше метра, жкуери занимает 100 килобайт...
    Одних только скиптов вы предлагаете загрузить посетителю столько, сколько весь сайт не весит. Не слишком ли вы обленились?
  • Аватарка
    19 ноября 2009 в 1:00 Константин
    Тут кто-то (не буду искать) рекламировал http://fullajax.ru/
    я пошел глянуть... Пока не отключил жабаскрипт, ссылки внитри их сайта не работали. Наверное, это что-то значит. Наверное, это значит то, что их код глючит, по крайней мере, в некоторых ситуациях).
    А не проще ли вместо 70-ти килобайтных страниц (весс чистого ШТМЛ с главной страницы данного сайта), делать страницы в 7 килобайт и перестать беспокоиться о том, что посетитель будет ее долго грузить? К примеру, зачем главная страница со всеми прибамбасами весит 270 килобайт? В 270 килобайт можно было вместить ВЕСЬ этот сайт.
    Объясните мне, непонятливому, почему людям нужно делать сверхтяжелые страницы, а потом думать, как бы к ним аякс прилепить? Если графики не много, я сайт-визитку могу вместить в размер одного только скрипта jquery. Сайтостроение - ювелирное занятие. Что тут делать с бульдозером?
  • Аватарка
    19 ноября 2009 в 11:12 ]]>Grin]]>
    Константин, мы не бредим. Это не готовая реализация, это демонстрация принципа, который мы описываем в статье.
  • Аватарка
    19 ноября 2009 в 18:03 Константин
    Я не о реализации. Я о самом принципе. То, что теоретически это штука полезная, я не спорю. И то, что ваш вариант - это один из путей его реализации - это тоже понятно. Но не ужели создание громоздких приложений целесообразней создания компактных сайтов? А если страницы компактны, чё там аяксить? Вот в чем вопрос. Я не понимаю, почему все, что делаю не я, сделано в 10-20 раз тяжелее и медленнее. Я уже 10 лет задаю людям один и тот же вопрос: откуда к вам приросли лишние мегабайты кода? Ладно бы надежность и функциональность увеличилась. А так - в лучшем случае, на том же уровне...
  • Аватарка
    19 ноября 2009 в 20:56 ]]>Grin]]>
    Константин, что я вас не пойму.
    Вы начали с указаний на то, что наш пример слишком «тяжелый» в плане кода и потом перешли на приложения в общем смысле. Я надеюсь, вы не считаете, что аяксу как таковому вообще не место в вебе?
    Не всегда же есть возможность делать компактные, как вы их назвали, сайты. Бывают случаи (и много), когда 700КБ кода позволяют существенно ускорить работу приложения и облегчить жизнь пользователю. И нормальным разработчикам ясно, когда можно и нужно использовать аякс.
    Да, кстати, приведите, пожалуйста, примеры таких сайтов, о которых вы говорите в своих комментариях, чтобы все было наглядно.
  • Аватарка
    26 ноября 2009 в 15:24 Константин
    Давайте на конкретном примере:
    Вот решение на jquery. Для него используется 130 килобайт скриптов и сама страница (без рисунков и стилей) весит 28 килобайт. То есть очень много кода. Аналогичное решение без jquery: страница весит 4 килобайта, скрипты 12. То есть, разница между двумя вариантами десятикратная.
    Это пример компактного решения и скрипты, которые меняли бы содержимое страницы аяксом весили бы больше, чем сама страница (хотя рисунки в галерее подгружаются именно аяксом, так как их может быть целая тысяча и грузить их все сразу не имеет смысла).
  • Аватарка
    26 ноября 2009 в 17:55 Димастый
    Сильно смеялся, поэтому хочется написать )))

    Константин, по показаниям моей Оперы №10.01.1844:
    galery.html
    Размер страницы: 4 165 байт
    Количество встроенных элементов: 28 (171 950 байт)
    Итого сами посчитайте. И, между прочим, тыкнул в картинку - так и не дождался самой картинки - все окно позиционировалось со странными подергиваниями...
    "Я не понимаю, почему все, что делаю не я, сделано в 10-20 раз тяжелее и медленнее." Браво. )))
    --
    За статью - большое спасибо, бум копать )
  • Аватарка
    26 ноября 2009 в 19:33 Константин
    Ну зачем же считать количество и объем встроеных элементов? Они зависит от дизайна. Какое отношение рисунки имеют к данной статье? Может нужно было еще объем флешки с vip-tv.ru посчитать? А если у вас в Опере 10.01.1844 не открылся рисунок - стоило бы сказать, о каком рисунке идет речь. У меня все рисунки открываются в 3-х браузерах. Но если вы считаете. что количество встроеных элементов имеет какое-то отношение к тексту данной статьи, то не удивительно, что у вас что-то не открывается
  • Аватарка
    28 ноября 2009 в 19:10 Константин
    Если еще не ясно, уточню: изложенная задача интересна сама по себе. Но применение многометровых скриптов для ее решения - идея бредовая. Вот если это вместить в пару килобайт... Что же касается практической целесообразности аяксить содержимое сайта целиком, имитируя переходы от страницы к странице, вплоть до адресной строки - то я сомневаюсь в целесообразности этого и говорю, что лучше делать компактные страницы.
  • Аватарка
    2 декабря 2009 в 9:29 AlexAnder Frozen
    Константин, полностью поддерживаю.
    Не общайся с ними, оне лузеры. Ленивые лузеры.
    Разве ты не понял это?
    Им сложно написать пять строк вместо одной, которая подгрузит полинтернета. Кали Юга, век невежества. И они ещё нас учить пытаются. Просто не общайся с ними да и всё, время теряешь.

    Лично я вот пожалел время пока читал весь этот бред. Настроение попортилось даже. Хорошо что хоть твои коменты тут, родственная душа. :)
  • Аватарка
    2 декабря 2009 в 9:30 AlexAnder Frozen
    JQuery - одна сплошная ошибка. Даже если внутри нету ошибок.
  • Аватарка
    2 декабря 2009 в 10:29 AlexAnder Frozen
    А вообще AJAX не для этих целей изобрели. Для игрушек в стиле гуглдокс.
  • Аватарка
    23 декабря 2009 в 8:29 Арестант
    AlexAnder Frozen... Что-то до боли знакомое... Не ты ли мне вчера на допросе руки выкручивал? ;)
    П. С. У меня периодически возникает впечатление, что программистами становятся выпусники интерната для умственно-отсталых: такое понапридумывают, такое понаворачивают... Мозги закипают! А между тем существуют способы сделать все то же просто и эффективно.
  • Аватарка
    5 февраля в 9:38 GooD
    Константин, тут был указан только метод. Реализацию его ты можешь писать сам. Вмести всё в пару килобайт. Можешь использовать сторонние расширения/плагины. Эту статью вообще уже можно назвать устаревшей в техническом плане
  • Эл. почта (используется для Граватарки)
  • Домашняя страница
  • Имя в Твиттере
  • Разрешенные теги Текст сообщения (надо бы заполнить это поле)
  • как выглядит какой тег
    жирный текст <b>жирный текст</b>
    курсивный тект <i>курсивный тект</i>
    зачеркнутый текст <s>зачеркнутый текст</s>
    подчеркнутый текст <u>подчеркнутый текст</u>
    ссылка <a href="адрес">ссылка</a>
    function foo() { ... }
    <pre><code>function foo() { ... } </code></pre>