«Переезжаем» в офлайн: Web Storage, Application Cache и WebSQL

http://dev.opera.com/articles/view/taking-your-web-apps-offline-web-storage-appcache-websql/
  • Перевод
Чтобы делать приложения, которые могут работать в полностью автономном режиме, нам нужно познакомиться со следующими технологиями: HTML5 Application Cache, Web Storage и WebSQL.
Мной уже были опубликованы вводные статьи, касающиеся Web Storage и HTML5 Application Cache. Рекомендую их к прочтению если вы еще не знакомы с основными понятиями. В данной статье будут пересмотрены эти технологии, в том числе и WebSQL, и описаны варианты их совместного эффективного использования. Все эти технологии поддерживаются настольной версией браузера Opera 11.10, Opera Mobile 11, браузерами на движке WebKit (в iOS и Google Android).

Примечание: Все примеры, описанные ниже, доступны в этом архиве. Вы можете скачать архив и смотреть примеры, попутно читая статью (естественно, находясь в офлайне).

Зачем вообще нужен «офлайн-режим»?


В настоящее время веб-страницы представляют собой не просто текст. Интернет получает всё более широкое распространение, и веб-приложения, соответственно, тоже, становясь всё более сложными и совершенными. И наша зависимость от них только растёт. Мы можем видеть всё больше и больше примеров, когда веб-приложения успешно заменяют традиционные настольные приложения, чьим основным преимуществом всегда была их возможность спокойно работать в автономном режиме. Дать такую же возможность веб-приложениям не позволяло отсутствие необходимых технологий и инструментов.
Но всё меняется, когда приходят они...
Но теперь это не проблема! С появлением таких технологий, как HTML5 Application Cache, Web Storage и WebSQL мы наконец получили способ заставить веб-приложения работать в офлайне:
  • Application Cache позволяет держать копию HTML, CSS и других элементов нашего веб-приложения в автономном режиме, которые будут использоваться, когда сеть будет недоступна.
  • Web Storage основывается на механизмах хранения, подобных cookies, представляя собой их более гибкую и более мощную реализацию.
  • WebSQL реализует полномерную SQL-базу данных внутри вашего браузера, которая может хранить копии данных веб-приложения для автономной работы, позволяя пользователям продолжить работу с данными даже при потере соединения с сетью. Данные синхронизируются с сервером при последующем подключении к сети.

Существует множество причин потери соединения с сетью: отключение питания, проблемы сетевым оборудованием, отсутствующий (или слабый) сигнал в случае работы через сотовый телефон. Как разработчики, мы хотим быть уверенными, что даже в подобных ситуациях наши веб-приложения будут работать корректно (по крайней мере, в разумных пределах).

Кэш: грузимся и при отсутствии сети


В общем случае, если мы не в сети, то при загрузке или перезагрузке страниц сайта мы получим ошибку. Первое, что нужно сделать — убедиться, что наши пользователи могу видеть страницы и работать с веб-приложением даже при отсутствующем подключении, т.е. все картинки, CSS, JavaScript и сама HTML-страница должны корректно загружаться в этом режиме.
Достигается это путем использования технологии Application Cache (называемую также AppCache). Чтобы её использовать, нужно для начала объявить manifest-файл, в котором указать имена файлов, которые необходимы приложению для работы офлайн.
Пример файла demo.manifest:
CACHE MANIFEST

CACHE:
logo.png
style.css
script.js
jquery.js
index.htm

Какие бы ссылки ни содержал этот manifest-файл, соответствующее содержимое помещается в кэш, и впоследствии к этому содержимому уже будет доступ. Объявить manifest-файл очень просто — нужно использоваться атрибут manifest элемента <html>:
<html manifest="demo.manifest">

Все файлы, объявленные в этом файле, будут помещены в кэш вашего браузера. Даже если человек не в сети и пытается загрузить страницу, то все ресурсы, указанные в этом файле, загрузятся браузером.
Более подробную информацию об этом вы можете найти в статье про HTML5 Application Cache.

Что насчет данных?


AppCache решает задачу доступности некоторых элементов сайта в офлайне, но, возможно, нам захочется хранить некоторый объём пользовательских данных или, например, его последние поисковые запросы. В другом случае, может быть, вам захочется хранить более структурированные данные. В любом случае, Web Storage и WebSQL будут лучшим решением.

Используем Web Storage


