Работа с IndexedDB

11 января 2018

Одним из наиболее интересных событий в веб-стандартах в последнее время является спецификация индексированной базы данных (IndexedDB для краткости). В свободное время вы можете прочитать спецификацию самостоятельно. В этом уроке я расскажу об этой функции и, надеюсь, дам вам вдохновение использовать эту мощную функцию самостоятельно.


Обзор

В качестве спецификации IndexedDB в настоящее время является Рекомендацией кандидата.

В двух словах IndexedDB предоставляет вам возможность хранить большие объемы данных в браузере вашего пользователя. Любое приложение, которое должно отправлять много данных по кабелю, может в значительной степени выиграть от возможности хранить эти данные на клиенте. Конечно, хранение является лишь частью уравнения. IndexedDB также предоставляет мощный индексированный API поиска для получения необходимых данных.

Вы можете удивиться, как IndexedDB отличается от других механизмов хранения?

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

Локальное хранилище также очень хорошо поддерживается, но ограничено с точки зрения общего объема хранения, которое вы можете использовать. Локальное хранилище не предоставляет истинный API поиска, поскольку данные извлекаются только с помощью значений ключа. Локальное хранилище отлично подходит для «конкретных» вещей, которые вы, возможно, захотите сохранить, например, предпочтений, тогда как IndexedDB лучше подходит для данных Ad Hoc (как база данных).

Прежде чем идти дальше, давайте честно поговорим о состоянии IndexedDB с точки зрения поддержки браузера. В качестве спецификации IndexedDB в настоящее время является Рекомендацией кандидата. На данный момент люди, стоящие за спецификацией, довольны этим, но теперь ищут отзывы от сообщества разработчиков. Спецификация может измениться между настоящим и заключительным этапами, Рекомендацией W3C. В общем, браузеры, которые поддерживают IndexedDB, теперь все делают достаточно последовательно, но разработчики должны быть готовы иметь дело с префиксами и принимать к сведению обновления в будущем.

Что касается тех браузеров, которые поддерживают IndexedDB, у вас есть дилемма. Поддержка довольно хороша для рабочего стола, но практически не существует для мобильных устройств. Посмотрим, что отличный сайт CanIUse.com говорит:

Chrome для Android поддерживает эту функцию, но очень немногие люди в настоящее время используют этот браузер на устройствах Android. Не означает ли использование мобильной поддержки, что вы не должны ее использовать? Конечно нет! Надеемся, что все наши читатели знакомы с концепцией прогрессивного совершенствования. Такие функции, как IndexedDB, могут быть добавлены в ваше приложение таким образом, чтобы они не ломались в не поддерживаемых браузерах. Вы можете использовать библиотеки обертки для переключения на WebSQL на мобильных устройствах или просто пропустить хранение данных локально на мобильных клиентах. Лично я считаю, что способность кэшировать большие блоки данных на клиенте достаточно важна для использования даже без поддержки мобильных устройств.


Давайте начнем

Мы рассмотрели спецификацию и поддержку, теперь давайте посмотрим на использование этой функции. Самое первое, что мы должны сделать, это проверить поддержку IndexedDB. Хотя есть инструменты, которые предоставляют общие способы проверки функций браузера, мы можем сделать это намного проще, так как мы просто проверяем одну вещь.

document.addEventListener("DOMContentLoaded", function(){
    if("indexedDB" in window) {
        console.log("YES!!! I CAN DO IT!!! WOOT!!!");
    } else {
        console.log("I has a sad.");
    }
},false);

Фрагмент кода выше (доступный в файле test1.html при загрузке zip-файла, прикрепленного к этой статье) использует событие DOMContentLoaded для ожидания загрузки страницы. (Хорошо, это очевидно, но я понимаю, что это может быть не знакомо людям, которые использовали только jQuery.) Затем я просто вижу, существует ли indexedDB в объекте window, и если это так, мы должны идти. Это самый простой пример, но, как правило, мы, вероятно, захотим его сохранить, поэтому мы узнаем позже, если мы сможем использовать эту функцию. Вот несколько более продвинутый пример (test2.html).

var idbSupported = false;
document.addEventListener("DOMContentLoaded", function(){
    if("indexedDB" in window) {
        idbSupported = true;
    }
},false);

Все, что я сделал, создано глобальная переменная idbSupported, которая может использоваться как флаг, чтобы увидеть, может ли текущий браузер использовать IndexedDB.


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

