Хороший AJAX

Хороший AJAX

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

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

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

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

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

Идеи

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

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

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

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

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

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

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

  • HTML версия страницы — /page1/page 1-1.html;
  • XML версия — /page1/page 1-1.html?ajax=yes.

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

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

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

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

  • внешние ссылки, т. е. ссылки на страницы других сайтов;
  • ссылки на картинки, архивы и прочие файлы (расширения таких файлов указываем в переменной forbiddenTypes);
  • ссылки с заранее указанным классом (forbiddenClass) — если внутри сайта есть страницы, которые необходимо грузить целиком.
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, либо в конце самого документа.

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

 

 

Расскажите друзьям

Оцените статью:
  • 1
  • 2
  • 3
  • 4
  • 5

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

SerjS
Приятная статья, вполне полезная.
#
SerjS
AztEK
Все-таки, в 95% случаях XML в AJAX ничем неоправдан. Т.ч. самая первая оптимизация, которую стоить проводить нереходить на JSON.
#
AztEK
Жека
Ура. Теперь гмэйл стал отображать "хорошие" адреса и в опере.
Хер
Странно как то. С одной стороны подключение Jquery, с другой - использование классического джаваскрипта.Хз чо и думать)
#
Хер
sheff88
jQuery - отличная библиотек. Соглашусь с предыдущим комментом - используете jQuery - пользуйтесь им на протяжении всего кода - это правила хорошего тона коддинга.
#
sheff88
аноним
в Опере не работает такой способ
#
аноним
taran2L
Здравствуйте автор, очень полезная статья, пытаюсь воспользоваться Вашим примером для написания своего сайта, но есть одна проблема. В примере Вы используете метод мод.реврайт для каждой отдельной станици, у меня страниц очень много и это будет не столько объективно приписывать в .htaccess код для каждой страници. Пэтому я пользуюсь одной записью RewriteRule ^(.*)$ index.php [L] и внутри index.php определяю URL браузера и соответвенно реагирую на разные записи, но вот проблема в том, что такой url /#/page1... я не могу определить, перменная $_SERVER[REQUEST_URI] не хочет работать, может подскажите, как решить эту проблему? моя ящик pavel.taran/gmail.com . Если вы уже сталкивались с этим и знаете ответ, помогите пожалуйста.
#
taran2L
Grey
Пример почему-то отказался работать.
#
Grey
diktator
Интересная идея, но, думаю, что вряд ли ajax приживётся на сайтах в таком качестве.
Кос
А можно ли эту вещь реализовать динамически? то есть, построить cms на основе этого материала? или все равно нужно прописывать все вручную?
#
Кос
App
Видел сайт сделанный по подобной (а может и именно по этой) схеме. Работает нормально, но не индексируется поисковиками... Только главная страница. Актуальна ли эта проблема или просто у автора того сайта кривые руки ?
#
App
Grin
App, по нашей схеме все будет индексироваться поисковиками
#
Grin
zzz
Пример на локальном сервере пробовал, но не запускается. Запустил кто нибудь?
#
zzz
Grin
zzz, вы в корень файлы положили?
#
Grin
App
Grin, спасибо
#
App
zzz
На локальном сервере я положил в папку www. Вот путь localhost/www/site. В корневой вы имеете в виду непосредственно в localhost/ ?
#
zzz
zzz
Все, заработало! Теперь понял, спасибо
#
zzz
Zoltan
В Opera нужно два раза нажимать на Back чтоб вернутся. Кто сталкивался? Как исправили?
Спасибо.
#
Zoltan
astons
в опере 9.62 не работает почему то
#
astons
Виктор
Отлично.
Но недоработок пока еще много: Конпка бэк в опере та же. Поисковикам придется обьяснять почему ваш javascript (а они его уже парсят, хоть и не выполняют) так смахивает на "серый" :)
Grin
Виктор, а чем он смахивает на серый?
#
Grin
Андрей
Зачем изобретать велосипед если уже есть готовые решения
например тут http://fullajax.ru/
#
Андрей
Grin
Андрей, мы же написали, что хотели просто показать принципы хорошего аякса. Естественно, наш пример не является полноценным решением, а всего лишь показывает, как правильно реализовывать технологию
#
Grin
wavebvg
На счёт истории, очень понравилось, только есть одно но... В опере заработало только после некоторых волшебных махинаций, если кому будет интересно - отметьте письмом на почту - напишу, что да как тама...
#
wavebvg
wavebvg
Оо, это у меня надо было изобретать велосипед, тут попроще - надо запретить "переход" на страницу через ссылку и позволить это делать только через js, т.е. посылать return false
Для оперы необходимо прописать страницу, сразу после первого посещения, если этого не происходит по первому срабатыванию события (у меня не совсем так, как в примере, тут должно работать без этого)...
#
wavebvg
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+"/#", ""));

#
Vladimir
Vladimir
Можете ответить на мой вопрос Очень срочно нужно!
#
Vladimir
Grin
Vladimir, я ни разу не делал подобного на флеше, поэтому не могу ничего посоветовать
#
Grin
ionian.wind
И все-таки в Опере 2 раза "назад" надо нажимать чтобы вернуться на предыдущую страницу.
Не могли бы вы написать решение?
Артур
Полностью согласен со вторым постом. Сериализация форматов XML и JSON почти одинаковая, а десериализация у формата JSON в несколько раз больше десериализация.
#
Артур
Константин
А вам не кажется, что вы бредите? Историкипер в архиве занимает больше метра, жкуери занимает 100 килобайт...
Одних только скиптов вы предлагаете загрузить посетителю столько, сколько весь сайт не весит. Не слишком ли вы обленились?
#
Константин
Константин
Тут кто-то (не буду искать) рекламировал http://fullajax.ru/
я пошел глянуть... Пока не отключил жабаскрипт, ссылки внитри их сайта не работали. Наверное, это что-то значит. Наверное, это значит то, что их код глючит, по крайней мере, в некоторых ситуациях).
А не проще ли вместо 70-ти килобайтных страниц (весс чистого ШТМЛ с главной страницы данного сайта), делать страницы в 7 килобайт и перестать беспокоиться о том, что посетитель будет ее долго грузить? К примеру, зачем главная страница со всеми прибамбасами весит 270 килобайт? В 270 килобайт можно было вместить ВЕСЬ этот сайт.
Объясните мне, непонятливому, почему людям нужно делать сверхтяжелые страницы, а потом думать, как бы к ним аякс прилепить? Если графики не много, я сайт-визитку могу вместить в размер одного только скрипта jquery. Сайтостроение - ювелирное занятие. Что тут делать с бульдозером?
#
Константин
Grin
Константин, мы не бредим. Это не готовая реализация, это демонстрация принципа, который мы описываем в статье.
Константин
Я не о реализации. Я о самом принципе. То, что теоретически это штука полезная, я не спорю. И то, что ваш вариант - это один из путей его реализации - это тоже понятно. Но не ужели создание громоздких приложений целесообразней создания компактных сайтов? А если страницы компактны, чё там аяксить? Вот в чем вопрос. Я не понимаю, почему все, что делаю не я, сделано в 10-20 раз тяжелее и медленнее. Я уже 10 лет задаю людям один и тот же вопрос: откуда к вам приросли лишние мегабайты кода? Ладно бы надежность и функциональность увеличилась. А так - в лучшем случае, на том же уровне...
#
Константин
Grin
Константин, что я вас не пойму.
Вы начали с указаний на то, что наш пример слишком «тяжелый» в плане кода и потом перешли на приложения в общем смысле. Я надеюсь, вы не считаете, что аяксу как таковому вообще не место в вебе?
Не всегда же есть возможность делать компактные, как вы их назвали, сайты. Бывают случаи (и много), когда 700КБ кода позволяют существенно ускорить работу приложения и облегчить жизнь пользователю. И нормальным разработчикам ясно, когда можно и нужно использовать аякс.
Да, кстати, приведите, пожалуйста, примеры таких сайтов, о которых вы говорите в своих комментариях, чтобы все было наглядно.
Константин
Давайте на конкретном примере:
Вот решение на jquery. Для него используется 130 килобайт скриптов и сама страница (без рисунков и стилей) весит 28 килобайт. То есть очень много кода. Аналогичное решение без jquery: страница весит 4 килобайта, скрипты 12. То есть, разница между двумя вариантами десятикратная.
Это пример компактного решения и скрипты, которые меняли бы содержимое страницы аяксом весили бы больше, чем сама страница (хотя рисунки в галерее подгружаются именно аяксом, так как их может быть целая тысяча и грузить их все сразу не имеет смысла).
#
Константин
Димастый
Сильно смеялся, поэтому хочется написать )))

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