Web Storage прекрасно подходит для хранения небольших объемов информации, нежели огромных таблиц с данным, о чём кратко и поговорим в этой статье, рассмотрим примеры. Более подробную информацию вы можете узнать в отдельной статье, посвящённой Web Storage.
Существует очень много мест, где частое отключение электричества — обычное явление («Чубайс, привет! :)», — от переводчика). Пользователю приходится сидеть и ждать, когда он сможет продолжить свою работу в Интернете, пока не включат электричество. А представьте, если кто-то оказался в подобной ситуации, заполняя многостраничную форму на каком-либо сайте, набирая большую статью для блога или важного электронного письма. Во время отключения питания (или сядут аккумуляторы) пользователь потеряет все эти данные. Не будет ли лучше, если после входа в сеть, ему будут доступны все несохраненные им записи, с которыми он продолжит работу?
Давайте посмотрим, что можно сделать на странице, содержащей обычное текстовое поле <textarea>? Страница должна сохранять всё, что мы набираем, в локальное хранилище каждые несколько секунд, а в случае её перезагрузки или закрытия, страница должна загружать последний сохранённый в поле текст.
Допустим, наша страница содержит поле <textarea> с id «draft»:
...
<textarea id="draft" rows="10" cols="30"></textarea>
...

Напишем простую функцию, которая будет сохранять в локальное хранилище содержимое <textarea>:
function saveMessage(){
	var message = document.getElementById("draft");
	localStorage.setItem("message", message.value)
}

Установим интервал сохранения в полсекунды:
setInterval(saveMessage, 500);

Примечание: здесь мы использовали setInterval() для простоты, чтобы сохранять сообщение в локальное хранилище каждые полсекунды. (Вы могли бы улучшить эту процедуру, например, сохраняя содержимое текстового поля только в том случае, если пользователь в него что-то ввёл).

Также нужно убедиться, что каждый раз, когда страница открывается или перезагружается, в текстовое поле загружалось последнее сохранённое содержимое из локального хранилища (localStorage):
window.addEventListener("DOMContentLoaded", loadMessage, false);
function loadMessage() {
	var textbox = document.getElementById("draft");
	var message = localStorage.getItem("message");
	if (!message) {
		textbox.value = "";
	}else {
		textbox.value = message;
	}
}

Посмотрите пример работы с Web Storage. Это вообще шикар
ная вещь, если вам нужно сохранять локально небольшие кусочки информации.

Работаем в автономном режиме


Для перехода в автономный режим нужно его, собственно, активировать (в браузере Opera: «Меню» → «Настройки» → «Работать автономно» либо «Файл» → «Работать автономно»). Свойство navigator.onLine имеет значение false в случае, если браузер находится в автономном режиме, в противном случае оно имеет значение true. Однако, во многих случаях лучше было бы использовать события. Когда пользователь переключается в автономный режим, срабатывает событие offline, когда переключается обратно — соответственно online. Можно воспользоваться этим для вывода небольшого сообщения о переходе в автономный режим.
Получится что-то типа этого:
...
window.addEventListener( "offline", function(){showWarningDiv("on")}, false);
window.addEventListener( "online", function(){showWarningDiv("off")}, false);
...
function showWarningDiv(status){
var warningdiv = document.getElementById("warning");
if (status == "on"){
	warningdiv.innerHTML = "<p style=\"padding:3px;\">Right now you are in offline mode. This message is saved and will be sent to the server the next time you are online.</p>";
	} else {
		warningdiv.innerHTML = "";
	}
}

Примечание: в настоящее время поддержка автономного режима реализована только в Opera и Firefox.

Имеет смысле убедиться, что формы не пытаются отправить данные в то время, когда пользователь работает в автономном режиме. Чтоб проверить это, можем сделать так:
...
window.addEventListener( "submit", submitForm, false);
...
function submitForm(){
	saveMessage();
	if (!navigator.onLine){
		return false;
	}
}

При отправке данных формы срабатывает событие submit, которое вызывает функцию submitForm(). Эта функция сначала сохранит сообщение в локальном хранилище, затем, если пользователь работает в автономном режиме, данные никуда не отправятся.
Вы можете усовершенствовать этот пример, чтобы он сохранял копию на сервер каждые несколько секунд, чтобы там она была доступна на тот случай, если пользователь случайно удалит данные у себя. Это особенно важно в тех случаях работы, например, с конфиденциальной информацией: вы, скажем, хотите, чтобы информация о вашей кредитной карте хранилась только у вас — в локальном хранилище.
Посмотрите также более продвинутый пример, который использует для хранение информации sessionStorage. Если вы не закрываете страницу (даже в случае её перезагрузки) текст, введённый в текстовое поле, там и останется. Страница также будет отправлять содержимое этого поля на сервер каждые несколько секунд и, соответственно, обновлять время последнего сохранения. Данный подход может использоваться в блоговых движках и сервисах электронной почты для периодического сохранения «черновиков», что позволит продолжить работу в случае проблем с подключением.