IndexedDB, как вы можете себе представить, использует базы данных. Чтобы быть ясным, это не реализация SQL Server. Эта база данных является локальной для браузера и доступна только пользователю. Базы IndexedDB соответствуют тем же правилам, что и файлы cookie и локальное хранилище. База данных уникальна для домена, из которого она была загружена. Так, например, база данных под названием «Foo», созданная на foo.com, не будет конфликтовать с базой данных с таким же именем на goo.com. Он не только не будет конфликтовать, но и не будет доступен другим доменам. Вы можете хранить данные для своего веб-сайта, зная, что другой веб-сайт не сможет получить к нему доступ.

Открытие базы данных осуществляется с помощью команды open. При базовом использовании вы предоставляете имя и версию. Версия очень важна по причинам, которые я расскажу позже. Вот простой пример:

var openRequest = indexedDB.open("test",1);

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

ошибка успеха, обновленная блокировка

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

Как правило, должно произойти то, что при первом попадании на ваш сайт будет активировано событие с обновлением. После этого - просто обработчик успеха. Давайте посмотрим на простой пример (test3.html).

var idbSupported = false;
var db;
document.addEventListener("DOMContentLoaded", function(){
    if("indexedDB" in window) {
        idbSupported = true;
    }
    if(idbSupported) {
        var openRequest = indexedDB.open("test",1);
        openRequest.onupgradeneeded = function(e) {
            console.log("Upgrading...");
        }
        openRequest.onsuccess = function(e) {
            console.log("Success!");
            db = e.target.result;
        }
        openRequest.onerror = function(e) {
            console.log("Error");
            console.dir(e);
        }
    }
},false);

Еще раз мы проверяем, действительно ли IndexedDB поддерживается, и если это так, мы открываем базу данных. Здесь мы рассмотрели три события: событие, необходимое для обновления, событие успеха и событие ошибки. На данный момент сосредоточьтесь на событии успеха. Событие передается обработчиком через target.result. Мы скопировали это в глобальную переменную db. Это то, что мы будем использовать позже, чтобы фактически добавить данные. Если вы запустите это в своем браузере (в одном, который поддерживает IndexedDB, конечно!), Вы должны увидеть сообщение об обновлении и успехе в консоли при первом запуске скрипта. Второй и т. Д., Когда вы запускаете скрипт, вы должны видеть сообщение об успешном завершении.


Объектные магазины

До сих пор мы проверили поддержку IndexedDB, подтвердили его и открыли подключение к базе данных. Теперь нам нужно место для хранения данных. IndexedDB имеет концепцию «Объектные магазины». Вы можете думать об этом как о типичной таблице базы данных. (Это гораздо более свободно, чем обычная таблица базы данных, но не беспокойтесь об этом сейчас.) В хранилищах объектов есть данные (очевидно), а также путь к ключам и необязательный набор индексов. Keypaths - это в основном уникальные идентификаторы для ваших данных и представлены в нескольких разных форматах. Индексы будут рассмотрены позже, когда мы начнем говорить о получении данных.

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

var idbSupported = false;
var db;
document.addEventListener("DOMContentLoaded", function(){
    if("indexedDB" in window) {
        idbSupported = true;
    }
    if(idbSupported) {
        var openRequest = indexedDB.open("test_v2",1);
        openRequest.onupgradeneeded = function(e) {
            console.log("ruing onupgradeneeded");
            var thisDB = e.target.result;
            if(!thisDB.objectStoreNames.contains("firstOS")) {
                thisDB.createObjectStore("firstOS");
            }
        }
        openRequest.onsuccess = function(e) {
            console.log("Success!");
            db = e.target.result;
        }
        openRequest.onerror = function(e) {
            console.log("Error");
            console.dir(e);
        }
    }
},false);

Этот пример (test4.html) основывается на предыдущих записях, поэтому я просто сосредоточусь на том, что нового. В рамках обновленного события я использовал переданную ему переменную базы данных (thisDB). Одним из свойств этой переменной является список существующих хранилищ объектов, называемых objectStoreNames. Для любопытных людей это не простой массив, а «DOMStringList». Не спрашивай меня, но я пойду. Мы можем использовать метод contains, чтобы узнать, существует ли наше хранилище объектов, а если нет, создайте его. Это одна из немногих синхронных функций в IndexedDB, поэтому нам не нужно слушать результат.

