Веб-приложения могут использовать прикладные программные интерфейсы браузеров для сохранения данных локально, на компьютере пользователя. Этот механизм сохранения данных на стороне клиента выполняет роль памяти для веб-браузеров. Веб-приложения могут сохранять, например, настройки пользователя или даже полную информацию о своем состоянии, чтобы иметь возможность возобновить работу точно с того момента, на котором их работа была прервана при последнем посещении. Хранилище на стороне клиента разграничивает данные в соответствии с происхождением, поэтому страницы с одного сайта не смогут читать данные, сохраненные страницами с другого сайта. Но две страницы с одного и того же сайта смогут использовать хранилище в качестве механизма взаимодействий. Например, данные, введенные в форме на одной странице, можно отображать в таблице на другой странице. Веб-приложения могут устанавливать срок хранения своих данных: данные могут храниться временно, т.е. получить такие данные из хранилища можно, пока не будет закрыто окно или пока браузер не завершит работу, или сохраняться на жестком диске и храниться постоянно, чтобы их можно было получить месяцы или даже годы спустя.
Существует несколько разновидностей хранилищ на стороне клиента:
Web Storage
localStorage
и
sessionStorage
, которые, по сути, являются постоянно хранимыми ассоциативными
массивами, отображающими ключи в строковые значения. Интерфейс Web Storage
очень прост в использовании, он подходит для хранения больших (но не огромных)
объемов данных и доступен во всех текущих браузерах, но не поддерживается старыми
браузерами. Объекты localStorage
и sessionStorage
описываются в разделе 20.1.
Cookies
IE User Data
Offline Web Applications
Разработчики, которым приходится работать с по-настоящему огромными объемами данных, предпочитают использовать базы данных, и многие производители начинают включать в свои браузеры функциональные возможности доступа к базам данных на стороне клиента. браузеры Safari, Chrome и Opera включают прикладной интерфейс к базе данных SQL. Однако попытка стандартизации этого прикладного интерфейса потерпела неудачу, и весьма маловероятно, что он будет реализован в Firefox и IE. Существует альтернативный прикладной интерфейс доступа к базам данных, который был стандартизован под названием «Indexed Database API». Это прикладной интерфейс к простейшей объектной базе данных, не поддерживающей язык запросов. Оба прикладных интерфейса являются асинхронными и требуют использования обработчиков событий, что усложняет их использование. Они не будут описываться в этой главе, но в разделе 22.8 вы найдете краткий обзор и пример применения механизма IndexedDB API.
Прикладной интерфейс к файловой системе
File
, позволяющий с помощью объекта XMLHttpRequest
выгружать файлы, выбранные пользователем. Проекты родственных стандартов
определяют прикладной интерфейс для организации частной, локальной файловой
системы и выполнения операций чтения и записи в файлы, находящиеся в этой
файловой системе. Эти появляющиеся прикладные интерфейсы описываются
в разделе 22.7. Когда они получат более
широкое распространение, у веб-приложений будет возможность использовать
механизмы хранения данных, основанные на файлах, уже знакомые многим
программистам.
Веб-браузеры часто предлагают пользователям сохранить пароли и сохраняют их на диске в зашифрованном виде. Но ни один из механизмов хранения данных на стороне клиента, описываемый в этой главе, никак не связан с шифрованием: все, что вы будете сохранять, будет сохраняться на жестком диске в незашифрованном виде. То есть хранящиеся данные могут быть извлечены чересчур любопытным пользователем, имеющим доступ к компьютеру, или злонамеренным программным обеспечением (например, разнообразными шпионскими программами), находящимся на компьютере. По этой причине ни один из механизмов хранения данных на стороне клиента никогда не должен использоваться для хранения паролей, номеров банковских счетов или другой конфиденциальной информации. Запомните: тот факт, что пользователь вводит какую-то информацию в поля форм при взаимодействии с вашим веб-сайтом, еще не означает, что он хочет сохранить копию этой информации на диске. Возьмите в качестве примера номер кредитной карты. Это конфиденциальная информация, которую люди предпочитают сохранять в тайне в своих бумажниках. Сохранить эту информацию с помощью механизма хранения данных на стороне клиента – это все равно что написать номер кредитной карты на бумажке и приклеить ее к клавиатуре.
Кроме того, учтите, что многие пользователи не доверяют веб-сайтам, использующим cookies или другие механизмы хранения данных на стороне клиента для целей, которые напоминают «слежение». Применяйте механизмы хранения, описываемые в этой главе, для повышения удобства работы с вашим сайтом, но не используйте их как механизм сбора конфиденциальной информации. Если появится слишком большое количество сайтов, дискредитирующих механизмы хранения данных на стороне клиента, пользователи будут отключать их или часто очищать хранилища, что сделает невозможным и их использование, и работу сайтов, опирающихся на их применение.
localStorage
и sessionStorage
браузеры, реализующие положения проекта спецификации «Web Storage», onpeделяют
в объекте Window
два свойства: localStorage
и sessionStorage
. Оба свойства ссылаются на объект
Storage
– постоянно хранимый
ассоциативный массив, отображающий строковые ключи в строковые значения. Объекты
Storage действуют подобно обычным объектам в языке JavaScript: достаточно просто
присвоить свойству объекта строку, и браузер автоматически сохранит ее. Разница
между localStorage
и sessionStorage
заключается лишь
в сроке хранения и области видимости: они определяют, как долго будут храниться
данные и кому они будут доступны. Ниже мы подробнее поговорим о сроке хранения
и области видимости. А пока рассмотрим несколько примеров. Следующий фрагмент
использует свойство localStorage
, но он точно так же мог бы
работать и со свойством sessionStorage
:
var name = localStorage.username; // Получить сохраненное значение. name = localStorage["username"]; // Эквивалентная форма обращения, как к массиву if (!name) { name = prompt("Как вас зовут?"); // Задать пользователю вопрос. localStorage.username = name; // Сохранить ответ. } // Выполнить итерации по всем хранящимся парам имя/значение for(var name in localStorage) { // Итерации по всем хранящимся именам var value = localStorage[name]; // Получить значение для каждого из них }
Объекты Storage также определяют методы для сохранения, извлечения и удаления
данных. Эти методы рассматриваются в разделе 20.1.2.
Проект спецификации «Web Storage» определяет возможность сохранения
структурированных данных (объектов и массивов), а также простых значений и данных
встроенных типов, таких как даты, регулярные выражения и даже объекты
File
. Однако на момент написания этих строк браузеры позволяли сохранять
только строки. Если потребуется сохранять и извлекать данные других типов,
их можно кодировать и декодировать вручную. Например:
// При сохранении числа оно автоматически преобразуется в строку. // Не забудьте выполнить обратное преобразование при извлечении из хранилища. localStorage.х = 10; var x = parselnt(localStorage.x); // Преобразовать объект Date в строку при записи и обратно - при чтении localStorage.lastRead = (new Date()).toUTCString(); var lastRead = new Date(Date.parse(localStorage.lastRead)); // Для кодирования любых простых или структурированных данных удобно // использовать формат JS0N localStorage.data = JSON.stringify(data); // Закодировать и сохранить var data = JSON.parse(localStorage.data); // Извлечь и декодировать.
Объекты localStorage
и sessionStorage
отличаются
сроком хранения данных и областью видимости хранилища. Объект localStorage
представляет долговременное хранилище данных: срок хранения не ограничен, и данные сохраняются
на компьютере пользователя, пока не будут удалены веб-приложением или пока
пользователь не потребует от браузера (посредством некоторого пользовательского
интерфейса, предоставляемого браузером) удалить их.
Доступность данных в объекте localStorage
ограничивается
происхождением документа. Как описывалось в разделе 13.6.2,
происхождение документа определяется такими параметрами, как протокол, имя хоста
и номер порта, поэтому все следующие URL-адреса ссылаются на документы с разным
происхождением:
http://www.example.com // Протокол: http; имя хоста: www.example.com https://www.example.com // Другой протокол http://static.example.com // Другое имя хоста http://www.example.com:8000 // Другой порт
Все документы, имеющие одно и то же происхождение, будут совместно
использовать одни и те же данные в объекте localStorage
(независимо
от происхождения сценария, который фактически обращается к хранилищу
localStorage
). Они смогут читать и изменять данные друг друга. Но документы
с разными происхождениями никогда не смогут прочитать или изменить данные
друг друга (даже если оба они будут выполнять сценарий, полученный с одного
и того же стороннего сервера).
Обратите внимание, что видимость данных в хранилище localStorage
также ограничивается конкретным браузером. Если посетить сайт с помощью Firefox,
а затем вновь посетить его, например, с помощью Chrome, никакие данные,
сохраненные при первом посещении, не будут доступны при втором посещении.
Данные, сохраняемые в sessionStorage
, имеют другой срок хранения:
они хранятся, пока остается открытым окно верхнего уровня или вкладка браузера,
в которой выполнялся сценарий, сохранивший эти данные. При закрытии окна или
вкладки все данные, хранящиеся в sessionStorage
, удаляются. (Отметьте, однако,
что современные браузеры имеют возможность повторно открывать недавно закрытые
вкладки и восстанавливать последний сеанс работы с браузером, поэтому срок
хранения информации об этих вкладках и связанных с ними хранилищах
sessionStorage
может оказаться больше, чем кажется.)
Доступность данных в хранилище sessionStorage
, как и в хранилище
localStorage
, ограничивается происхождением документа, т. е. документы
с разным происхождением никогда не смогут совместно использовать одни и те же
данные в sessionStorage
. Но помимо этого доступность данных в хранилище
sessionStorage
ограничивается также окном. Если пользователь откроет
в браузере две вкладки, отображающие документы с общим происхождением, эти две
вкладки будут владеть разными хранилищами sessionStorage
. Сценарий,
выполняющийся в одной вкладке, не сможет прочитать или изменить данные,
сохраненные в другой вкладке, даже если в обеих вкладках будет открыта одна и та
же страница и будет выполняться один и тот же сценарий. Обратите внимание, что
разграничение хранилищ sessionStorage
в разных окнах касается только
окон верхнего уровня. Если в одной вкладке браузера будет находиться несколько
элементов <iframe>
и в этих фреймах будут отображаться документы с общим
происхождением, они будут совместно использовать одно и то же хранилище
sessionStorage
.
Объекты localStorage
и sessionStorage
часто используются
как обычные объекты языка JavaScript: присваивание значения свойству приводит
к сохранению строки, а чтение свойства – к ее извлечению из хранилища.
Но эти объекты определяют также более формальный прикладной интерфейс, основанный
на методах. Сохранить значение можно с помощью метода setltem()
,
передав ему имя и значение. Извлечь значение можно с помощью метода
getltem()
, передав ему имя. Удалить значение можно с помощью метода
removeItem()
, передав ему имя. (В большинстве браузеров удалить значение
можно также с помощью оператора delete, как если бы оно было обычным
объектом, но этот прием не работает в IE8.) Удалить все хранящиеся значения можно
вызовом метода с1еаг() (без аргументов). Наконец, перечислить имена
всех хранящихся значений можно с помощью свойства length и метода
кеу(), передавая ему значения от 0 до length-1. Ниже приводится
несколько примеров использования объекта localStorage
. Этот программный код
с тем же успехом мог бы использовать объект sessionStorage
:
localStorage.setltem("x", 1); // Сохранить число под именем "x" localStorage.getltem("x"); // Извлечь значение // Перечислить все хранящиеся пары имя-значение
for(var i = 0; i < localStorage.length; i++) { // length дает количество пар var name = localStorage.key(i); // Получить имя i-й пары var value = localStorage.getltem(name); // Получить значение этой пары } localStorage.removeltem("x"); // Удалить элемент "x" localStorage.clear(); // Удалить все остальные элементы
Несмотря на то что обычно удобнее сохранять и извлекать данные, обращаясь
к свойствам, тем не менее иногда может потребоваться использовать эти методы.
Во-первых, метод с1еаг()
не имеет эквивалента и является единственным
способом удаления всех пар имя/значение в объекте Storage
. Аналогично
метод removeItem()
является единственным переносимым способом удаления
одной пары имя/значение, потому что IE8 не позволяет использовать оператор
delete
для этой цели. Если производители браузеров реализуют все
положения спецификации и позволят сохранять в объекте Storage
другие
объекты и массивы, появится еще одна причина использовать методы, такие как
setltem()
и getltem()
. Объекты и массивы являются изменяемыми
значениями, поэтому объекту Storage
необходимо будет создавать копию
сохраняемого значения, чтобы все последующие изменения в оригинале не коснулись
хранящегося значения. Точно так же объект Storage
должен будет
создавать копию и при извлечении значения, чтобы никакие изменения в извлеченном
значении не коснулись хранящегося значения. Когда такое копирование будет
реализовано, использование интерфейса свойств может показаться запутывающим.
Взгляните на следующий (гипотетический, пока браузеры не поддерживают возможность
сохранения структурированных значений) фрагмент:
localStorage.о = {x:1}; // Сохранить объект, имеющий свойство x localStorage.о.x =2; // Попытка установить свойство хранящегося объекта localStorage.о.x // => 1: свойство x не изменилось
Во второй строке этого фрагмента выполняется безуспешная попытка присвоить
новое значение свойству хранящегося объекта, потому что интерпретатор сначала
извлечет копию хранящегося объекта, присвоит свойству копии новое значение
и затем удалит копию. В результате изменения не коснутся хранящегося объекта.
Меньше шансов ошибиться, если использовать метод getltem()
:
localStorage.getltemfо").х = 2; // Не предполагает сохранение значения 2
Наконец, еще одна причина отдать предпочтение более явному прикладному
интерфейсу на основе методов заключается в возможности имитировать этот
прикладной интерфейс поверх других механизмов сохранения данных в браузерах,
которые пока не поддерживают спецификацию «Web Storage». В следующих разделах
будут представлены примеры реализации интерфейса объекта Storage с применением
cookies и userData в IE. При использовании прикладного интерфейса на основе
методов можно писать программный код, который будет использовать свойство
localStorage
, когда оно доступно, и возвращаться к использованию
других механизмов в противном случае. Такой программный код мог бы начинаться
следующими строками:
// Определить, какой механизм хранения будет использоваться var memory = window.localStorage || (window.UserDataStorage && new UserDataStorageO) || new CookieStorageO; // Затем отыскать требуемый элемент в хранилище var username = memory.getltem("username");
При изменении данных, хранящихся в localStorage
или sessionStorage
, браузер
генерирует событие «storage» во всех объектах Window, в которых доступны
эти данные (но не в окне, где выполнялось сохранение). Если в браузере открыты
две вкладки со страницами с общим происхождением и в одной из страниц
производится сохранение значения в localStorage
, в другой вкладке будет
сгенерировано событие «storage». He забывайте, что область
видимости данных, хранящихся в sessionStorage
, ограничивается
окном верхнего уровня, поэтому при изменении данных в sessionStorage
события «storage» будут генерироваться только при наличии нескольких фреймов.
Обратите также внимание, что события «storage» генерируются, только когда
содержимое хранилища действительно изменяется. Присваивание хранимому элементу
его текущего значения, как и попытка удалить несуществующий элемент,
не возбуждают событие.
Регистрация обработчиков события «storage» выполняется с помощью
метода addEventl_istener()
(или attachEvent()
в IE). В большинстве браузеров для этой цели можно
также использовать свойство onstorage объекта Window, но на момент написания этих
строк данное свойство не поддерживалось в Firefox. Объект события,
связанный с событием «storage», имеет пять основных свойств (к сожалению, они
не поддерживаются в IE8):
key
newValue
oldValue
storageArea
localStorage
или sessionStorage
целевого объекта Window.url
Наконец, обратите внимание, что объект localStorage
и событие
«storage» могут служить широковещательным механизмом, с помощью которого браузер
может отправлять сообщения всем окнам, в которых в настоящий момент открыт один
и тот же веб-сайт. Например, если пользователь потребует от веб-сайта прекратить
воспроизводить анимационные эффекты, сценарий может сохранить соответствующий
параметр настройки в localStorage
, чтобы соблюсти это требование при
последующих посещениях сайта. При сохранении параметра будет сгенерировано
событие, что позволит другим окнам, отображающим тот же сайт, также удовлетворить
это требование. В качестве другого примера представьте веб-приложение
графического редактора, позволяющее пользователю отображать палитры
с инструментами в отдельных окнах. При выборе пользователем некоторого
инструмента приложение могло бы сохранять в localStorage
признак
выбранного инструмента и тем самым рассылать другим окнам извещения о том, что
был выбран новый инструмент.
Cookies – это небольшие фрагменты именованных данных, сохраняемые
веб-браузером и связанные с определенными веб-страницами или веб-сайтами.
Cookies первоначально предназначались для разработки серверных
сценариев и на низшем уровне реализованы как расширение протокола HTTP. Данные
в cookies автоматически передаются между веб-браузером и
веб-сервером, благодаря чему серверные сценарии могут читать и записывать
значения, сохраняемые на стороне клиента. В этом разделе будет показано, как
клиентские сценарии могут работать с cookies, используя свойство
cookie
объекта Document
.
Прикладной интерфейс для работы с cookies является одним из старейших,
а это означает, что он поддерживается всеми браузерами. К сожалению, этот
прикладной интерфейс слишком замысловат. В нем отсутствуют методы: операции
чтения, записи и удаления cookies осуществляются с помощью свойства
cookie
объекта Document
с применением строк
специального формата. Срок хранения и область видимости можно указать отдельно
для каждого cookie
с помощью атрибутов. Эти атрибуты также
определяются посредством записи строк специального формата в то же самое
свойство cookie
.
Особого смысла у термина «cookie» (булочка) нет, тем не менее появился он не «с потолка». В туманных анналах истории компьютеров термин «cookie», или «magic cookie», использовался для обозначения небольшой порции данных, в частности, привилегированных или секретных данных, вроде пароля, подтверждающих подлинность или разрешающих доступ. В JavaScript cookies применяются для сохранения информации о состоянии и могут служить средством идентификации для веб-браузера, хотя они не шифруются и не могут считаться безопасными (впрочем, это не относится к передаче их через защищенное соединение по протоколу https:).
В следующих подразделах сначала будут описаны атрибуты,
которые определяют срок хранения и область видимости cookies, а затем
будет продемонстрировано, как сохранять и извлекать значения cookies
в сценариях на языке JavaScript. Завершится этот раздел примером
реализации на основе cookies прикладного интерфейса,
имитирующего объект Storage
.
Cookies пользуются дурной славой у многих пользователей Всемирной паутины
из-за недобросовестного использования cookies, связанных не с самой
веб-страницей, а с изображениями на ней. Например, сторонние cookies
позволяют компаниям, предоставляющим услуги рекламного характера, отслеживать
перемещение пользователей с одного сайта на другой, что вынуждает многих
пользователей по соображениям безопасности отключать режим сохранения
cookies в своих веб-браузерах. Поэтому, прежде чем использовать
cookies в сценариях JavaScript, следует проверить, не отключен ли режим
их сохранения. В большинстве браузеров это можно сделать, проверив свойство
navigator.cookieEnabled
. Если оно содержит значение
true
, значит, работа с cookies разрешена, а если false
–
запрещена (хотя при этом может быть разрешено использование временных cookies,
срок хранения которых ограничивается продолжительностью сеанса работы браузера).
Свойство navigator.cookieEnabled
не является стандартным, поэтому
если сценарий вдруг обнаружит, что оно не определено, придется проверить,
поддерживаются ли cookies, попытавшись записать, прочитать и удалить тестовый
cookie
, используя прием, описываемый ниже.
Помимо имени и значения каждый cookie
имеет необязательные
атрибуты, управляющие сроком его хранения и областью видимости. По умолчанию
cookies являются временными – их значения сохраняются на период
сеанса веб-браузера и теряются при закрытии браузера. Обратите внимание, что
этот срок хранения не совпадает со сроком хранения данных в
sessionStorage
: доступность cookies не ограничивается единственным
окном, поэтому период их хранения по умолчанию совпадает с периодом работы
процесса браузера, а не какого-то одного окна. Чтобы cookie
сохранялся после окончания сеанса, необходимо сообщить браузеру, как долго
(в секундах) он должен храниться, указав значение атрибута max-ageage
.
Если указать срок хранения, браузер сохранит cookie
в локальном файле
и удалит его только по истечении срока хранения.
Видимость cookie
ограничивается происхождением документа, как и
при использовании хранилищ localStorage
и sessionStorage
,
а также строкой пути к документу. Область видимости cookie
может
регулироваться посредством атрибутов path
и domain
.
По умолчанию cookie
связывается с создавшей его веб-страницей
и доступен этой странице, а также другим страницам из того же каталога или
любых его подкаталогов. Если, например, веб-страница
http://www.example.com/catalog/index.html создаст cookie
,
то этот cookie
будет также видим страницам
http://www.example.com/catalog/order.html
и http://www.example.com/catalog/widgets/index.html, но невидим странице
http://www.example.com/about.html.
Этого правила видимости, принятого по умолчанию, обычно вполне достаточно.
Тем не менее иногда значения cookie
требуется использовать на всем
многостраничном веб-сайте независимо от того, какая страница создала
cookie
. Например, если пользователь ввел свой адрес в форму
на одной странице, целесообразно было бы сохранить этот адрес
как адрес по умолчанию. Тогда этим адресом можно будет воспользоваться
при следующем посещении тем же пользователем этой страницы, а также
при заполнении им совершенно другой формы на любой другой странице, где требуется
ввести адрес, например для выставления счета. Для этого в cookie
можно определить атрибут path. И тогда любая страница того же
веб-сервера с URL-адресом, начинающимся с указанного значения, сможет
использовать этот cookie. Например, если для cookie
,
установленного страницей http://www.example.com/catalog/widgets/index.html,
в атрибуте path установлено значение «/catalog», этот cookie
также будет виден для страницы http://www.example.com/catalog/order.html.
А если атрибут path установлен в значение «/
», то cookie
будет видим для любой страницы на веб-сервере http://www.example.com.
Установка атрибута path в значение «/
» определяет
такую же область видимости cookie, как для хранилища
localStorage
, а также говорит о том, что браузер должен передавать
имя и значение cookie
на сервер при запросе любой веб-страницы
с этого сайта. Имейте в виду, что атрибут path не должен
восприниматься как своеобразный механизм управления доступом.
Если веб-странице потребуется прочитать cookies, принадлежащие
какой-то другой странице на том же веб-сайте, она может просто загрузить
эту страницу в скрытый элемент <iframe> и прочитать все cookies,
принадлежащие документу во фрейме. Политика общего происхождения
(раздел 13.6.2) предотвращает подобное
«подглядывание» за cookies, установленных веб-страницами с других
сайтов, но оно считается вполне допустимым для документов с одного
и того же сайта.
По умолчанию cookies доступны только страницам с общим
происхождением. Однако большим веб-сайтам может потребоваться возможность
совместного использования cookies несколькими поддоменами. Например,
серверу order.example.com может потребоваться прочитать значения
cookie, установленного сервером catalog.example.com. В этой ситуации
поможет атрибут domain. Если cookie
, созданный страницей
с сервера catalog.example.com, имеет в атрибуте path значение
«/
», а в атрибуте domain – значение «.example.com», этот
cookie
будет доступен всем веб-страницам в поддоменах
catalog.example.com, orders.example.com и в любых других поддоменах
в домене example.com. Если атрибут domain не установлен,
его значением по умолчанию будет имя веб-сервера, на котором находится страница.
Обратите внимание, что в атрибут domain нельзя записать значение,
отличающееся от домена вашего сервера.
Последний атрибут cookie
– это логический атрибут с именем
secure, определяющий, как значения cookie
передаются по сети.
По умолчанию cookie
не защищен, т.е. передается по обычному
незащищенному HTTP-соединению. Однако если cookie
помечен
как защищенный, он передается, только когда обмен между браузером и сервером
организован по протоколу HTTPS или другому защищенному протоколу.
Чтобы связать временное значение cookie
с текущим документом,
достаточно присвоить его свойству cookie
строку следующего формата:
имя-значение
Например:
document.cookie = "version=" + encodeURIComponent(document.lastModified);
При следующем чтении свойства cookie
сохраненная пара
имя/значение будет включена в список cookies документа. Значения
cookie
не могут содержать точки с запятой, запятые или пробельные
символы. По этой причине для кодирования значения перед сохранением его
в cookie
, возможно, потребуется использовать глобальную
JavaScript-функцию encodeURIComponent()
. В этом случае при чтении
значения cookie
надо будет вызвать соответствующую функцию
decodeURIComponent()
. Записанный таким способом cookie
сохраняется в течение сеанса работы веб-браузера, но теряется при его закрытии
пользователем. Чтобы создать cookie
, сохраняющийся между сеансами
браузера, необходимо указать срок его хранения (в секундах) с помощью атрибута
max-age
. Это можно сделать, присвоив свойству cookie
строку следующего формата:
имя=значение; max-age=число_секунд
Следующая функция устанавливает cookie
с дополнительным атрибутом max-age:
// Сохраняет пару имя/значение в виде cookie, кодируя значение с помощью // encodeURIComponent(), чтобы экранировать точки с запятой, запятые и пробелы. // Если в параметре daysToLive передается число, атрибут max-age // устанавливается так, что срок хранения cookie истекает через // указанное число дней. Если передать значение 0, cookie будет удален. function setCookie(name, value, daysToLive) { var cookie = name + "=" + encodeURIComponent(value); if (typeof daysToLive === "number") cookie += "; max-age=" + (daysTolive*60*60*24); document.cookie = cookie; }
Аналогичным образом можно установить атрибуты path
,
domain
и secure
, дописав к значению cookie
строки следующего формата перед его записью в свойство cookie
:
; path=путь ; secure=домен ; secure
Чтобы изменить значение cookie
, установите его значение снова,
указав то же имя, путь, домен и новое значение. При изменении значения
cookie
можно также переопределить срок его хранения, указав новое
значение в атрибуте max-age
. Чтобы удалить cookie
,
установите его снова, указав то же имя, путь, домен и любое произвольное
(возможно пустое) значение, а в атрибут max-age
запишите 0.
Когда свойство cookie
используется в JavaScript-выражении,
возвращаемое им значение содержит все cookies, относящиеся к текущему документу.
Эта строка представляет собой список пар имя = значение, разделенных точками
с запятой и пробелами. Значение не включает какие-либо атрибуты, которые могли
быть установлены для cookie
. При работе со свойством
document.cookie
обычно приходится использовать метод
split()
, чтобы разбить его значение на отдельные пары имя/значение.
После извлечения значения cookie
из свойства cookie
его требуется интерпретировать, основываясь на том формате или кодировке, которые
были указаны создателем cookie
. Например, cookie
можно
передать функции decodeURIComponent()
, а затем функции
JSON.parse()
.
В примере 20.1 определяется функция getCookie()
, которая
анализирует свойство document.cookie
и возвращает объект, свойства которого соответствуют парам имя/значение всех cookies в документе.
Пример 20.1. Анализ свойства document.cookie
// Возвращает cookies документа в виде объекта с парами имя/значение.
// Предполагается, что значения cookie
кодируются с помощью
// функции encodeURIComponent().
function getCookies() {
var cookies = {}; // Возвращаемый объект
var all = document.cookie; // Получить все cookies в одной строке
if (all === "") // Если получена пустая строка,
return cookies; // вернуть пустой объект
var list = all.split("; "); // Разбить на пары имя/значение
for(var i = 0; i < list.length; i++) { // Для каждого cookie
var cookie = list[i];
var p = cookie.indexOf("="); // Отыскать первый знак =
var name = cookie.substring(0,p); // Получить имя cookie
var value = cookie.substring(p+1); // Получить значение cookie
value = decodeURIComponent(value); // Декодировать значение
cookies[name] = value; // Сохранить имя и значение в объекте
}
return cookies;
}
Cookies предназначены для сохранения небольших объемов данных серверными
сценариями, которые должны передаваться на сервер при обращении к каждому
соответствующему URL-адресу. Стандарт, определяющий cookies, рекомендует
производителям браузеров не ограничивать количество и размеры сохраняемых
cookies, но браузеры не обязаны сохранять в сумме более 300 cookies, 20 cookies
на один веб-сервер или по 4 Кбайт данных на один cookie
(в этом
ограничении учитываются и значение cookie
, и его имя). На практике
браузеры позволяют сохранять гораздо больше 300 cookies, но ограничение на размер
4 Кбайт для одного cookie
в некоторых браузерах по-прежнему
соблюдается.
Пример 20.2 демонстрирует, как поверх cookies можно реализовать методы,
имитирующие прикладной интерфейс объекта Storage
. Передайте
конструктору СоokieStorage()
желаемые значения атрибутов
max-age
и path
и используйте получившийся объект
подобно тому, как вы использовали бы localStorage
или
sessionStorage
. Однако имейте в виду, что этот пример не реализует
событие «storage» и не выполняет автоматическое сохранение или извлечение
значений при записи или чтении свойств объекта CookieStorage
.
Пример 20.2. Реализация интерфейса объекта Storage на основе cookies h
/* * CookieStorage.js * Этот класс реализует прикладной интерфейс объекта Storage, на который ссылаются * свойства localStorage и sessionStorage, но поверх HTTP Cookies. */ function CookieStorage(maxage, path) { // Аргументы определяют срок хранения // и область видимости // Получить объект, хранящий все cookies var cookies = (function() { // Функция getCookies(), реализованная выше var cookies = {}; // Возвращаемый объект var all = document.cookie; // Получить все cookies в одной строке if (all === "") // Если получена пустая строка return cookies; // вернуть пустой объект var list = all.split("; "); // Разбить на пары имя/значение for(var i = 0; i < list.length; i++) { // Для каждого cookie var cookie = list[i]; var p = cookie.indexOf("="); // Отыскать первый знак = var name = cookie.substring(0,p); // Получить имя cookie var value = cookie.substring(p+1); // Получить значение cookie value = decodeURIComponent(value); // Декодировать значение cookies[name] = value; // Сохранить имя и значение } return cookies; }()); // Собрать имена cookies в массиве var keys = []; for(var key in cookies) keys.push(key); // Определить общедоступные свойства и методы Storage API // Количество хранящихся cookies this.length = keys.length; // Возвращает имя n-го cookie или null, если n вышло за диапазон индексов this.key = function(n) { if (n < 0 || n >= keys.length) return null; return keys[n]; }; // Возвращает значение указанного cookie или null, this.getItem = function(name) { return cookies[name] || null; }; // Сохраняет значение this.setItem = function(key, value) { if (!(key in cookies)) { // Если cookie с таким именем не существует keys.push(key); // Добавить ключ в массив ключей this.length++; // И увеличить значение length } // Сохранить пару имя/значение в множестве cookies. cookies[key] = value; // Установить cookie. // Предварительно декодировать значение и создать строку // имя=кодированное-значение var cookie = key + "=" + encodeURIComponent(value); // Добавить в строку атрибуты cookie if (maxage) cookie += "; max-age=" + maxage; if (path) cookie += "; path=" + path; // Установить cookie с помощью свойства document.cookie document.cookie = cookie; }; // Удаляет указанный cookie this.removeItem = function(key) { if (!(key in cookies)) return; // Если не существует, ничего не делать // Удалить cookie из внутреннего множества cookies delete cookies[key]; // И удалить ключ из массива имен. // Это легко можно было бы сделать с помощью метода indexOf() массивов, // определяемого стандартом ES5. for(var i = 0; i < keys.length; i++) { // Цикл по всем ключам if (keys[i] === key) { // При обнаружении ключа keys.splice(i,1); // Удалить его из массива. break; } } this.length--; // Уменьшить значение length // Наконец фактически удалить cookie, присвоив ему пустое значение // и истекший срок хранения. document.cookie = key + "=; max-age=0"; }; // Удаляет все cookies this.clear = function() { // Обойти все ключи и удалить cookies for(var i = 0; i < keys.length; i++) document.cookie = keys[i] + "=; max-age=0"; // Установить внутренние переменные в начальное состояние cookies = {}; keys = []; this.length = 0; }; }
В IE версии 5 и ниже поддерживается механизм сохранения данных на стороне
клиента, доступный в виде нестандартного свойства behavior
элемента
документа. Использовать его можно следующим образом:
var memory = document.createElement("div"); // Создать элемент memory.id = "jnemory"; // Дать ему имя memory.style.display = "none"; // He отображать его memory.style.behavior = "url('tfdefaulttfuserData')"; // Присоединить свойство document.body.appendChild(memory); // Добавить в документ
После того как для элемента будет определено поведение «userData», он получает
новые методы load()
и save()
. Вызов метода load()
загружает сохраненные данные. Этому методу можно передать строку, напоминающую
имя файла и идентифицирующую конкретный пакет хранящихся данных. После загрузки
данных пары имя/значение становятся доступны в виде атрибутов элемента, получить
которые можно с помощью метода getAttribute()
. Чтобы сохранить новые
данные, необходимо установить атрибуты вызовом метода setAttribute()
и затем вызвать метод save()
. Удалить значение можно с помощью методов
removeAttribute()
и save()
. Ниже приводится пример использования
элемента memory
, инициализированного выше:
memory.load("myStoredData"); // Загрузить сохраненные данныеvar name = memory.getAttribute("username"); // Получить элемент данных if (!name) { // Если он не был определен, name = prompt("Как вас зовут?"); // запросить у пользователя memory.setAtttribute("username", name); // Установить как атрибут memory.save("myStoredData"); // И сохранить до следующего раза }
По умолчанию данные, сохраняемые с помощью механизма userData,
имеют неограниченный срок хранения и сохраняются до тех пор, пока явно не будут
удалены. Однако с помощью свойства expires
можно определить дату, когда
истечет срок хранения данных. Например, в предыдущий фрагмент можно было бы
добавить следующие строки, чтобы указать, что срок хранения данных истечет
через 100 дней:
var now = (new Date()).getTime(); // Текущее время в миллисекундах var expires = now + 100 * 24 * 60 * 60 * 1000; // + 100 дней в миллисекундах expires = new Date(expires).toUTCStringO; // Преобразовать в строку memory.expires = expires; // Установить срок хранения
Данные, сохраняемые с помощью механизма userData в IE, доступны
только для документов, хранящихся в том же каталоге, что и оригинальный документ,
сохранивший их. Это более ограниченная область видимости, чем у cookies, которые
также доступны документам в подкаталогах оригинального каталога. В механизме
userData отсутствует какой-либо эквивалент атрибутов path
и
domain
в cookies
, позволяющий расширить область
видимости данных.
Механизм userData позволяет сохранять гораздо большие объемы данных,
чем cookies, но меньшие, чем объекты localStorage
и sessionStorage
. Пример 20.3 реализует методы
getltem()
, setltem()
и removeltem()
интерфейса Storage поверх механизма userData в IE. (Он не реализует такие методы,
как key()
и clear()
, потому что механизм userData
не предоставляет возможность выполнять итерации по всем хранящимся элементам.)
Пример 20.3. Частичная реализация интерфейса Storage на основе механизма userData в IE
а основе механизма userData в IE function UserDataStorage(maxage) { // Создать элемент документа и установить в нем специальное // свойство behavior механизма userData, чтобы получить доступ // к методам save() и load(). var memory = document.createElement("div"); // Создать элемент memory.style.display = "none"; // He отображать его memory.style.behavior = "url(′#default#userData′)"; // Присоединить свойство behavior document.body.appendChild(memory); // Добавить в документ // Если указано значение параметра maxage, хранить данные maxage секунд if (maxage) { var now = new Date().getTime(); // Текущее время var expires = now + maxage * 1000; // maxage секунд от текущего времени memory.expires = new Date(expires).toUTCString(); } Инициализировать хранилище, загрузив сохраненные значения. // Значение аргумента выбирается произвольно, но оно должно совпадать // со значением, переданным методу save() memory.load("UserDataStorage"); // Загрузить сохраненные данные this.getItem = function(key) { // Загрузить значения атрибутов return memory.getAttribute(key) || null; }; this.setItem = function(key, value) { memory.setAttribute(key,value); // Сохранить значения как атрибуты memory.save("UserDataStorage"); // Сохранять после любых изменений }; this.removeItem = function(key) { memory.removeAttribute(key); // Удалить сохраненные значения memory.save("UserDataStorage"); // Сохранить новое состояние }; }
Поскольку программный код из примера 20.3 будет работать только в IE, можно воспользоваться условными комментариями IE, чтобы предотвратить его загрузку в браузерах, отличных от IE:
<!—[if IE]> <script src="UserDataStorage. js"&lgt;</script> <![endif]-->
Стандарт HTML5 определяет новую особенность «кэш приложений»
(application cache), которая может использоваться веб-приложениями для сохранения
самих себя локально в браузере пользователя. Объекты localStorage
и sessionStorage
позволяют сохранять данные веб-приложений,
тогда как кэш приложений позволяет сохранять сами приложения – все файлы (HTML,
CSS, JavaScript, изображения и т. д.), необходимые для работы приложения.
Кэш приложений отличается от обычного кэша веб-браузера: он не очищается, когда
пользователь очищает обычный кэш. И кэшированные приложения не очищаются
по признаку LRU (least-recently used – давно не используемые), как это может
происходить в обычном кэше фиксированного размера. Приложения сохраняются в кэше
не временно: они устанавливаются и могут оставаться в нем, пока не удалят себя
сами или не будут удалены пользователем. В действительности, кэш приложений
вообще не является кэшем – для него больше подошло бы название «хранилище
приложений» (application storage).
Основная причина необходимости установки веб-приложений локально заключается
в обеспечении их доступности при работе в автономном режиме (например, в самолете
или когда сотовый телефон находится вне доступа к сети). Веб-приложения,
способные работать автономно, устанавливают себя в кэш приложений, используют
localStorage
для сохранения своих данных и реализуют механизм
синхронизации для передачи сохраненных данных при подключении к сети. Пример
автономного веб-приложения мы увидим в разделе 20.4.3,
но сначала нам необходимо узнать, как приложение может установить себя в кэш
приложений.
Чтобы установить приложение в кэш приложений, необходимо создать файл
объявления: файл, перечисляющий URL всех ресурсов, необходимых приложению.
Затем нужно просто добавить ссылку на файл объявления в основную HTML-страницу
приложения, определив атрибут manifest
в теге <html>:
<!DOCTYPE HTML> <_html manifest="myapp.appcache"><head>...</head> <body>...</body> </html>
Файлы объявлений должны начинаться со строки «CACHE MANIFEST». В следующих
строках должны перечисляться URL-адреса кэшируемых ресурсов. Относительные
URL-адреса откладываются относительно URL-адреса файла объявления. Пустые строки
игнорируются. Строки, начинающиеся с символа #
, являются
комментариями и также игнорируются. Перед комментариями могут быть пробелы,
но они не могут следовать в строке за какими-либо непробельными символами.
Ниже приводится пример простого файла объявления:
CACHE MANIFEST # Строка выше определяет тип файла. Данная строка является комментарием # Следующие строки определяют ресурсы, необходимые для работы приложения myapp.html myapp.js myapp.css images/background.png
Этот файл объявления служит признаком приложения, устанавливаемого в кэш. Если веб-приложение содержит более одной веб-страницы (более одного HTML-файла, которые могут быть открыты пользователем), в каждой из этих страниц должен быть определен атрибут , ссылающийся на файл объявления. Факт наличия во всех страницах ссылок на один и тот же файл объявления недвусмысленно говорит о том, что все они должны кэшироваться вместе как части одного и того же веб-приложения. Если в приложении имеется всего несколько HTML-страниц, их обычно перечисляют непосредственно в файле объявления. Однако это совершенно необязательно: все файлы, ссылающиеся на файл объявления, будут считаться частью веб-приложения и вместе с ним будут установлены в кэш.
Простой файл объявления, подобный тому, что показан выше, должен перечислять все ресурсы, необходимые веб-приложению. После загрузки веб-приложения в первый раз и установки его в кэш при последующих обращениях к нему оно будет загружаться из кэша. Когда приложение загружается из кэша, все необходимые ему ресурсы должны быть перечислены в файле объявления. Ресурсы, которые не были перечислены, не загружаются. Эта политика имитирует работу в автономном режиме. Если простое кэшированное приложение сможет запускаться из кэша, оно точно так же сможет запускаться, когда браузер работает в автономном режиме. Более сложные веб-приложения в общем случае не могут кэшировать каждый необходимый им ресурс отдельно. Но они тем не менее могут использовать кэш приложений, если они имеют более сложные объявления.
По соглашению файлам объявлений кэшируемых приложений даются имена с расширением .appcache. Однако это всего лишь соглашение, а для фактической идентификации типа файла веб-сервер должен отправлять файл объявления с MIME-типом «text/cache-manifest». Если при отправке файла объявления сервер установит в заголовке «Content-Type» любой другой MIME-тип, приложение не будет установлено в кэш. Вам может потребоваться специально настроить свой веб-сервер на использование нужного MIME-типа, например, создав в Apache файл .htaccess в каталоге веб-приложения.
При запуске приложения из кэша загружаются только ресурсы, перечисленные в файле объявления. В примере файла объявления, представленном выше, URL-адреса ресурсов перечисляются по одному. В действительности, файлы объявлений имеют более сложный синтаксис, чем было показано в этом примере, и существует еще два способа перечисления ресурсов в файлах объявлений. Для идентиг фикации типов записей в объявлении используются специальные строки-заголовки разделов. Простые записи, как те, что были показаны выше, помещаются в раздел «CACHE:», который является разделом по умолчанию. Два других раздела начинаются с заголовков «NETWORK:» и «FALLBACK:». (В файле объявления может быть любое количество разделов, и они могут следовать в любом порядке.)
Раздел «NETWORK:» определяет ресурсы, которые никогда не должны кэшироваться и всегда должны загружаться из сети. Здесь можно перечислить, например, URL-адреса серверных сценариев. URL-адреса в разделе «NETWORK:» в действительности являются префиксами URL-адресов. Все ресурсы, URL-адреса которых начинаются с этих префиксов, будут загружаться только из сети. Если браузер работает в автономном режиме, то попытки обратиться к таким ресурсам будут оканчиваться неудачей. В разделе «NETWORK:» допускается использовать шаблонный URL-адрес «*». В этом случае браузер будет пытаться загружать из сети все ресурсы, не упомянутые в объявлении. Это фактически отменяет правило, которое требует явно перечислять в файле объявления все ресурсы, необходимые кэшируемому приложению.
Записи в разделе «FALLBACK:» включают два URL-адреса в каждой строке. Ресурс, указанный во втором URL, загружается и сохраняется в кэше. Первый URL используется как префикс. Все URL-адреса, соответствующие этому префиксу, не кэшируются и при возможности загружаются из сети. Если попытка загрузить ресурс с таким URL-адресом терпит неудачу, вместо него будет использоваться кэшированный ресурс, определяемый вторым URL-адресом. Представьте веб-приложение, включающее несколько видеоруководств. Поскольку видеоролики имеют большой объем, они не подходят для сохранения в локальном кэше. Для работы в автономном режиме файл объявления мог бы предусматривать отображение вместо них текстовой справки.
Ниже приводится более сложный файл объявления кэшируемого приложения:
CACHE MANIFEST CACHE: myapp.html myapp.css myapp.js FALLBACK: videos/ offline_help.html NETWORK: cgi/
При запуске кэшированного веб-приложения все его файлы загружаются непосредственно из кэша. Если браузер подключен к сети, он также асинхронно проверит наличие изменений в файле объявления. Если он изменился, будут загружены и установлены в кэш приложения новый файл объявления и все файлы, на которые он ссылается. Обратите внимание, что браузер не проверяет наличие изменений в кэшированных файлах – проверяется только файл объявления. Например, если вы изменили файл сценария на языке JavaScript и вам необходимо, чтобы ваше веб-приложение обновило свой кэш, вам следует обновить файл объявления. Поскольку список файлов, необходимых приложению, при этом не изменяется, проще всего добиться требуемого результата, изменив номер версии:
CACHE MANIFEST # МуАрр версия 1 (изменяйте этот номер, чтобы заставить браузеры повторно # загрузить следующие файлы) MyApp.html МуАрр.js
Аналогично, если потребуется, чтобы веб-приложение удалило себя из кэша приложений, следует удалить файл объявления на сервере, чтобы на запрос этого файла возвращался бы HTTP-ответ 404 «Not Found», и изменить HTML-файл или файлы, удалив из них ссылки на файл объявления.
Обратите внимание, что браузеры проверяют файл объявления и обновляют кэш асинхронно, после (или во время) загрузки копии приложения из кэша. Для простых веб-приложений это означает, что после обновления файла объявления пользователь должен дважды загрузить приложение, чтобы получить обновленную версию: в первый раз будет загружена старая версия из кэша, после чего произойдет обновление файлов в кэше, а во второй раз из кэша будет загружена новая версия.
В ходе обновления кэша браузер запускает множество событий, что дает возможность зарегистрировать их обработчики и извещать пользователя. Например:
applicationCache.onupdateready = function() { var reload = confirm("Доступна новая версия приложения, которая\n" + "будет использована при следующем запуске.\n" + "Хотите ли перезапустить ее сейчас?"); if (reload) location.reload();
Обратите внимание, что этот обработчик событий регистрируется в объекте
ApplicationCache
, на который ссылается свойство
applicationCache
объекта Window
. браузеры,
поддерживающие кэш приложений, определяют это свойство. Помимо события
«updateready», показанного выше, существует еще семь различных событий,
имеющих отношение к кэшу приложений. В примере 20.4 демонстрируются простые
обработчики, которые выводят сообщения, информирующие пользователя о ходе
обновления кэша и о его текущем состоянии.
Пример 20.4. Обработка событий кэша приложений
Эту функцию используют все обработчики событий, реализованные ниже, и выводят с ее помощью сообщения, информирующие о состоянии кэша приложений. // Поскольку все обработчики отображают сообщения таким способом, они // возвращают false, чтобы отменить дальнейшее распространение события // и предотвратить вывод сообщений самим браузером. function status(msg) { // Вывести сообщение в элементе документа с id="statusline" document.getElementById("statusline").innerHTML = msg; console.log(msg); // А также в консоли для отладки } // Каждый раз, когда приложение загружается, браузер проверяет файл объявления. // В начале этого процесса первым всегда генерируется событие "checking". window.applicationCache.onchecking = function() { tatus("Проверка наличия новой версии."); return false; }; // Если файл объявления не изменился и приложение уже имеется в кэше. // генерируется событие "noupdate" и процедура проверки заканчивается. window.applicationCache.onnoupdate = function() { tatus("Версия приложения не изменилась.") return false; }; // Если приложение отсутствует в кэше или если изменился файл объявления, // браузер загрузит и поместит в кэш все, что перечислено в файле объявления. // Событие "downloading" свидетельствует о начале этой процедуры загрузки. window.applicationCache.ondownloading = function() { tatus("Загружается новая версия"); window.progresscount = 0; // Используется в обработчике "progress" ниже return false; }; // В ходе загрузки периодически генерируются события "progress", // обычно после загрузки каждого файла. window.applicationCache.onprogress = function(e) { // Объект события должен соответствовать событию "progress" (подобному тому, // что используется XHR2), что позволяет вычислять процент выполнения, // но на всякий случай мы заведем счетчик количества вызовов. var progress = ""; if (e && e.lengthComputable) // Событие "progress": вычислить процент progress = " " + Math.round(100*e.loaded/e.total) + "%" else // Иначе сообщить кол-во вызовов progress = " (" + ++progresscount + ")" tatus("Загружается новая версия" + progress); return false; }; // Когда приложение впервые загружается в кэш, по окончании загрузки // браузер сгенерирует событие "cached", window.applicationCache.oncached = function() { tatus("Приложение загружено и установлено локально"); return false; }; // Когда обновляется приложение, находящееся в кэше, то по завершении загрузки // браузер сгенерирует событие "updateready". Обратите внимание, что при этом // пользователь по-прежнему будет работать со старой версией приложения. window.applicationCache.onupdateready = function() { tatus("Bbma загружена новая версия приложения. Перезапустите его."); return false; }; // Если браузер выполняется в автономном режиме и файл объявления не может // быть проверен, генерируется событие "error". Это же событие генерируется, // когда некэшированное приложение ссылается на отсутствующий файл объявления. window.applicationCache.onerror = function() { status("Невозможно загрузить файл объявления " + “или сохранить приложение в кэш"'); return false; }; // Если кэшированное приложение ссылается на несуществующий файл объявления, // генерируется событие "obsolete" и приложение удаляется из кэша. // В следующий раз приложение будет целиком загружаться из сети, а не из кэша. window.applicationCache.onobsolete = function() { tatus("3TO приложение больше не кэшируется. " + Перезапустите его, чтобы получить последнюю версию из сети."); return false; };
Всякий раз, когда загружается HTML-файл с атрибутом manifest
,
браузер генерирует событие «checking» и загружает из сети файл объявления. Вслед
за событием «checking» в разных ситуациях генерируются разные события:
Нет обновлений
Есть обновления
Первая загрузка нового приложения
браузер работает в автономном режиме
Файл объявления отсутствует
false
,
чтобы отменить действия,
предусмотренные для событий по умолчанию. Это предотвращает вывод браузером
своих собственных сообщений. (На момент написания этих строк ни один браузер
не выводил никаких сообщений.)В качестве альтернативы обработчикам событий, приложение может также
использовать свойство falseapplicationCache.status
и с его помощью определять состояние кэша. Это свойство может иметь шесть разных
значений: ApplicationCache.UNCACHED (0)
ApplicationCache.UNCACHED (0)
manifest
:
оно не кэшируется.ApplicationCache.IDLE(l)
pplicationCache.CHECKING (2)
ApplicationCache.DOWNLOADING (3)
ApplicationCache.UPDATEREADY (4)
ApplicationCache.OBSOLETE (5)
Объект ApplicationCache
также определяет два метода. Метод
update()
явно запускает процедуру проверки наличия новой версии
приложения. Он заставляет браузер выполнить проверку файла объявления
(и сгенерировать сопутствующие события), как если бы приложение загружалось
в первый раз.
Метод swapCache()
немного сложнее. Напомню, что, когда браузер
загрузит и сохранит в кэше обновленную версию приложения, пользователь
по-прежнему будет работать со старой версией. Он увидит новую версию, только
когда перезагрузит приложение. Если пользователь не сделает этого, старая версия
должна работать вполне корректно. И отметьте, что старая версия может по-прежнему
загружать ресурсы из кэша: она, например, может использовать объект
XMLHttpRequest
для загрузки файлов, и эти файлы будут извлекаться
из старой версии кэша. То есть, вообще говоря, браузер должен сохранять старую
версию кэша, пока пользователь не перезагрузит приложение.
Метод swapCache()
сообщает браузеру, что он может удалить старый
кэш и удовлетворять все последующие запросы из нового кэша. Имейте в виду, что
это не приводит к перезагрузке приложения: HTML-файлы, изображения, сценарии
и другие ресурсы, которые уже были загружены, останутся старыми. Но на все
последующие запросы будут возвращаться ресурсы из нового кэша. Это может вызвать
конфликт между версиями, и в общем случае использовать этот метод
не рекомендуется, если только вы специально не предусмотрели такую возможность.
Представьте, например, приложение, которое не делает ничего и отображает экранную
заставку, пока браузер проверяет файл объявления. Когда приложение получает
событие «noupdate», оно продолжает работу и загружает начальную страницу. Если
приложение получает событие «downloading», оно отображает соответствующий
индикатор хода выполнения операции, пока обновляется кэш. И когда приложение
получает событие «updateready», оно вызывает метод swapCache()
и затем загружает обновленную начальную страницу из свежей версии в кэше.
Обратите внимание, что вызывать метод swapCache()
имеет смысл,
только когда свойство status
имеет значение
ApplicationCache.UPDATEREADY
или ApplicationCache.OBSOLETE
.
(Вызов метода swapCache()
, когда свойство status
имеет
значениеOBSOLETE
сразу же удалит старую версию кэша, и все ресурсы
будут загружаться из сети.) Если вызвать метод swapCache()
, когда
свойство status
имеет любое другое значение, это приведет
к исключению.
Автономными называют веб-приложения, которые устанавливаются в кэш приложений
и поэтому доступны всегда, даже когда браузер работает
в автономном режиме. В простейших случаях – таких как часы или генераторы
фракталов – приложение уже имеет все, что ему необходимо для автономной работы.
Но в более сложных веб-приложениях бывает необходимо выгружать данные на сервер:
даже простейшим игровым приложениям может потребоваться выгружать на сервер
высшие достижения игрока. Приложения, которым необходимо выгружать данные
на сервер, также могут быть автономными, если для сохранения своих данных
они будут использовать объект localStorage
и затем выгружать данные,
как только сеть будет доступна. Реализация синхронизации данных между локальным
хранилищем и сервером может оказаться самым сложным этапом подготовки
веб-приложения к работе в автономном режиме, особенно когда пользователь может
обращаться к нескольким источникам данных. Чтобы выполняться в автономном режиме,
веб-приложение должно иметь возможность выяснить, работает ли оно в автономном
режиме, и определять моменты подключения и отключения от сети. Проверить режим
работы браузера можно с помощью свойства navigator.onLine
.
А определить изменение состояния подключения можно, зарегистрировав обработчики
событий «online» и «offline» в объекте Window
.
Эта глава завершается простым примером автономного веб-приложения,
демонстрирующим использование этих приемов. Приложение называется PermaNote –
это простое приложение управления заметками, которое сохраняет текст, введенный
пользователем, в объекте localStorage
и выгружает его на сервер,
когда соединение с Интернетом будет доступно.¹ Приложение PermaNote дает
пользователю создать единственную заметку и игнорирует проблемы аутентификации
и авторизации – предполагается, что сервер обладает некоторым механизмом,
позволяющим ему отличать одного пользователя от другого без использования
какой-либо страницы входа. Реализация приложения PermaNote состоит из трех файлов.
В примере 20.5 приводится содержимое файла объявления. В нем перечислены
остальные два файла и указывается, что URL «note» не должен кэшироваться:
этот URL-адрес используется для чтения и записи заметки на сервере.
Пример 20.5. permanote.appcache
CACHE MANIFEST # PermaNote v8 permanote.html permanote.js NETWORK: note
В примере 20.6 приводится второй файл приложения PermaNote: это HTML-файл, который реализует пользовательский интерфейс простого редактора. Он отображает элемент <textarea> с панелью инструментов вдоль верхнего края и строкой состояния для сообщений вдоль нижнего края. Обратите внимание, что тег <html> имеет атрибут manifest.
Пример 20.6. permanote.html
<!DOCTYPE HTML> <html manifest="permanote.appcache"> <head> <title>Редактор PermaNote</title> <script src="permanote.js"></script> <style> #editor { width: 100%; height: 250px; } #statusline { width: 100%; } </style> </head> <body> <div id="toolbar"> <button id="savebutton" onclick="save()">Coxpaнить</button> <button onclick="sync()">Синхронизировать</button> <button onclick="applicationCache.update()">0бновить пpилoжeниe</button> </div> <textarea id="editor"></textarea> <div id="statusline"></div> </body>
Наконец, в примере 20.7 приводится сценарий на языке
JavaScript, который обеспечивает работу веб-приложения PermaNote. Он определяет функцию
status()
для отображения сообщений в строке состояния, функцию
save()
– для сохранения текущей версии заметки на сервере и функцию
sync()
– для синхронизации серверной и локальной копии заметки.
Функции save()
и sync()
используют приемы управления
протоколом HTTP, описанные в главе 18. (Интересно
отметить, что функция save()
использует HTTP-метод «PUT» вместо
более типичного для таких случаев метода «POST».)
Помимо этих трех основных функций в примере 20.7 определяются также
обработчики событий. Чтобы обеспечить синхронизацию локальной и серверной копий
заметки, приложению требуется довольно много обработчиков
событий: onload
Пытается загрузить заметку с сервера, если там хранится более новая ее версия,
и по завершении синхронизации разрешает доступ к окну редактора. Функции
save()
и sync()
выполняют HTTP-запросы и регистрируют
обработчик события «onload» в объекте XMLHttpRequest
, чтобы
определить момент, когда выгрузка или загрузка будут завершены.
onbeforeunload
oninput
localStorage
, и запускается таймер. Если пользователь не продолжит
редактирование в течение 5 секунд, заметка будет выгружена на сервер.onoffline
ononline
onupdateready
onnoupdate
А теперь, после краткого обзора логики работы приложения PermaNote, в примере примере 20.7 приводится ее реализация.
Пример 20.7. permanote.js
// Некоторые необходимые переменные var editor, statusline, savebutton, idletimer; // При первой загрузке приложения window.onload = function() { // Инициализировать локальное хранилище, если это первый запуск if (localStorage.note == null) localStorage.note = ""; if (localStorage.lastModified == null) localStorage.lastModified = 0; if (localStorage.lastSaved == null) localStorage.lastSaved = 0; // Отыскать элементы, которые составляют пользовательский интерфейс редактора. // Инициализировать глобальные переменные, editor = document.getElementById("editor"); statusline = document.getElementById("statusline"); savebutton = document.getElementById("savebutton"); editor.value = localStorage.note; // Восстановить сохраненную заметку editor.disabled = true; // Но запретить редактирование до синхр. // При вводе нового текста в элемент textarea editor.addEventListener("input", function (e) { // Сохранить новую заметку в localStorage localStorage.note = editor.value; localStorage.lastModified = Date.now(); // Переустановить таймер ожидания if (idletimer) clearTimeout(idletimer); idletimer = setTimeout(save, 5000); // Разрешить кнопку сохранения savebutton.disabled = false; }, false); // При каждой загрузке приложения пытаться синхронизироваться с сервером sync(); }; // Сохраняет заметку на сервере перед уходом со страницы window.onbeforeunload = function() { if (localStorage.lastModified > localStorage.lastSaved) save(); }; // Сообщить пользователю перед переходом в автономный режим window.onoffline = function() { status("Автономный режим"); } // При подключении к сети выполнить синхронизацию, window.ononline = function() { sync(); }; // Сообщить пользователю, если доступна новая версия приложения. // Здесь можно было бы выполнить перезагрузку принудительно, вызвав // метод location.reload() window.applicationCache.onupdateready = function() { status("Доступна новая версия приложения. " + "Чтобы использовать ее, необходимо перезагрузить приложение "); }; // Также сообщить пользователю, если он использует последнюю версию приложения. window.applicationCache.onnoupdate = function() { status("Bbi используете последнюю версию приложения."); }; // Функция отображения сообщения в строке состояния function status(msg) { statusline.innerHTML = msg; } // Выгружает текст заметки на сервер (если сеть подключена). // Автоматически вызывается через 5 секунд простоя // после изменения текста заметки. function save() { if (idletimer) clearTimeout(idletimer); idletimer = null; if (navigator.onLine) { var xhr = new XMLHttpRequest(); xhr.open("PUT", "/note"); xhr.send(editor.value); xhr.onload = function() { localStorage.lastSaved = Date.now(); savebutton.disabled = true; }; } } // Проверяет наличие новой версии заметки на сервере. Если она отсутствует, // сохраняет текущую версию на сервере. function sync() { if (navigator.onLine) { var xhr = new XMLHttpRequest(); xhr.open("GET", "/note"); xhr.send(); xhr.onload = function() { var remoteModTime = 0; if (xhr.status == 200) { var remoteModTime = xhr.getResponseHeader("Last-Modified"); remoteModTime = new Date(remoteModTime).getTime(); } if (remoteModTime > localStorage.lastModified) { status("Ha сервере найдена более новая заметка."); var useit = confirm("Ha сервере имеется более новая версия\n" + "заметки. Щелкните на кнопке Ok, чтобы\\n"+ "использовать эту версию, или на кнопке\n"+ "Отмена, чтобы продолжить редактировать\n"+ “текущую версию и затереть версию на сервере"); var now = Date.now(); if (useit) { editor.value = localStorage.note = xhr.responseText; localStorage.lastSaved = now; status("Загружена более новая версия."); } else status("Игнорируется более новая версия заметкиe."); localStorage.lastModified = now; } else status("Редактируется последняя версия заметки."); if (localStorage.lastModified > localStorage.lastSaved) { save(); } editor.disabled = false; // Разрешить доступ к редактору editor.focus(); // И поместить в него курсор ввода } } else { // В автономном режиме мы не можем синхронизироваться status("Невозможно синхронизироваться в автономном режиме"); editor.disabled = false; editor.focus(); } }