WebSQL: еще «глужбе» в офлайн


Web Storage прекрасно подходит для хранения небольших объемов информации, а что если мы захотим хранить целую базу данных? Как насчёт того, чтобы веб-приложение могло делать различные запросы к базе данных, поиск по ней?
Здесь Web Storage уже не позволит развернуться — нужно что-то более надёжное. А именно — WebSQL. WebSQL представляет собой локальную SQLite базу данных, в которой вы можете хранить свои данные, используя комбинацию Javascript и SQL.

Работа с WebSQL-базами данных

Перво-наперво нужно убедиться, поддерживает ли браузер WebSQL? Сделать это можно через свойство window.openDatabase:
if (window.openDatabase){
	//rest of your code
} else{
	alert("It seems your browser does not have support for WebSQL. Please use a browser which does, otherwise parts of this application may not run as intended."); //or any other similar message
}

Создание и открытие базы данных

Создать и открыть базу данных можно, используя команду openDatabase, так:
var db = openDatabase("food_db", "1.0", "Web SQL Storage Demo Database", 1*1024*1024); // creates a database called 'food_db' with version number 1.0, description as 'Web SQL Demo Database' and a size of 1MB.

Мы только что создали базу данных food_db «версии» 1.0, с описанием «Web SQL Storage Demo Database», размером 1 МБ. Переменная db представляет собой указатель на объект базы данных, который мы и будем в дальнейшем использовать.
Примечание: размер базы данных устанавливается в байтах. Поэтому мы определили размер в формате 1*1024*1024, что составляет 1 МБ. Если нужно установить размер, например, 4 МБ, следует указывать величину 4*1024*1024 соответственно.

Работа с базой данных

Мы создали и открыли базу данных. Теперь можно выполнять различные операции над ней, используя SQL-команды. Операции мы будем производить, вызывая функцию transaction() объекта базы данных (в нашем случае это db). Её вызов возвращает объект в качестве указателя, над которым мы будем выполнять различные команды, используя executeSQL(). Синтаксис этой команды следующий:
executeSql(sqlStatement, arguments, callback, errorCallback);

Из параметров только sqlStatement является обязательным, остальные — необязательные.
Так, например, если мы хотим создать таблицу, нужно будет написать следующее:
...
db.transaction(
function(t){ // This is the callback with "t" as the transaction object
	t.executeSql("CREATE TABLE IF NOT EXISTS cal_list (food_name TEXT PRIMARY KEY, calories REAL, servings TEXT)");
	}
);
...

Этот код создаст таблицу cal_list (если её не существовало) с полями food_name, calories и servings.

Добавление записей в таблицу

Выполнять запросы на добавление записей в таблицу — простая задача для WebSQL. Рассмотрим пример:
var food_name = "pizza";
var amount_of_calories = 320;
var serving_size = "one slice";

db.transaction(
function(t){
	t.executeSql("INSERT INTO cal_list VALUES (?, ?, ?)", [food_name, amount_of_calories, serving_size]);
	}
);

Первый знак вопроса экранирует параметр food_name, второй — amount_of_calories, а третий — serving_size. Этот код добавляет запись в таблицу cal_list со значениями: pizza, 320 и one slice соответствующие столбцы.
Выполним другой запрос — на получение данных:
var min_cal_amount = 300;
...
t.executeSql("SELECT * FROM cal_list WHERE calories > ?", [min_cal_amount]);

Этот код выполнит запрос на выборку всех строк со значением calories больше 300: знак вопроса экранирует переменную min_cal_amount.

Обработка результатов запроса

Ну, вот, мы создали базу данных с таблицами, в которые записали данные, теперь мы хотим сделать запрос и вывести полученные результаты. Обычно, мы получаем кучу результатов для одного SQL-запроса и нам нужно как-то обработать эти результаты, чтобы вывести их в виде таблицы или каком-то другом структурированном виде на странице. Третий параметр функции executeSQL() определяет успешность выполнения запроса. Ниже приведён пример обработки результатов:
var list = document.getElementById("thelist");
var food;
var min_cal_amount = 400;
var serving_size;