Подводя итог тогда - это то, что произойдет, когда пользователь посещает ваш сайт. В первый раз, когда они здесь, активируется событие, обновленное. Код проверяет, существует ли хранилище объектов, «firstOS». Я не буду. Поэтому - создается. Затем выполняется обработчик успеха. Во второй раз, когда они посещают сайт, номер версии будет таким же, чтобы обновленное событие не было запущено.

Теперь представьте, что вы хотите добавить второй магазин объектов. Все, что вам нужно сделать, это увеличить номер версии и в основном дублировать содержащий код блока createObjectStore, который вы видите выше. Самое приятное, что ваш обновленный код будет поддерживать как новых пользователей, так и тех, у кого уже было первое хранилище объектов. Вот пример этого (test5.html):

var openRequest = indexedDB.open("test_v2",2);
openRequest.onupgradeneeded = function(e) {
    console.log("ruing onupgradeneeded");
    var thisDB = e.target.result;
    if(!thisDB.objectStoreNames.contains("firstOS")) {
        thisDB.createObjectStore("firstOS");
    }
    if(!thisDB.objectStoreNames.contains("secondOS")) {
        thisDB.createObjectStore("secondOS");
    }
}

Добавление данных

Как только вы получите готовые хранилища объектов, вы можете начать добавлять данные. Это - возможно, один из самых крутых аспектов IndexedDB. В отличие от традиционных табличных баз данных, IndexedDB позволяет хранить объект как есть. Это означает, что вы можете взять общий объект JavaScript и просто сохранить его. Готово. Очевидно, здесь есть некоторые оговорки, но по большей части, вот и все.

Для работы с данными требуется использовать транзакцию. Сделки принимают два аргумента. Первый - это массив таблиц, с которыми вы будете работать. В большинстве случаев это будет одна таблица. Второй аргумент - это тип транзакции. Существует два типа транзакций: readonly и readwrite. Добавление данных будет выполнять операцию readwrite. Начнем с создания транзакции:

//Assume db is a database variable opened earlier
var transaction = db.transaction(["people"],"readwrite");

Обратите внимание, что хранилище объектов «люди» - это только тот, который мы составили в приведенном выше примере. Наша следующая полная демо-версия будет использовать его. После получения транзакции вы затем спросите об этом в хранилище объектов, о котором вы сказали, что будете работать с:

var store = transaction.objectStore("people");

Теперь, когда у вас есть магазин, вы можете добавлять данные. Это делается с помощью метода - wait for it - add.

//Define a perso
var person = {
    name:name,
    email:email,
    created:new Date()
}
//Perform the add
var request = store.add(person,1);

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

Операция add является асинхронной, поэтому позволяет добавить два обработчика событий для результата.

request.onerror = function(e) {
    console.log("Error",e.target.error.name);
    //some type of error handler
}
request.onsuccess = function(e) {
    console.log("Woot! Did it");
}

У нас есть обработчик ошибок для ошибок и onsuccess для хороших изменений. Довольно очевидно, но давайте посмотрим полный пример. Вы можете найти это в файле test6.html.

<!doctype html>
<html>
<head>
</head>
<body>
<script>
var db;
function indexedDBOk() {
    return "indexedDB" in window;
}
document.addEventListener("DOMContentLoaded", function() {
    //No support? Go in the corner and pout.
    if(!indexedDBOk) return;
    var openRequest = indexedDB.open("idarticle_people",1);
    openRequest.onupgradeneeded = function(e) {
        var thisDB = e.target.result;
        if(!thisDB.objectStoreNames.contains("people")) {
            thisDB.createObjectStore("people");
        }
    }
    openRequest.onsuccess = function(e) {
        console.log("ruing onsuccess");
        db = e.target.result;
        //Listen for add clicks
        document.querySelector("#addButton").addEventListener("click", addPerson, false);
    }
    openRequest.onerror = function(e) {
        //Do something for the error
    }
},false);
function addPerson(e) {
    var name = document.querySelector("#name").value;
    var email = document.querySelector("#email").value;
    console.log("About to add "+name+"/"+email);
    var transaction = db.transaction(["people"],"readwrite");
    var store = transaction.objectStore("people");
    //Define a perso
    var person = {
        name:name,
        email:email,
        created:new Date()
    }
    //Perform the add
    var request = store.add(person,1);
    request.onerror = function(e) {
        console.log("Error",e.target.error.name);
        //some type of error handler
    }
    request.onsuccess = function(e) {
        console.log("Woot! Did it");
    }
}
</script>
<input type="text" id="name" placeholder="Name"><br/>
<input type="email" id="email" placeholder="Email"><br/>
<button id="addButton">Add Data</button>
</body>
</html>

В приведенном выше примере содержится небольшая форма с кнопкой, чтобы отключить событие для хранения данных в IndexedDB. Запустите это в своем браузере, добавьте что-то в поля формы и нажмите «Добавить». Если у вас открыты инструменты для браузера, вы должны увидеть что-то вроде этого.

Это замечательное время, чтобы указать, что Chrome имеет отличный просмотр для данных IndexedDB. Если вы перейдете на вкладку «Ресурсы», разверните раздел IndexedDB, вы увидите базу данных, созданную этой демонстрацией, а также только что введенный объект.

Для этого перейдите еще раз и нажмите кнопку «Добавить данные» еще раз. В консоли должна появиться ошибка:

Сообщение об ошибке должно быть ключом. ConstraintError означает, что мы просто пытались добавить данные с тем же ключом, что и уже существующий. Если вы помните, мы жестко закодировали этот ключ, и мы знали, что это будет проблемой. Пришло время поговорить с клавишами.


Ключи

Ключи являются индексированными версиями первичных ключей в DB. Традиционные базы данных могут иметь таблицы без ключей, но для каждого хранилища объектов должен быть ключ. IndexedDB позволяет использовать пару различных типов ключей.

Первый вариант - просто указать его самостоятельно, как мы это делали выше. Мы могли бы использовать логику для генерации уникальных ключей.

Второй вариант - это ключевой путь, где ключ основан на свойстве самих данных. Рассмотрим пример наших людей - мы могли бы использовать адрес электронной почты в качестве ключа.

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

Клавиши определяются при создании хранилищ объектов. Вот два примера: один использует ключевой путь и один генератор.

thisDb.createObjectStore("test", { keyPath: "email" });  
thisDb.createObjectStore("test2", { autoIncrement: true });

Мы можем изменить нашу предыдущую демонстрацию, создав хранилище объектов с ключом autoIncrement:

thisDB.createObjectStore("people", {autoIncrement:true});

Наконец, мы можем взять вызов Add, который мы использовали ранее, и удалить жесткий код:

var request = store.add(person);

Это оно! Теперь вы можете добавлять данные в течение всего дня. Вы можете найти эту версию в test7.html.


Чтение данных

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

var transaction = db.transaction(["test"], "readonly");
var objectStore = transaction.objectStore("test");
//x is some value
var ob = objectStore.get(x);
ob.onsuccess = function(e) {
}

Обратите внимание, что транзакция считывается только. Вызов API - это всего лишь простой вызов get с переданным ключом. В качестве быстрого ответа, если вы считаете, что использование IndexedDB немного подробное, обратите внимание, что вы также можете связать многие из этих вызовов. Вот тот же самый код, написанный гораздо более жестким:

db.transaction(["test"], "readonly").objectStore("test").get(X).onsuccess = function(e) {}

Лично я все еще считаю IndexedDB немного сложным, поэтому я предпочитаю подход «взломать», чтобы помочь мне отслеживать, что происходит.

Результатом обработчика onsuccess get является объект, который вы сохранили ранее. Когда у вас есть этот объект, вы можете делать все, что хотите. В нашем следующем демо (test8.html) мы добавили простое поле формы, чтобы вы могли ввести ключ и распечатать результат. Вот пример:

Обработчик кнопки Get Data находится ниже:

function getPerson(e) {
    var key = document.querySelector("#key").value;
    if(key === "" || isNaN(key)) return;
    var transaction = db.transaction(["people"],"readonly");
    var store = transaction.objectStore("people");
    var request = store.get(Number(key));
    request.onsuccess = function(e) {
        var result = e.target.result;
        console.dir(result);
        if(result) {
            var s = "&lt;h2>Key "+key+"&lt;/h2>&lt;p>";
            for(var field in result) {
                s+= field+"="+result[field]+"&lt;br/>";
            }
            document.querySelector("#status").ierHTML = s;
        } else {
            document.querySelector("#status").ierHTML = "&lt;h2>No match&lt;/h2>";
        }  
    }  
}

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


Чтение дополнительных данных

Таким образом, вы получите одну часть данных. Как насчет большого количества данных? IndexedDB поддерживает так называемый курсор. Курсор позволяет вам перебирать данные. Вы можете создавать курсоры с дополнительным диапазоном (базовым фильтром) и направлением.

