Книга: JavaScript. Подробное руководство, 6-е издание
22.2. Управление историей посещений
Разделы на этой странице:
22.2. Управление историей посещений
Веб-броузеры запоминают, какие документы загружались в окно, и предоставляют кнопки Back и Forward, позволяющие перемещаться между этими документами. Эта модель хранения истории посещений в броузерах появилась еще в те дни, когда документы были статическими и все вычисления выполнялись на стороне сервера. В настоящее время веб-приложения часто загружают содержимое динамически и отображают новые состояния приложения без полной перезагрузки документа. Такие приложения должны предусматривать собственные механизмы управления историей посещений, если необходимо дать пользователю возможность использовать кнопки Back и Forward для перехода из одного состояния приложения в другое интуитивно понятным способом. Спецификация HTML5 определяет два механизма управления историей посещений.
Простейший способ работы с историей посещений связан с использованием свойства location.hash
и события «hashchange». На момент написания этих строк данный способ был также наиболее широко реализованным: его поддержка в броузерах появилась еще до того, как он был стандартизован спецификацией HTML5. В большинстве броузеров (кроме старых версий IE) изменение свойства location.hash
приводит к изменению URL, отображаемого в строке ввода адреса, и добавлению записи в историю посещений. Свойство hash
определяет идентификатор фрагмента в URL и традиционно использовалось для перемещения к разделу документа с указанным идентификатором. Но свойство location.hash
не обязательно должно определять идентификатор элемента: в него можно записать произвольную строку. Если состояние приложения можно представить в виде строки, эту строку можно будет использовать как идентификатор фрагмента.
Предусмотрев изменение значения свойства location.hash
, вы даете пользователю возможность использовать кнопки Back и Forward для перемещения между состояниями приложения. Чтобы такие перемещения были возможны, приложение должно иметь способ определять момент изменения состояния, прочитать строку, хранящуюся в виде идентификатора фрагмента, и обновить себя в соответствии с требуемым состоянием. Согласно спецификации HTML5, при изменении идентификатора фрагмента броузер должен возбуждать событие «hashchange» в объекте Window
. В броузерах, поддерживающих событие «hashchange», можно присвоить свойству window.onhashchange
функцию обработчика, которая будет вызываться при каждом изменении идентификатора фрагмента документа, вызванном перемещением по истории посещений. При вызове эта функция-обработчик должна проанализировать значение location.hash
и отобразить содержимое страницы, соответствующее выбранному состоянию.
Спецификация HTML5 также определяет другой, более сложный и более надежный способ управления историей посещений, основанный на использовании метода history.pushState()
и события «popstate». При переходе в новое состояние вебприложение может вызвать метод history.pushState()
, чтобы добавить это состояние в историю посещений. В первом аргументе методу передается объект, содержащий всю информацию, необходимую для восстановления текущего состояния приложения. Для этой цели подойдет любой объект, который можно преобразовать в строку вызовом метода JSON.stringify(),
а также некоторые встроенные типы, такие как Date
и RegExp
(смотрите врезку ниже). Во втором аргументе передается необязательное заглавие (простая текстовая строка), которую броузер сможет использовать для идентификации сохраненного состояния в истории посещений (например, в меню кнопки Back). В третьем необязательном аргументе передается строка URL, которая будет отображаться как адрес текущего состояния. Относительные URL-адреса интерпретируются относительно текущего адреса документа и нередко определяют лишь часть URL, соответствующую идентификатору фрагмента, такую как #state. Связывание различных состояний приложения с собственными URL-адресами дает пользователю возможность делать закладки на внутренние состояния приложения, и если в строке URL будет указан достаточное количество информации, приложение сможет восстановить это состояние при запуске с помощью закладки.
Структурированные копии
Как отмечалось выше, метод pushState() принимает объект с информацией о состоянии и создает его частную копию. Это полная, глубокая копия объекта: при ее создании рекурсивно копируется содержимое всех вложенных объектов и массивов. В стандарте HTML5 такие копии называются структурированными копиями. Процедура создания структурированной копии напоминает передачу объекта функции JSON. stringif у() и обработку результата функцией JSON.parse() (раздел 6.9). Но в формат JSON можно преобразовать только простые значения JavaScript, а также объекты и массивы. Стандарт HTML5 требует, чтобы алгоритм создания структурированных копий поддерживал возможность создания копий объектов Date и RegExp, ImageData (полученных из элементов <canvas> - раздел 21.4.14) и FileList, File и Blob (описывается в разделе 22.6). Функции JavaScript и объекты ошибок явно исключены из списка объектов, поддерживаемых алгоритмом создания структурированных копий, также как и большинство объектов среды выполнения, таких как окна, документы, элемент и т. д.
Вряд ли вам понадобится сохранять файлы или изображения как часть состояния приложения, однако структурированные копии также используются некоторыми другими стандартами, связанными со стандартом HTML5, и мы будем встречать их на протяжении всей этой главы.
***********************************************
В дополнение к методу pushState()
объект History
определяет метод replaceState(),
который принимает те же аргументы, но не просто добавляет новую запись в историю посещений, а замещает текущую запись.
Когда пользователь перемещается по истории посещений с помощью кнопок Back и Forward, броузер возбуждает событие «popstate» в объекте Window
. Объект, связанный с этим событием, имеет свойство с именем state
, содержащее копию (еще одну структурированную копию) объекта с информацией о состоянии, переданного методу pushState().
В примере 22.3 демонстрируется простое веб-приложение - игра «Угадай число», изображенная на рис. 22.1, - в которой используются описанные приемы сохранения истории посещений, определяемые стандартом HTML5, с целью дать пользователю возможность «вернуться назад», чтобы пересмотреть или повторить попытку.
Когда эта книга готовилась к печати, в броузере Firefox 4 было внесено два изменения в прикладной интерфейс объекта History
, которые могут быть заимствованы и другими броузерами. Во-первых, в Firefox 4 информация о текущем состоянии теперь доступна через свойство state
самого объекта History
, а это означает, что вновь загружаемые страницы не должны ждать события «popstate». Во-вторых, Firefox 4 более не возбуждает событие «popstate» для вновь загруженных страниц, для которых отсутствует сохраненное состояние. Это второе изменение означает, например, что пример, приведенный ниже, будет работать неправильно в Firefox 4.
Примерх 22.3. Управление историей посещений с помощью pushStatef()
<!DOCTYPE html>
<html><head><title>I'm thinking of a number...</title>
<script>
window.onload = newgame; // Начать новую игру при загрузке
window.onpopstate = popstate; // Обработчик событий истории посещений
var state, ui; // Глобальные переменные, инициализируемые в функции newgame()
function newgame(playagain) { // Начинает новую игру "Угадай число"
// Настроить объект для хранения необходимых элементов документа
ui = {
heading: null, // Заголовок <h1> в начале документа.
prompt: null, // Текст предложения ввести число.
input: null, // Поле, куда пользователь вводит-числа.
low: null, // Три ячейки таблицы для визуального представления
mid: null, // ...диапазона, в котором находится загаданное число.
high: null
};
// Отыскать каждый из этих элементов по их атрибутам id
for(var id in ui) ui[id] = document.getElementByld(id):
// Определить обработчик событий для поля ввода
ui.input.onchange = handleGuess;
// Выбрать случайное число и инициализировать состояние игры
state = {
n: Math.floor(99 * Math.random()) + 1, // Целое число: 0 < n < 100
low: 0, // Нижняя граница, где находится угадываемое число
high: 100, // Верхняя граница, где находится угадываемое число
guessnum: 0, // Количество выполненных попыток угадать число
guess: undefined // Последнее число, указанное пользователем
};
// Изменить содержимое документа, чтобы отобразить начальное состояние
display(state):
// Эта функция вызывается как обработчик события onload, а также как обработчик щелчка
// на кнопке Play Again (Играть еще раз), которая появляется в конце игры.
// Во втором случае аргумент playagain будет иметь значение true, и если это так,
// мы сохраняем новое состояние игры. Но если функция была вызвана в ответ
// на событие "load", сохранять состояние не требуется, потому что событие "load"
// может возникнуть также при переходе назад по истории посещений из какого-то
// другого документа в существующее состояние игры. Если бы мы сохраняли начальное
// состояние, в этом случае мы могли бы затереть имеющееся в истории актуальное
// состояние игры. В броузерах, поддерживающих метод pushState(), за событием "load"
// всегда следует событие "popstate". Поэтому, вместо того чтобы сохранять
// состояние здесь, мы ждем событие "popstate". Если вместе с ним будет получен
// объект состояния, мы просто используем его. Иначе, если событие "popstate"
// содержит в поле state значение null, мы знаем, что была начата новая игра,
// и поэтому используем replaceState для сохранения нового состояния игры,
if (playagain === true) save(state);
}
// Сохраняет состояние игры с помощью метода pushStateO, если поддерживается
function save(state) {
if (!history.pushState) return;// Вернуться, если pushState() не определен
// С каждым состоянием мы связываем определенную строку URL-адреса.
// Этот адрес отображает число попыток, но не содержит информации о состоянии игры,
// поэтому его нельзя использовать для создания закладок.
// Мы не можем поместить информацию о состоянии в URL-адрес,
// потому что при этом пришлось бы указать в нем угадываемое число,
var url = "#guess" + state.guessnum;
// Сохранить объект с информацией о состоянии и строку URL
history.pushState(state, // Сохраняемый объект с информацией о состоянии
"", // Заглавие: текущие броузеры игнорируют его
url); // URL состояния: бесполезен для закладок
}
// Обработчик события onpopstate, восстанавливающий состояние приложения,
function popState(event) {
if (event.state) { // Если имеется объект состояния, восстановить его
// Обратите внимание, что event.state является полной копией
// сохраненного объекта состояния, поэтому мы можем изменять его,
// не опасаясь изменить сохраненное значение.
state = event.state; // Восстановить состояние
displayCstate); // Отобразить восстановленное состояние
}
else {
// Когда страница загружается впервые, событие "popstate" поступает
// без объекта состояния. Заменить значение null действительным
// состоянием: смотрите комментарий в функции newgame().
// Нет необходимости вызывать display() здесь.
history.replaceState(state, "", "#guess" + state.guessnum);
}
};
// Этот обработчик событий вызывается всякий раз, когда пользователь вводит число.
// Он обновляет состояние игры, сохраняет и отображает его.
function handle6uess() {
// Извлечь число из поля ввода
var g = parseInt(this.value);
// Если это число и оно попадает в требуемый диапазон
if ((g > state.low) && (g < state.high)) {
// Обновить объект состояния для этой попытки
if (g < state.n)
state.low = g;
else
if (g > state.n) state.high = g;
state.guess = g;
state.guessnum++;
// Сохранить новое состояние в истории посещений
save(state);
// Изменить документ в ответ на попытку пользователя
display(state);
}
else { // Ошибочная попытка: не сохранять новое состояние
alert("Please enter a number greater than " + state.low +
" and less than " + state.high);
}
}
// Изменяет документ, отображая текущее состояние игры,
function display(state) {
// Отобразить заголовок документа
ui.heading.innerHTML = document.title =
"I'm thinking of a number between " + state.low +
" and " + state.high + "."
// Отобразить диапазон чисел с помощью таблицы
ui.low.style.width = state.low + "%";
ui.mid.style.width = (state.high-state.low) + "%";
ui.high.style.width = (100-state.high) + "%";
// Сделать поле ввода видимым, очистить его и установить фокус ввода
ui.input.style.visibility = "visible";
ui.input.value = "";
ui.input.focus();
// Вывести приглашение к вводу, опираясь на последнюю попытку
if (state.guess === undefined)
ui.prompt.innerHTML = "Type your guess and hit Enter:";
else if (state.guess < state.n)
ui.prompt.innerHTML = state.guess + " is too low. Guess again:";
else if (state.guess > state.n)
ui.prompt.innerHTML = state.guess + " is too high. Guess again:";
else {
// Если число угадано, скрыть поле ввода и отобразить кнопку
// Play Again (Играть еще раз).
ui.input.style.visibility = "hidden"; // Попыток больше не будет
ui.heading.innerHTML = document.title = state.guess + " is correct!";
ui.prompt.innerHTML =
"You Win! <button>Play Again</button>“;
}
}
</script>
<style> /* CSS-стили, чтобы придать игре привлекательный внешний вид */
#prompt { font-size: 16pt; }
table { width: 90%; margin:10px; margin-left:5%; }
#low, «high { background-color: lightgray; height: 1em; }
#mid { background-color: green; }
</style>
</head>
<body><!-- Следующие элементы образуют пользовательский интерфейс игры -->
<!-- Заголовок игры и текстовое представление диапазона чисел -->
<h1>I'm thinking of a number...</h1>
<!-- визуальное представление чисел, которые еще не были исключены -->
<table><tr><tdx/td><tdx/td><tdprompt"x/label><input type="text">
</body></html>
- 22.1. Геопозиционирование
- 22.2. Управление историей посещений
- 22.3. Взаимодействие документов с разным происхождением
- 22.4. Фоновые потоки выполнения
- 22.5. Типизированные массивы и буферы
- 22.6. Двоичные объекты
- 22.7. Прикладной интерфейс к файловой системе
- 22.8. Базы данных на стороне клиента
- 22.9. Веб-сокеты
- 14.3. История посещений
- УПРАВЛЕНИЕ РЕПУТАЦИЕЙ В ИНТЕРНЕТЕ
- Глава 7 Управление хранилищем данных
- Глава 11 Корпоративное управление и стратегия
- 5.7 Устройства NAS под управлением Windows и моментальные снимки
- 7.8 Управление иерархическим хранилищем
- Управление файлами занятий
- Управление пользователями и разрешениями узла
- Управление функциями узла
- Управление объединением содержимого узлов
- Использование панели задач Управление документами в приложениях Office 2007
- Управление rpm-пакетами: нынче не то, что давеча