db.transaction(
function(t){
t.executeSql("SELECT food_name AS food, calories AS amount_of_calories, servings as serving_size FROM cal_list where calories > ?" ,[min_cal_amount], function(t,r){
	for (var i=0; i < r.rows.length; i++){
		food = r.rows.item(i).food;
		amount_of_calories = r.rows.item(i).amount_of_calories;
		serving_size = r.rows.item(i).serving_size;			
		list.innerHTML +="<li>"+food+" has "+amount_of_calories+" KCAL worth of calories.</li>";
		}
	}, 
	function(t,e) {alert(e.message);})
}
);

Сначала мы определяем количество элементов в результате запроса через r.rows.length и пробегаемся от 0 до этого значения. Каждый элемент доступен по r.rows.item(i), где i — номер строки. Названия столбцов можно получить так же. Для получения получения значения столбца food используем r.rows.item(i).food и так далее для других столбцов.

Используем всё и сразу


Не исключено, что вам захочется использовать Web Storage, Application Cache и WebSQL вместе. Это возможно, всё зависит от задач, которые нужно решить. Например, если вы хотите хранить всего лишь некоторые пользовательские настройки, будет излишеством использовать WebSQL. Для подобных целей лучше подойдёт Web Storage.
Однако, если у вас большой объём данных, и нужно их как-то обрабатывать, то логичнее было бы использовать WebSQL.
Посетите страницу с примером про калории, на которой используются вместе все три технологии: база данных хранится в Web Storage, так что вы сможете искать даже если закроете или перезагрузите страницу, AppCache и WebSQL используются для предоставления возможности работать в автономном режиме.
Примечание: вы можете самостоятельно получить информацию о том, какие базы данных используются в Opera и управлять ими, перейдя по ссылке opera:webdatabases. А получить информацию о доменах, которые хранят свои данные в браузере, используя Web Storage, и управлять ими, перейдя по ссылке opera:webstorage.

Подождите… разве спецификации WebSQL утверждены?


Ещё не окончательно. Но вы уже можете применять эту технологию на приведённом списке браузеров и устройств. Чего нельзя сказать о IndexedDB, у которой нет такой кросс-платформенной поддержки. Осталось только дождаться утверждения спецификации, что и произойдёт в скором времени.
Важно также отметить, что некоторые настольные версии браузеров, такие как Firefox и Internet Explorer, не будут поддерживать WebSQL, они будут работать с IndexedDB. В этом случае, первое, где можно применить WebSQL — автономное хранилище данных для смартфонов: поддерживаются браузеры Opera Mobile 11, а также браузеры на движке WebKit для платфом Android и iOS.
Хорошая новость о WebSQL заключается в том, что он предлагает удобный инструмент для создания автономной базы данных в браузере. Он лёгок в изучении и применении и позволяет быстро разработать автономное веб-приложение, которое будет сразу работать в нескольких браузерах.

Заключение


Веб-приложения теперь получили возможность работать в полностью автономном режиме, используя такие технологии, как Application Cache, Web Storage и WebSQL базы данных. Application Cache нам нужен для кэширования файлов, чтоб исползовать их в автономном режиме; Web Storage — для хранения небольших объёмов информации, ну а WebSQL будет удобным инструментом для работы с большими объёмами данных. Разработчики в зависимости от потребностей могут использовать различное сочетание этих технологий, для создания автономных приложений.


Технология перспективная. И, как обычно, будут гонки… WebSQL и IndexedDB, упоминания о которой уже были на хабре. Пусть они сделают мир лучше :)

