Статьи

Реальное хранение данных в автономном режиме

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


HTML5 представил несколько вариантов автономного хранения. AppCache, localStorage, sessionStorage и IndexedDB. Каждый из них подходит для конкретного использования. Например, AppCache может улучшить ваше приложение или позволить некоторым его частям работать без подключения к Интернету. Ниже я опишу все эти варианты и покажу несколько фрагментов кода с примером использования.


Если часть вашего приложения (или всего приложения) может использоваться без доступа к серверу, вы можете использовать AppCache, чтобы позволить вашим пользователям выполнять некоторые действия в автономном режиме. Все, что вам нужно сделать, это создать файл манифеста, в котором вы бы указали, что следует кэшировать, а что не следует. Вы также можете указать замены для файлов, которые требуют онлайн-доступа.

Манифест AppCache — это просто текстовый файл с .appcache (рекомендуется). Он начинается с CACHE MANIFEST и состоит из трех частей:

  • CACHE — указанные вами файлы будут загружаться и кэшироваться при первом входе пользователя на ваш сайт.
  • NETWORK — здесь вы видите список файлов, для работы которых требуется подключение к Интернету, они никогда не будут кэшироваться
  • FALLBACK — эти файлы будут использоваться при доступе к онлайн-ресурсу без подключения

Сначала вы должны определить файл манифеста на вашей странице:

1
2
3
4
<!DOCTYPE html>
<html manifest=»manifest.appcache»>
</html>

Вы должны помнить, что файл манифеста должен обслуживаться MIME-типом text/cache-manifest , иначе он не будет проанализирован браузером. Далее вам нужно создать файл, который вы определили ранее. Для целей этого примера давайте представим, что у вас есть информационный веб-сайт с возможностью связаться с вами и написать комментарии. Вы можете разрешить пользователям доступ к статическим частям сайта и заменять контактную форму и комментарии другой информацией, чтобы форма и комментарии были недоступны в автономном режиме.

Сначала давайте определим некоторый статический контент:

01
02
03
04
05
06
07
08
09
10
11
CACHE MANIFEST
 
CACHE:
/about.html
/portfolio.html
/portfolio_gallery/image_1.jpg
/portfolio_gallery/image_2.jpg
/info.html
/style.css
/main.js
/jquery.min.js

Примечание: одна плохая вещь в манифесте состоит в том, что вы не можете использовать подстановочный знак, чтобы указать, например, что вся папка должна быть кэширована, вы можете использовать только подстановочный знак в разделе СЕТЬ, чтобы указать, что все ресурсы не перечислены в манифесте не должно быть кэшировано.

Вам не нужно кэшировать страницу, на которой определен манифест, он будет кэширован автоматически. Теперь мы определим запасные варианты для разделов контактов и комментариев:

1
2
3
FALLBACK:
/contact.html /offline.html
/comments.html /offline.html

Наконец, мы можем использовать * для остановки кэширования всех других ресурсов:

1
2
NETWORK:
*

Конечный результат должен выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
CACHE MANIFEST
 
CACHE:
/about.html
/portfolio.html
/portfolio_gallery/image_1.jpg
/portfolio_gallery/image_2.jpg
/info.html
/style.css
/main.js
/jquery.min.js
 
FALLBACK:
/contact.html /offline.html
/comments.html /offline.html
 
NETWORK:
*

Важно помнить, что ваши ресурсы будут кэшироваться только один раз. Они не будут кэшироваться при обновлении, а только при изменении манифеста. Хорошая практика — вводить в комментарии номер версии и увеличивать его при каждом обновлении файла:

1
2
3
4
5
6
CACHE MANIFEST
 
# version 1
 
CACHE:

Эти два варианта хранения будут полезны, если вы хотите что-то сохранить в своем коде JavaScript. Первый позволяет сохранить значение без срока годности. Это значение будет доступно для любой страницы с тем же доменом и протоколом. Например, вы можете сохранить настройки приложения пользователя на своем компьютере, чтобы он / она мог настроить их для компьютера, который они используют в настоящее время. Второй будет хранить значения, пока пользователь не закроет окно браузера (или вкладку). Кроме того, данные не передаются между окнами, даже если пользователь открывает несколько страниц вашего приложения.