В качестве примера следующий блок кода открывает курсор для извлечения всех данных из хранилища объектов. Как и все остальное, что мы сделали с данными, это асинхронно и в транзакции.

var transaction = db.transaction(["test"], "readonly");
var objectStore = transaction.objectStore("test");
var cursor = objectStore.openCursor();
cursor.onsuccess = function(e) {
    var res = e.target.result;
    if(res) {
        console.log("Key", res.key);
        console.dir("Data", res.value);
        res.continue();
    }
}

Обработчик успеха передается объекту результата (переменная res выше). Он содержит ключ, объект для данных (в ключе значения выше) и метод continue, который используется для итерации к следующей части данных.

В следующей функции мы использовали курсор для итерации по всем объектам данных объекта. Поскольку мы работаем с данными «person», мы назвали это getPeople:

function getPeople(e) {
    var s = "";
    db.transaction(["people"], "readonly").objectStore("people").openCursor().onsuccess = function(e) {
        var cursor = e.target.result;
        if(cursor) {
            s += "&lt;h2>Key "+cursor.key+"&lt;/h2>&lt;p>";
            for(var field in cursor.value) {
                s+= field+"="+cursor.value[field]+"&lt;br/>";
            }
            s+="&lt;/p>";
            cursor.continue();
        }
        document.querySelector("#status2").ierHTML = s;
    }
}

Вы можете увидеть полную демоверсию этого файла в файле test9.html. Он имеет логику Add Person, как в предыдущих примерах, поэтому просто создайте несколько человек, а затем нажмите кнопку, чтобы отобразить все данные.

Итак, теперь вы знаете, как получить одну часть данных, а также как получить все данные. Давайте теперь затронем нашу последнюю тему - работаем с индексами.


Они называют этот IndexedDB, правильно?

Мы говорили о IndexedDB для всей статьи, но пока не сделали никаких хороших индексов. Индексы являются важной частью хранилищ объектов IndexedDB. Они обеспечивают способ получения данных на основе их значения, а также указание того, должно ли значение быть уникальным в хранилище. Позже мы продемонстрируем, как использовать индексы для получения диапазона данных.

Во-первых - как вы создаете индекс? Как и все остальное структурное, они должны выполняться в событии обновления, в основном в то же время, когда вы создаете хранилище объектов. Вот пример:

var objectStore = thisDb.createObjectStore("people", 
                { autoIncrement:true });
//first arg is name of index, second is the path (col);
objectStore.createIndex("name","name", {unique:false});
objectStore.createIndex("email","email", {unique:true});

В первой строке мы создаем магазин. Мы принимаем этот результат (объект objectStore) и запускаем метод createIndex. Первый аргумент - это имя индекса, а второе - свойство, которое будет проиндексировано. В большинстве случаев я думаю, что вы будете использовать одно и то же имя для обоих. Последний аргумент - это набор опций. Пока мы используем один, уникальный. Первый индекс для имени не является уникальным. Второй для электронной почты. Когда мы храним данные, IndexedDB проверяет эти индексы и гарантирует уникальность свойства электронной почты. Он также будет выполнять некоторую обработку данных на задней панели, чтобы обеспечить возможность сбора данных этими индексами.

Как это работает? После получения хранилища объектов через транзакцию вы можете запросить индекс из этого хранилища. Используя приведенный выше код, вот пример этого:

var transaction = db.transaction(["people"],"readonly");
var store = transaction.objectStore("people");
var index = store.index("name");
//name is some value
var request = index.get(name);

Сначала мы получаем транзакцию, затем магазин, а затем индексируем. Как мы уже говорили, вы можете связать эти первые три строки, чтобы сделать их немного более компактными, если хотите.

После того, как у вас есть индекс, вы можете выполнить вызов get, чтобы получить данные по имени. Мы могли бы сделать что-то подобное для электронной почты. Результатом этого вызова является еще один асинхронный объект, к которому вы можете привязать обработчик onsuccess. Вот пример этого обработчика, найденного в файле test10.html:

request.onsuccess = function(e) {
    var result = e.target.result;
    if(result) {
        var s = "&lt;h2>Name "+name+"&lt;/h2>&lt;p>";
        for(var field in result) {
            s+= field+"="+result[field]+"&lt;br/>";
        }
        document.querySelector("#status").ierHTML = s;
    } else {
        document.querySelector("#status").ierHTML = "&lt;h2>No match&lt;/h2>";
    }  
}

Обратите внимание, что вызов вызова индекса может возвращать несколько объектов. Поскольку наше имя не уникально, мы, вероятно, должны изменить код для его обработки, но это не требуется.

Теперь давайте поднимем его. Вы видели использование get API для индекса, чтобы получить значение, основанное на этом свойстве. Что делать, если вы хотите получить более широкий набор данных? Конечным термином, который мы собираемся выучить сегодня, являются диапазоны. Диапазоны - это способ выбора подмножества индекса. Например, с учетом индекса свойства имени, мы можем использовать диапазон, чтобы находить имена, начинающиеся с A до имен, начинающихся с C. Диапазоны представлены в нескольких разных вариантах. Они могут быть «все ниже определенного маркера», «все выше определенного маркера» и «что-то между нижним маркером и более высоким маркером». Наконец, чтобы сделать интересные вещи, диапазоны могут быть инклюзивными или эксклюзивными. В основном это означает, что для диапазона, идущего от A-C, мы можем указать, хотим ли мы включить A и C в диапазон или только значения между ними. Наконец, вы также можете запросить как восходящие, так и нисходящие диапазоны.

Диапазоны создаются с использованием объекта toplevel с именем IDBKeyRange. Он имеет три метода, представляющих интерес: lowerBound, upperBound и bound. lowerBound используется для создания диапазона, который начинается с более низкого значения и возвращает все данные «выше». upperBound - противоположное. И - finally - bound используется для поддержки набора данных с нижней и верхней границей. Давайте посмотрим на некоторые примеры:

//Values over 39
var oldRange = IDBKeyRange.lowerBound(39);
//Values 40a dn over
var oldRange2 = IDBKeyRange.lowerBound(40,true);
//39 and smaller...
var youngRange = IDBKeyRange.upperBound(40);
//39 and smaller...
var youngRange2 = IDBKeyRange.upperBound(39,true);
//not young or old... you can also specify inclusive/exclusive
var okRange = IDBKeyRange.bound(20,40)

Когда у вас есть диапазон, вы можете передать его методу openCursor индекса. Это дает вам итератору цикл для значений, соответствующих этому диапазону. Практически это не обычай. Вы можете использовать это для поиска контента на основе начала строки, но не для середины или конца. Давайте посмотрим на полный пример. Сначала мы создадим простую форму для поиска людей:

Starting with: <input type="text" id="nameSearch" placeholder="Name"><br/>
Ending with: <input type="text" id="nameSearchEnd" placeholder="Name"><br/>
<button id="getButton">Get By Name Range</button>

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

function getPeople(e) {
    var name = document.querySelector("#nameSearch").value;
    var endname = document.querySelector("#nameSearchEnd").value;
    if(name == "" &amp;&amp; endname == "") return;
    var transaction = db.transaction(["people"],"readonly");
    var store = transaction.objectStore("people");
    var index = store.index("name");
    //Make the range depending on what type we are doing
    var range;
    if(name != "" &amp;&amp; endname != "") {
        range = IDBKeyRange.bound(name, endname);
    } else if(name == "") {
        range = IDBKeyRange.upperBound(endname);
    } else {
        range = IDBKeyRange.lowerBound(name);
    }
    var s = "";
    index.openCursor(range).onsuccess = function(e) {
        var cursor = e.target.result;
        if(cursor) {
            s += "&lt;h2>Key "+cursor.key+"&lt;/h2>&lt;p>";
            for(var field in cursor.value) {
                s+= field+"="+cursor.value[field]+"&lt;br/>";
            }
            s+="&lt;/p>";
            cursor.continue();
        }
        document.querySelector("#status").ierHTML = s;
    }
}

Сверху вниз - мы начинаем с захвата двух полей формы. Затем мы создаем транзакцию и с этого получаем магазин и индекс. Теперь для полукомплексной части. Поскольку у нас есть три разных типа диапазонов, нам нужно поддержать, мы должны сделать немного условной логики, чтобы выяснить, что нам нужно. Какой диапазон мы создаем, основывается на том, какие поля вы заполняете. Приятно, что когда мы имеем диапазон, мы просто передаем его индексу и открываем курсор. Это оно! Вы можете найти этот полный пример в test11.html. Обязательно сначала введите некоторые значения, чтобы у вас были данные для поиска.


Что дальше?

Верьте или нет - мы только начали нашу дискуссию по IndexedDB. В следующей статье мы рассмотрим дополнительные темы, включая обновления и удаления, значения на основе массива и некоторые общие рекомендации для работы с IndexedDB.