Лично я вот пожалел время пока читал весь этот бред. Настроение попортилось даже. Хорошо что хоть твои коменты тут, родственная душа. :)
#
AlexAnder Frozen
AlexAnder Frozen
JQuery - одна сплошная ошибка. Даже если внутри нету ошибок.
#
AlexAnder Frozen
AlexAnder Frozen
А вообще AJAX не для этих целей изобрели. Для игрушек в стиле гуглдокс.
#
AlexAnder Frozen
Арестант
AlexAnder Frozen... Что-то до боли знакомое... Не ты ли мне вчера на допросе руки выкручивал? ;)
П. С. У меня периодически возникает впечатление, что программистами становятся выпусники интерната для умственно-отсталых: такое понапридумывают, такое понаворачивают... Мозги закипают! А между тем существуют способы сделать все то же просто и эффективно.
#
Арестант
GooD
Константин, тут был указан только метод. Реализацию его ты можешь писать сам. Вмести всё в пару килобайт. Можешь использовать сторонние расширения/плагины. Эту статью вообще уже можно назвать устаревшей в техническом плане
#
GooD
Boldis Media
За метод спасибо, но имхо не практично, как-то.
MVH
ИМХО, вся эта идея, в том свете, в котором Вы её представили (как надстройка на сайт), - ересь, которую будут слепо использовать начинающие вебмастера, что приведёт с появлению сайтов, которыми будет неудобно пользоваться.

Выгода от этой идеи не много, а если учесть, что всю выгоду надо кодировать в дополнение, то это всё оборачивается большим минусом, т.к., уверен, что тот незначительный прирост производительности выйдет дороже (дешевле железо докупить, чем тратить время программистов на создание и поддержку всего этого). Ну что за выгода может быть? Снижение объёма трафика? Да бросьте. Сколько Вы выиграете, если не будете боковые колонки, шапку и подвал грузить? Несколько килобайт и немного ресурсов сервера, затраченных на gzip и генерацию этих колонок боковых? К тому же все эти боковые колонки, шапки, подвал и прочее закешировать на сервере можно.

А вот, что будет если на какой-то странице колонки боковые должны быть разные (что очень часто бывает)? Или какая-то рандомно генерироваться каждый раз?

А что будет, если страница не загрузится или сервер ошибку вернёт? Надо опять же делать проверку кода ответа сервера.

А как это будет работать на мобильных устройствах?

Да и вообще зачем вся это байда с созданием, по сути, своего недо-браузера?

AJAX не для этого предназначен. Для этого уже есть браузер.
#
MVH
Сергей
если уж и jQuery подключили, то всё реализуется в 10-20 строчек скрипта.
Зачем столько лишнего придумывать?
if ($.browser.msie) if (window.location.hash == '') window.location.href = '!#' + window.location.hash.slice(1);
$("a:not(a[target=_blank],a[rel=no-ajax],a[rel=img],a[href*=mailto:])").live("click", function (e) {
url = $(this).attr("href");
e.preventDefault();
$.ajax({
url: url,
success: function (data) {.....}
typeof (window.history.pushState) == 'function' ? window.history.pushState('', url, url) : window.location.href = '!#' + url.replace('http://' + window.location.host, '');
и понеслась душа в рай// только для ишака лишних пара строчек придётся добавить.
несколько усложнённая реализация тут http://reklamagt.ru/ , для своих проектов упростил код.
#
Сергей
Евгений
Дааааа, пример конечно крутой, крутейшая вёрстка, табличная это верх профессионализма, крутейший php код, где в переменной $int находится текст!, где чуть ли не каждая строчка начинается с оператора управления ошибками (@), даааа и это я ещё не дошёл JavaScript.
Действительно есть чему людей учить, так держать!
#
Евгений
impulse
Извините за глупый вопрос, но я не понял что и куда надо прописывать и какие файлы создавать((((подскажите пожалуйста
#
impulse

Новый комментарий

как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть
как выглядит какой тег
жирный текст <b>жирный текст</b>
курсивный тект <i>курсивный тект</i>
зачеркнутый текст <s>зачеркнутый текст</s>
подчеркнутый текст <u>подчеркнутый текст</u>
ссылка <a href="адрес">ссылка</a>
function foo() { ... }
<pre><code>function foo() { ... } </code></pre>
разрешенные теги или посмотреть как будет выглядеть

metin2 pvp metin2 pvp serverler pvp serverler