Стоит помнить, что вы можете хранить только базовые типы в localStorage / sessionStorage . Так что будут работать только строки и числа. Все остальное будет сохранено с использованием метода toString() . Если вам нужно сохранить объект, вы должны сделать это с помощью JSON.stringify (если этот объект является классом, вы можете просто переопределить метод toString() по умолчанию, чтобы сделать это для вас автоматически).

Давайте рассмотрим предыдущий пример. В разделах комментариев и контактов сайта мы можем сохранить то, что набрал пользователь, поэтому, если он / она случайно закроет окно, значения все равно останутся для него, чтобы продолжить позже. Это будет действительно простой кусок кода, использующий jQuery (поскольку мы будем использовать идентификатор поля для его идентификации позже, каждое из полей формы должно иметь атрибут id)

1
2
3
4
5
6
$(‘#comments-input, .contact-field’).on(‘keyup’, function () {
    // let’s check if localStorage is supported
    if (window.localStorage) {
        localStorage.setItem($(this).attr(‘id’), $(this).val());
    }
});

Когда форма комментария / контакта отправлена, мы должны очистить значение. Давайте сделаем это, обработав событие submit (вот самый простой пример):

1
2
3
4
5
6
7
$(‘#comments-form, #contact-form’).on(‘submit’, function () {
    // get all of the fields we saved
    $(‘#comments-input, .contact-field’).each(function () {
        // get field’s id and remove it from local storage
        localStorage.removeItem($(this).attr(‘id’));
    });
});

И, наконец, при загрузке страницы мы восстановим значения:

1
2
3
4
5
6
7
8
9
// get all of the fields we saved
$(‘#comments-input, .contact-field’).each(function () {
    // get field’s id and get it’s value from local storage
    var val = localStorage.getItem($(this).attr(‘id’));
    // if the value exists, set it
    if (val) {
        $(this).val(val);
    }
});

Это самый интересный вариант хранения на мой взгляд. Это позволяет хранить довольно большие объемы проиндексированных данных в браузере пользователя. Таким образом, вы можете сохранять сложные объекты, большие документы и т. Д. И получать к ним доступ пользователя без подключения к Интернету. Эта функция полезна для всех видов приложений — если вы создаете почтовый клиент, вы можете сохранить электронные письма пользователя, чтобы он мог получить к ним доступ позже, в фотоальбоме можно сохранять фотографии для автономного использования или сохранять GPS-навигацию. конкретный маршрут и список можно продолжить.

IndexedDB — это объектно-ориентированная база данных. Это означает, что нет таблиц и SQL. Вы храните пары данных ключ-значение, где ключами являются строки, числа, даты или массивы, а значения могут быть сложными объектами. Сама база данных состоит из магазинов. Магазин похож на таблицу в реляционной базе данных. Каждое значение должно иметь свой собственный ключ. Ключ может быть сгенерирован автоматически, вы можете указать его при добавлении значения, или это может быть какое-то поле в значении (которое также может быть сгенерировано автоматически). Если вы решите использовать поле в качестве ключа, вы сможете добавлять только объекты JavaScript в хранилище (потому что простые числа или строки не могут иметь никаких свойств, как объекты).

Для этого примера давайте представим, что у нас есть музыкальный альбом. Теперь я не собираюсь рассказывать о создании приложения для музыкального альбома. Я расскажу только о части приложения IndexedDB, но само приложение для создания музыкальных альбомов включено в эту статью для загрузки, поэтому вы можете посмотреть полный исходный код. Сначала мы должны открыть базу данных и создать магазин:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// check if the indexedDB is supported
if (!window.indexedDB) {
    throw ‘IndexedDB is not supported!’;
}
 
// variable which will hold the database connection
var db;
 
// open the database
// first argument is database’s name, second is it’s version (I will talk about versions in a while)
var request = indexedDB.open(‘album’, 1);
 
request.onerror = function (e) {
    console.log(e);
};
 
// this will fire when the version of the database changes
request.onupgradeneeded = function (e) {
    // e.target.result holds the connection to database
    db = e.target.result;
     
    // create a store to hold the data
    // first argument is the store’s name, second is for options
    // here we specify the field that will serve as the key and also enable the automatic generation of keys with autoIncrement
    var objectStore = db.createObjectStore(‘cds’, { keyPath: ‘id’, autoIncrement: true });
     
    // create an index to search cds by title
    // first argument is the index’s name, second is the field in the value
    // in the last argument we specify other options, here we only state that the index is unique, because there can be only one album with specific title
    objectStore.createIndex(‘title’, ‘title’, { unique: true });
     
    // create an index to search cds by band
    // this one is not unique, since one band can have several albums
    objectStore.createIndex(‘band’, ‘band’, { unique: false });
};

Приведенный выше код довольно прост. Вы, вероятно, заметили версию и событие onupgradeneeded . Это событие вызывается, когда база данных открывается с новой версией. Поскольку база данных еще не существует, событие запускается, и мы можем создать магазин, который нам нужен. Позже мы добавим два индекса, один для поиска по названию и один для поиска по группе. Теперь давайте посмотрим процесс добавления и удаления альбомов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// adding
$(‘#add-album’).on(‘click’, function () {
    // create the transaction
    // first argument is a list of stores that will be used, second specifies the flag
    // since we want to add something we need write access, so we use readwrite flag
    var transaction = db.transaction([ ‘cds’ ], ‘readwrite’);
    transaction.onerror = function (e) {
        console.log(e);
    };
    var value = { … };
    // add the album to the store
    var request = transaction.objectStore(‘cds’).add(value);
    request.onsuccess = function (e) {
        // add the album to the UI, e.target.result is a key of the item that was added
    };
});
 
// removing
$(‘.remove-album’).on(‘click’, function () {
    var transaction = db.transaction([ ‘cds’ ], ‘readwrite’);
    var request = transaction.objectStore(‘cds’).delete(/* some id got from DOM, converted to integer */);
    request.onsuccess = function () {
        // remove the album from UI
    }
});

Довольно просто. Необходимо помнить, что все операции с базой данных основаны на транзакциях, чтобы сохранить согласованность данных. Теперь осталось только отобразить альбомы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
request.onsuccess = function (e) {
    if (!db) db = e.target.result;
     
    var transaction = db.transaction([ ‘cds’ ]);
    var store = transaction.objectStore(‘cds’);
    // open a cursor, which will get all the items from database
    store.openCursor().onsuccess = function (e) {
        var cursor = e.target.result;
        if (cursor) {
            var value = cursor.value;
            $(‘#albums-list tbody’).append(‘<tr><td>’+ value.title +'</td><td>’+ value.band +'</td><td>’+ value.genre +'</td><td>’+ value.year +'</td></tr>’);
 
            // move to the next item in the cursor
            cursor.continue();
        }
    };
}

Это тоже не очень сложно. Как вы можете видеть, используя IndexedDB, вы можете действительно легко хранить сложные значения. Вы также можете искать значения по индексу, например так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function getAlbumByBand(band) {
    var transaction = db.transaction([ ‘cds’ ]);
    var store = transaction.objectStore(‘cds’);
    var index = store.index(‘band’);
    // open a cursor to get only albums with specified band
    // notice the argument passed to openCursor()
    index.openCursor(IDBKeyRange.only(band)).onsuccess = function (e) {
        var cursor = e.target.result;
        if (cursor) {
            // render the album
            // move to the next item in the cursor
            cursor.continue();
        }
    });
}

Вы можете использовать курсор с индексом так же, как мы делали с магазином. Поскольку может быть несколько записей с одинаковым значением индекса (если оно не уникально), нам нужно использовать IDBKeyRange . Это отфильтрует результаты в зависимости от того, какую функцию вы используете. Здесь мы хотим получать элементы только по предоставленной полосе, поэтому мы использовали метод only() . Вы также можете использовать lowerBound() , upperBound() и bound . Имена методов довольно понятны.


Таким образом, включение автономного доступа для ваших пользователей не так сложно, как может показаться. Я надеюсь, что после прочтения этой статьи вы сделаете свои приложения более удобными для пользователя, предоставив им доступ к некоторым его частям (или, возможно, даже ко всем) без подключения к Интернету. Вы можете загрузить образец приложения и поэкспериментировать с ним, добавив дополнительные параметры или включив некоторые его части на свой веб-сайт.