p.s. Все замечания по переводу, как обычно, принимаются в личку.
p.p.s. где-то я, видимо, плавно переехал с «web storage» на «локальное хранилище». Что лучше оставить-то? ;)
Поделиться публикацией
Комментарии 27
    +3
    >>Подождите… разве спецификации WebSQL утверждены?
    Так, вроде, не хотят они её уже поддерживать:
    Beware. This specification is no longer in active maintenance and the Web Applications Working Group does not intend to maintain it further.
      +2
      В оригинале «Wait… isn’t the WebSQL specification in impasse?» Имеется ввиду, «тупиковый путь развития». Единственный плюс этой спецификации то, что её можно использовать на мобильных устройствах.
      +3
      Еще можно упомянуть об ограничении cache manifest в ios — 50Мб, тоже самое с WebSQL то же ограничение в 50Мб, Хром также имеет ограничение в 5Мб для WebSQL (вроде как у extensions через manifest можно указать флаг и будет доступно больше места). Еще одна из странностей работы с WebSQL это то, что safari прерывает контекст после того, как место кончилось и показывается диалог с вопросом об увеличении места для БД
        +2
        Более того, iPhone кэширует файлы объёмом не более 25Kb (по поводу картинок не уверен, но css, js, html это точно касается).
          0
          Подскажите пожалуйста, а что за флаг для Хрома для увеличения объёма базы для расширений? А то 5 Мб маловато…
      • НЛО прилетело и опубликовало эту надпись здесь
        • НЛО прилетело и опубликовало эту надпись здесь
            0
            Это только localStorage.

            Webkit SQLite is not supported
          0
          WebSQL пугает в плане безопасности, получается клиенту придется отдавать часть данных, которые ему и не надо иметь.
          • НЛО прилетело и опубликовало эту надпись здесь
              0
              возможно и так, но в технологии хотелось бы иметь некоторые инструменты по обеспечению безопасности
                +3
                По обеспечению безопасности чего?
                Когда клиент запоминает в своем браузере пароль от Вашего сервиса — тоже небезопасно. Ну вот Вам и решать что ему можно запоминать, а что нет. Обеспечение безопасности — это действительно проблема проекта. Не хотите что бы сохранял приватную информацию — не сохраняйте. Не хотите светить глобальными ID — создайте приватные ключи привязанные на пользователя. Все в ваших редакторах, и что бы это было безопасно — нужно писать безопасно.
            +3
            так же, как вариант, если структура хранения данных не сильно «перемудренная», то можно не заморачиваться на WebSQL, а сохранять эту структуру в виде JSON-объекта прямо в localStorage.

            localStorage["obj"]="{\"main\":[1,2,3,\"четыре\"]}";

            а потом, просто

            OBJ=JSON.parse(localStorage["obj"]);
            OBJ["main"][0]; // 1
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                Только строкой. Для этого WebSQL и прочие штуки и придумывают, что localStorage только строки умеют.
                +2
                И использовать JSON.stringify(obj) и JSON.parse(str). :)
                  0
                  Очень большие накладные расходы. Я так сделал в своём расширении для быстрой разработки, так хранится очень мало сообщений… вот сейчас перевожу на Sqlite.
                  +2
                  Я как-то переводил статью про кеш приложений. Так же переводил небольшую заметку про localStorage и sessionStorage:
                  Идём в оффлайн с кешем приложения

                  HTML5: Хранение данных на клиенте
                    +2
                    Спасибо.
                    Правильно ли я понимаю, что кэш не обновляется, даже если пользователь снова в онлайне открывает страницы?
                    То есть, нужно при обновлении ресурсов каждый раз генерить манифест а для файерфокса приделывать еще один костыль?
                    Ну ладно, генерить манифест можно автоматически при обращении к нему браузером (хоть и не большая, но лишняя нагрузка).
                    Да и десятки килобайт дополнительного трафика для манифеста не очень то хорошо…
                      0
                      > Ну ладно, генерить манифест можно автоматически при обращении к нему браузером (хоть и не большая, но лишняя нагрузка).
                      Ну манифест можно не генерить, а давать ссылку на разные манифесты здесь, если нужно:
                        +1
                        Прошу прощения, парсер поел:
                        <html manifest="demo.manifest">
                        +2
                        Прошу прощения, что долго не отвечал. Я просто спал :)

                        Как я понял (я не пробовал просто на деле), кеш обновится только тогда, когда обновится файл манифеста.
                        Насчет ФФ можно предположить, что с 4й версией могло все заработать.
                      +2
                      К слову, недавно в WHATWG поменяли рекомендованное расширение файла манифеста с
                      *.manifest
                      на
                      *.appcache
                      www.whatwg.org/specs/web-apps/current-work/multipage/offline.html
                        0
                        Никак не удалось в Хроме поработать с синхронным доступом к базе. Посоветуете что-нибудь?

                        И также размер создаваемой базы совершенно не учитывается Хромом. У него ограничение 5 Мб на домен и это нигде почему-то нельзя сменить. Пишут про какой-то запрос подтверждения пользователя, но такого не возникает.
                          0
                          Как в Опере добраться до баз данных для расширений?

                          opera:webdatabases показывает только для сайтов.

                          И похоже, я нашёл настройки для увеличения sqlite в Опере!

                          вот ссылка opera:config#PersistentStorage

                          Увеличил — работает!

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое