Пример использования History.js
В процессе переписывания движка блога я решил, что переход по ссылкам «Следующая / Предыдущая страница» должен происходить без перезагрузки страницы. Проще говоря, с помощью AJAX.
В принципе, при знании матчасти, сделать это проще простого: ловим onclick на ссылке, отправляем AJAX-запрос, получаем HTML с содержимым следующей страницы и вставляем его в соответствующий контейнер в нашем документе.
Однако, в этом решении есть один существенный недостаток. URL страницы при этом не меняется. Это значит, что не работает кнопка «Назад» в браузере, нельзя обновить страницу и получить тот же результат, нельзя сохранить текущую страницу в закладках или отправить её кому-нибудь по почте.
Некоторые извращенцы (Google) предлагают в качестве решения так называемый HashBang (#!), когда информация о текущем состоянии страницы сохранятеся в идентификаторе фрагмента. По такому принципу работает новый интерфейс Твиттера и некоторые другие популярные сайты.
Этот способ мне кажется ужасным костылём. Во-первых, это некрасиво :-) Во-вторых, wget 'http://twitter.com/#!/a_fedoseev'
скачает главную страницу Твиттера, а не мои твиты. Более развёрнуто HashBang ругают на Хабре, см. Ломаем web c ‘#!’ (hash-bang).
Хорошая новость заключается в том, что можно получить желаемый результат очень элегантным и простым способом — с помощью History.js. Это скрипт, который позволяет управлять состоянием страницы, используя HTML5 History API (в тех браузерах, которые его поддерживают). При этом состояние страницы сохраняетя в виде нормального URL и всё работает так, как будто вы на самом деле переходите с одной страницы на другую.
В старых браузерах и IE оно тоже может работать, но при этом используюся хаки с хешем в URL. Меня старые браузеры не волнуют, поэтому для них я просто отключаю History.js.
Теперь перейдём к делу. Я покажу и расскажу, каким образом реализовано перелистывание страниц у меня в блоге.
Упрощённая структуры страницы блога у меня выглядит так:
<html>
<head>
<title>Страница 2</title>
...
</head>
<body>
...
<div class="page">
<div class="entries">
<article>
<h1>...</h1>
</article>
...
</div>
<div class="page-links">
<a href="/blog/page/1" class="prev">Предыдущая страница</a>
<a href="/blog/page/3" class="next">Следующая страица</a>
</div>
</div>
...
</body>
</html>
Думаю, что тут всё очевидно. При переходе по ссылке «Слудующая страница» попадаем на следующую страницу с URL /blog/page/3
. Ссылка «Предыдущая страница» работает аналогично.
Вот как выглядит упрощённый Javascript код для переворачивания страницы, если использовать jQuery.
$(function () {
/* Проверяем, поддерживается ли History.js браузером.
Если нет, то ничего не делаем, пусть всё работает
по-старинке. */
if (!History.enabled) {
return;
}
/* Инициализируем контейнер для записей */
var $entries = $("div.entries");
/* Вешаем обработчики onlick на ссылки */
var $page_links = $("div.page-links"),
$prev_link = $page_links.find("a.prev"),
$next_link = $page_links.find("a.next");
$page_links.delegate("a", "click", function (e) {
e.preventDefault();
var url = $(this).attr("href");
/* Удаляем текущие записи из контейнера */
$entries.empty();
/* Показываем индикатор загрузки */
$entries.addClass("loading");
/* И сообщаем History.js об изменении состояния страницы
В качестве первого агрумента можно передать произвольный объект
с дополнительными данными, которые можно извлечь в обработчике
изменения состояния, описанном ниже.
В нашем случае это будет пустой объект. */
History.pushState({}, null, url);
});
/* Готовим обработчик изменения состояния страницы */
History.Adapter.bind(window, "statechange", function () {
/* Получаем информацию о состоянии страницы */
var state = History.getState();
/* Получаем URL нового состояния. Это URL, который мы передали
в .pushState() */
var url = state.url;
/* Тут можно извлечь дополнительные данные, о которых шла речь выше.
Например, так: var data = state.data; */
/* Отправляем AJAX-запрос на сервер.
В качестве ответа мы ожидаем JSON-объект следующего формата:
{entries: "<article><h1>...</h1>...</article> <article>...",
title: "Страница 3",
next_url: "/blog/page/4",
prev_url: "/blog/page/2" }
Каким образом будет сформирован этот ответ, зависит только от вас. */
$.getJSON(url, function (response) {
/* Обновляем заголовок страницы */
$("title").text(response.title);
/* Обновляем ссылки на предыдущую и следующую страницы */
$prev_link.attr("href", response.prev_url);
$next_link.attr("href", response.next_url);
/* И, наконец, показываем новый блок записей */
$entries.removeClass("loading").html(response.entries);
});
});
});
Приведённый пример далёк от совершенства. Прежде всего, надо правильно обрабатывать случаи, когда дошли до самой последней или, наоборот, самой первой страницы. В этом случае нужно скрывать ссылки на следующую или предыдующую страницу соответственно. Ещё можно добавить какой-нибудь прикольный эффект при перелистывании, как это сделано у меня.
Самое приятное, что это решение полностью совместимо со старыми браузерами и не содержит никаких костылей. Кроме того, его очень легко прикрутить к уже существующим сайтам. Надо только научить сервер отдавать описанный выше JSON-объект.
В <div class="page-links"></div>
можно добавить ссылки с номерами страниц, для перехода на конкретную страницу. При этом модифицировать Javascript-код вообще не нужно.
Ну разве не красота?