Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development

Робота з базою IndexedDB

by
Read Time:21 minsLanguages:

Ukrainian (українська мова) translation by AlexBioJS (you can also view the original English article)

Одна з найцікавіших розробок у галузі веб-стандартів за останній час – специфікація Indexed Database (або IndexedDB, якщо коротко). Якщо ви хочете весело провести час, то можете ознайомитися зі специфікацією самостійно. У цьому посібнику я буду розповідати вам про те, як працює ця  функціональна можливість, і, сподіваюся, трошки надихати вас на те, щоб ви самі використовували цю потужну функціональну можливість.


Огляд

Як специфікація IndexedDB зараз знаходиться на стадії Candidate Recommendation.

Якщо коротко, то IndexedDB надає вам можливість зберігання великих об'ємів даних у браузерах ваших користувачів. Кожному додатку, якому потрібно відправляти велику кількість даних по мережі, була би дуже корисною можливість зберігання даних на стороні клієнта, а не сервера. Звісно ж, вмістище для даних – тільки частина рівняння. IndexedDB також надає потужний API для пошуку даних, що працює на основі індексів, для отримання потрібних вам даних.

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

Кукі дуже добре підтримуються браузерами, проте мають правові наслідки та обмежений об'єм пам'яті. Також вони відправляються від сервера та назад при виконанні кожного запиту, зводячи до нуля переваги вмістища, що працює на стороні клієнта.

Веб-сховище (* Локальне сховище, HTML5-сховище, DOM-сховище; вбудована можливість зберігання даних на стороні клієнта (об'єм даних значно перевищує той, що надається кукі). Тут і надалі примітка перекладача) також добре підтримується браузерами, проте воно обмежено з точки зору доступного для вас об'єму сховища. Веб-сховище не надає вам справжнього «пошукового» API, оскільки дані добуваються тільки за допомогою значень ключів. Веб-сховище чудово підходить для зберігання "конкретних" речей, наприклад налаштувань користувача, тоді як IndexedDB більше підходить для зберігання довільних даних (подібно базі даних).

Перед тим як продовжити, давайте подивимося, як складається ситуація з підтримкою IndexedDB браузерами. Як специфікація IndexedDB зараз знаходиться на стадії Candidate Recommendation (* Можлива рекомендація. Цей документ підтверджує, що пропозиції W3C (консорціуму World-Wide Web)) ретельно розглянуто та вони відповідають вимогам Робочої Групи. IndexedDB була на цій стадії на момент написання оригіналу посібника (2013). Зараз (2018) знаходиться на стадії Recommendation (R)). На цьому етапі розробники цієї специфікації задоволені нею, проте очікують зараз зауважень та пропозицій від товариства розробників. Специфікація, ймовірно, зміниться на фінальній стадії, W3C Recommendation, у порівнянні з тим, якою вона є зараз. У цілому, браузери, що підтримують IndexedDB, роблять це доволі стабільно, проте розробники повинні бути готові до використання префіксів та з'явлення оновлень у майбутньому.

Стосовно тих браузерів, що підтримують IndexedDB, то ми маємо тут своєрідну дилему (* положення, при якому вибір однієї з двох протилежних можливостей однаково важкий). Цю технологію доволі добре підтримують десктопні браузери, проте у мобільних вона майже відсутня. Давайте поглянемо на те, що повідомляє з цього приводу чудовий сайт CanIUse.com:

CanIUse Report for IndexedDBCanIUse Report for IndexedDBCanIUse Report for IndexedDB

Chrome для Android дійсно підтримує IndexedDB, проте дуже мало людей сьогодні використовує цей браузер на пристроях Android. Чи означає відсутність підтримки для мобільних браузерів те, що вам не варто використовувати цю технологію? Звичайно ж, ні! Сподіваюся, що всі наші читачі знайомі з концепцією прогресивного покращення (* передбачає, що розробка веб-інтерфейсу відбувається поетапно, від простого до складного. На кожному з етапів ми отримуємо завершений веб-інтерфейс, що з кожним етапом стає ще кращим, ще зручнішим. В результаті виходить ресурс, що працює у всіх браузерах). Такі можливості як IndexedDB можуть бути додані у ваше застосування таким чином, що не порушать його роботу у браузерах без підтримки цієї технології. Ви би могли скористатися бібліотеками-обгортками для переключення на WebSQL при запуску додатка на мобільних пристроях або просто відмовитися від локального зберігання даних на стороні клієнтів мобільних пристроїв. Особисто я вважаю, що можливість гешування великих об'ємів даних на стороні клієнта достатньо важлива, щоб користуватися нею зараз, незважаючи на відсутність підтримки на стороні клієнта.


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

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

У фрагменті коду вище (з яким ви можете ознайомитися у test1.html, якщо завантажили прикріплений до цього посібника архів) використовується подія DOMContentLoaded, щоб код виконувався по завершенні завантаження сторінки. (Добре, це цілком очевидно, але я передбачаю, що розробники, які користувалися тільки jQuery, можуть не знати цього.) Потім я просто перевіряю наявність indexedDB в об'єкті window, і якщо це так, то можемо продовжувати. Це найпростіший приклад, проте звичайно нам, ймовірно, потрібно було би зберегти результат перевірки, щоб знати пізніше, чи можемо ми використовувати indexedDB. Нижче наводиться більш просунутий приклад (test2.html).

Я всього-на-всього створив глобальну змінну, idbSupported, яку можна використовувати в якості прапорця для перевірки підтримки поточним браузером IndexedDB.


Підключення до бази даних

Як ви можете здогадатися, в IndexedDB використовуються бази даних. Поясню, що така база даних не є реалізацією SQL Server. Вона локальна для браузера та доступна тільки для користувача. Бази даних IndexedDB працюють згідно з тими правилами, що й кукі з Веб-сховищем. База даних прив'язується конкретно до домену, з якого її було завантажено. Тому, наприклад, база під назвою "Foo", створена для foo.com не буде конфліктувати з базою даних під тією ж назвою для goo.com. Вона не тільки не буде конфліктувати, але й не буде доступна для інших доменів. Ви можете зберігати дані для вашого веб-сайту і бути впевненими, що інший веб-сайт не зможе отримати до них доступ.

Підключення до бази даних виконується за допомогою команди open. У найпростішому випадку ви вказуєте ім'я та версію. Дуже важливо, щоб ви вказали версію, через причини, які ми розглянемо пізніше. Нижче наводиться простий приклад:

Підключення до бази даних – асинхронна операція. Для оброблення результату виконання цієї операції вам потрібно буде додати деякі обробники подій. Є чотири різних типи подій, які можуть бути згенеровано:

  • success
  • error
  • upgradeneeded
  • blocked

Ви, ймовірно, можете здогадатися про значення success та error. Подія upgradeneeded генерується і тоді, коли користувач підключається до бази даних уперше, і тоді, коли ви змінюєте версію. blocked не відноситься до подій, які будуть звичайно генеруватися, але може бути згенеровано, якщо попереднє з'єднання ніколи не закривалося.

Звичайно при першому звертанні до вашого сайту буде згенеровано подію upgradeneeded. Після цього – тільки success. Давайте розглянемо простий приклад (test3.html):

Знов-таки, ми перевіряємо, чи підтримується власне IndexedDB, і якщо так, то підключаємося до бази даних. Ми додали у цьому прикладі обробники для трьох подій: upgradeneeded, success та error. Зараз давайте розглянемо подію success. Вона передається до обробника за допомогою target.result. Ми скопіювали її до глобальної змінної під назвою db. Саме нею ми скористаємося пізніше для, власне, додавання даних. Якщо ви виконаєте цей код у вашому браузері (в одному з тих, що підтримує IndexedDB, звісно ж!), то при першому запуску скрипта повинні будете побачити у вашій консолі повідомлення, що виводяться при виникненні подій upgrade та success. При подальших запусках скрипта ви повинні будете побачити лише повідомлення, що виводяться при виникненні події success.


Сховища об'єктів

Поки що ми перевірили, чи підтримує браузер IndexedDB, впевнилися, що це так, та підключилися до бази даних. Тепер нам потрібно місце, де будемо зберігати дані. В IndexedDB є концепція під назвою «сховище об'єктів». Ви можете вважати його типовою таблицею бази даних. (Приблизно, але поки що не заморочуйтеся.) У сховищі об'єктів містяться дані (що не дивно), а також ключове поле та необов'язковий набір індексів (* файл у СКБД, який зберігає список ключів, кожний з яких визначає унікальний запис у БД і містить інформацію про її фізичне розташування. Служить для прискорення пошуку та сортування даних). Ключові поля є по суті унікальними ідентифікаторами для ваших даних і можуть бути вказані у декількох різних форматах. Ми розглянемо індекси пізніше, коли піде мова про добування даних.

Тепер дещо важливе. Пам'ятаєте згадану вище подію upgradeneeded? Ви можете створити сховища об'єктів тільки при виникненні події upgradeneeded. Знайте, що за налаштуванням воно буде згенеровано автоматично при першому зверненні користувача до сайту. Ви можете цим скористатися для створення ваших сховищ об'єктів. Дуже важливо запам'ятати те, що якщо вам будь-коли потрібно буде змінити ваші об'єкти сховищ, ви повинні будете оновити версію (у згаданому раніше методі open) та внести потрібні зміни до коду. Давайте подивимося, як це реалізується на практиці:

Цей приклад (test4.html) заснований на попередніх, тому ми просто зосередимося на тому, що було додано. Я отримав значення змінної бази даних, яку було передано до події upgradeneeded (thisDB). Однією з властивостей цієї змінної є список наявних сховищ об'єктів під назвою objectStoreNames. Для тих, кому цікаво, це не простий масив, а "DOMStringList." Не запитуйте мене, але це так. Ми можемо скористатися методом contains, щоб перевірити, чи існує наше сховище об'єктів, і якщо ні, то створити його. Це одна з небагатьох синхронних функцій в IndexedDB, так що нам не потрібно використовувати обробник події.

Якщо узагальнити, то ось що відбувалося би при відвідуванні користувачем вашого сайту. При першому відвідуванні генерується подія upgradeneeded. За допомогою коду перевіряється, чи існує сховище об'єктів "firstOS". Його не буде. Тому воно створюється. Потім запускається обробник події success. При подальших відвідуваннях сайту номер версії буде тим самим, тому подія upgradeneeded не виникає.

Тепер давайте уявимо, що вам потрібно додати друге сховище об'єктів. Для цього вам потрібно збільшити номер версії та по суті продублювати наведений вище блок коду, де використовувалися методи contains/createObjectStore. Класно те, що код, який виконується при виникненні події upgradeneeded, буде коректно працювати і для абсолютно нових користувачів сайту, і для тих, хто вже має перше сховище об'єктів. Нижче наводиться приклад коду для цього випадку (test5.html):


Додавання даних

Після підготування сховищ об'єктів можете взятися за додавання даних. Це, ймовірно, один з найкрутіших аспектів IndexedDB. На відміну від традиційних баз даних, що працюють на основі таблиць, IndexedDB дозволяє вам зберігати об'єкт, як є. Це означає, що ви можете взяти звичайний об'єкт JavaScript та просто його зберегти. Готово. Звісно ж, не все так просто, але більшою мірою, це так.

Для роботи з даними вам потрібно використовувати транзакцію. Транзакції приймають два аргументи. Перший – масив таблиць, з яким ви будете працювати. У більшості випадків це буде одна таблиця. Другий аргумент – тип транзакції. Є два типи транзакції: readonly та readwrite. Додавання даних буде відноситися до операції типу readwrite. Давайте почнемо зі створення транзакції:

Зверніть увагу на сховище об'єктів "people", яке створили у прикладі вище. Ми будемо його використовувати у нашій наступній демоверсії. Після отримання транзакції ви запитуєте вказане вами сховище об'єктів:

Тепер, коли ви отримали сховище, можете додати дані. Це виконується за допомогою – зачекайте-но на нього – методу add.

Пам'ятаєте, як я раніше згадав, що ви можете зберігати будь-які дані (у цілому). Так що мій об'єкт person вище створено повністю довільно. Я би міг використовувати firstName та lastName замість просто name. Я би міг використовувати властивість gender. Ви вловили суть. Другий аргумент – ключ для однозначної ідентифікації даних. У нашому прикладі ми встановили у якості його значення 1, через що у нас скоро виникне проблема. Це нормально, скоро ми дізнаємося, як це виправити.

Операція add є асинхронною, так що давайте додамо двоє обробників подій для результату.

Ми маємо обробник onerror на випадок виникнення помилок, та onsuccess для оброблення результату вдало виконаної операції. Цілком очевидно, але давайте поглянемо на весь приклад. Ви можете ознайомитися з ним у файлі test6.html.

 >

У коді вище ми створюємо невелику форму з кнопкою, щоб згенерувати подію для зберігання даних в IndexedDB. Запустіть його у вашому браузері, уведіть якийсь текст у поля форми та натисніть кнопку для додавання даних. Якщо у вас відкрито інструменти розробника, то ви повинні будете побачити дещо подібне:

Data Entry FormData Entry FormData Entry Form

Зараз доречно згадати, що у Chrome є чудовий інструмент для перегляду даних IndexedDB. Якщо ви відкриєте вкладку Resources, розгорнете розділ IndexedDB, то побачите створену у нашому прикладі базу даних та тільки-но доданий об'єкт.

Chrome Dev Tools and IndexedDBChrome Dev Tools and IndexedDBChrome Dev Tools and IndexedDB

Давайте, задля інтересу, знову натисніть ту кнопку Add Data. Ви повинні будете побачити у консолі повідомлення про помилку.

Error adding data againError adding data againError adding data again

Повідомлення про помилку повинно бути для вас підказкою. ConstraintError означає, що ми тільки-но спробували додати дані з тим самим ключем, що вже існує. Якщо ви пам'ятаєте, ми встановили те значення і знали, що через це виникне проблема. Настав час поговорити про ключі.


Ключі

Ключі – версія IndexedDB первинних ключів. У традиційних базах даних можуть матися таблиці без ключів, проте кожне сховище об'єктів повинно мати ключ. В IndexedDB передбачено декілька різних типів ключів.

Перший варіант – просто вказуємо ключ самостійно, як зробили це вище. Ми могли би скористатися програмною логікою для генерування ключів.

Другий варіант – ключове поле, де в якості ключа вказується властивість самих даних. У нашому прикладі з людьми ми могли би використовувати адреси електронної пошти у якості ключа.

Ваш третій варіант, і на мій погляд найпростіший, – використання генератора ключів. Він подібний до первинного ключа, що працює в автоінкрементному режимі (* зі збільшенням вмісту на одиницю), і є найпростішим методом завдання ключів.

Ключі вказуються при створенні сховищ об'єктів. Нижче наведено два приклади: в одному використовується ключове поле, в іншому – генератор.

Ми можемо змінити попередню демоверсію, створивши сховище об'єктів з ключем, що працює в автоінкрементному режимі:

Нарешті, ми можемо скористатися раніше згаданим викликом Add та видалити заданий нами ключ:

От і все! Тепер ви можете додавати дані цілий день. Ви можете ознайомитися з кодом цього прикладу у файлі test7.html.


Зчитування даних

Тепер давайте перейдемо до зчитування окремих фрагментів даних (як зчитувати більші набори даних, ми розглянемо пізніше). Знов-таки, це асинхронна операція, яку буде виконано у транзакції. Нижче наводиться простий приклад:

Зверніть увагу, що вказана транзакція належить до типу «тільки для зчитування». У якості виклику API використовується простий виклик методу get, якому передано ключ. Вам на замітку: якщо ви вважаєте, що для використання IndexedDB потрібно доволі багато слів, то зверніть увагу, що ви також можете зчепити багато з тих викликів. Нижче наводиться той самий приклад, написаний набагато компактніше:

Особисто я як і раніше вважаю IndexedDB доволі складною технологією, так що мені більше до вподоби «розгорнутий» спосіб запису, що допомагає мені угледіти, що відбувається у коді.

Обробнику onsuccess передається збережений вами раніше об'єкт. Тільки-но ви отримали той об'єкт, можете робити, що хочете. У нашому наступному прикладі (test8.html) ми додали просто поле форми, щоб ви могли ввести ключ та вивести результат. Нижче наведено приклад:

Fetching dataFetching dataFetching data

Код обробника для кнопки Get Data наводиться нижче:

Значна частина коду повинна говорити сама за себе. Отримуємо значення, передане у полі, і викликаємо метод get сховища об'єктів, отриманого з транзакції. Зверніть увагу, що за допомогою коду для відображення даних ми просто отримуємо значення всіх полів та виводимо їх. У додатку з реального світу розробки ви повинні були би (будемо сподіватися) знати, що міститься у ваших даних, і працювати з певними полями.


Зчитування більших наборів даних

Що ж, ми розібралися з тим, як отримати один фрагмент даних. Як щодо отримання великих наборів даних? В IndexedDB є підтримка так званого курсору. Курсор дозволяє вам обходити дані. Ви можете створити курсори, передаючи необов'язкові діапазон (базовий фільтр) та напрямок.

У якості прикладу погляньте на наступний блок коду, за допомогою якого відкривається курсор для добування всіх даних зі сховища об'єктів. Як і все, що ми розглянули раніше, це асинхронна операція, виконувана у транзакції.

До обробника, який запускається у випадку вдалого виконання операції, передається об'єкт із результатом (який ми зберігаємо у змінній res). У ньому міститься ключ, об'єкт з даними (у ключі value вище) та метод continue, використовуваний для переходу до наступного фрагменту даних.

У наступній функції ми скористалися курсором для обходу всіх даних, що містяться у сховищі об'єктів. Оскільки ми маємо справу з даними про «осіб», то назвали її getPeople:

Ви можете ознайомитися з повним кодом демоверсії у завантаженому вами архіві у файлі test9.html. У ньому використовується та сама логіка для додавання осіб, як і у попередніх прикладах, так що просто створіть декілька осіб і потім натисніть кнопку для відображення всіх даних.

List of DataList of DataList of Data

Що ж, тепер ви знаєте, як отримати один фрагмент даних і як отримати всі дані. Зараз давайте перейдемо до нашої останньої теми – роботи з індексами.


Ця технологія називається IndexedDB, вірно?

Ми говорили про IndexedDB увесь цей час, але не працювали ще власне з будь-якими, що ж, індексами. Індекси – ключовий момент сховищ об'єктів IndexedDB. Завдяки ним ви можете добути дані залежно від їх значення та вказати, чи повинно бути значення унікальним у межах сховища. Пізніше ми продемонструємо, як використовувати індекси для отримання якогось діапазону даних.

Для початку розглянемо, як створювати індекси. Як і все конструктивне, вони повинні створюватися в обробнику для події upgrade, у принципі тоді ж, коли ви створюєте ваше сховище об'єктів. Нижче наведено приклад:

У першому рядку ми створюємо сховище. Ми беремо результат виконання цієї операції (об'єкт objectStore) та викликаємо метод createIndex. В якості першого аргументу він приймає ім'я індексу, а в якості другого – властивість, яку буде проіндексовано. Гадаю, що у більшості випадків ви будете використовувати однакову назву для обох. Останній аргумент – набір опцій. Поки що ми використовуємо тільки одну – unique. Перший індекс для name – не унікальний. А другий для email – унікальний. При зберіганні нами даних IndexedDB перевірить ці індекси та впевниться, що властивість email має унікальне значення. Також вона обробить дані певним чином приховано від нас, щоб гарантувати те, що ми можемо добути дані за допомогою цих індексів.

Як це працює? Після отримання сховища об'єктів через транзакцію ви можете потім отримати індекс із того сховища. Продовжуючи код вище, ось як це виконати:

Для початку ми отримуємо транзакцію, потім сховище та індекс. Як ми бачили раніше, ви могли би зчепити ті три перші рядки, щоб код виглядав компактніше.

Після отримання об'єкта індексу ви можете викликати його метод get для отримання даних залежно від імені. Ми могли би виконати дещо подібне і для email. Той виклик є асинхронною операцією, до результату виконання якої ви можете прив'язати обробник onsuccess. Нижче наводиться приклад такого обробника, код якого розташовується у test10.html:

Зверніть увагу, що метод об'єкта індексу get може повертати безліч об'єктів. Оскільки наше ім'я – не унікальне, нам, ймовірно, варто змінити код, проте це необов'язково.

Тепер давайте ускладнимо задачу. Ви бачили, як використовувати метод get API об'єкта індексу для отримання значення проіндексованої властивості. Що робити, коли вам потрібно отримати більший набір даних? Останній термін, який ми розглянемо, – діапазони. Діапазони – спосіб вибору підмножини індексу. Наприклад, якщо проіндексовано властивість name, то ми можемо скористатися діапазоном для отримання набору, який включає імена, починаючи з тих, що починаються на А, по ті, що починаються на С. є декілька різних варіантів діапазонів. Це може бути діапазон, що включає «всі значення нижче певної мітки», «всі значення вище певної мітки» та «значення між нижньою та верхньою мітками». Також, це просто вам на замітку, діапазони можуть бути включаючими (з ... по ... включно) або виключаючими. По суті це означає, що для діапазону A-C ми можемо вказати, чи хочемо ми включити до нього імена, що починаються на А та С, або ж тільки імена, що починаються з букв, розташованих між ними. Нарешті, ви можете також вказати, чи потрібно проводити ітерацію при відборі значень за збільшенням або за зменшенням.

Діапазони створюються за допомогою об'єкта вищого рівня під назвою IDBKeyRangeНа. Нас цікавлять три його методи: lowerBound, upperBound та bound. lowerBound використовується для створення діапазону, який починається з нижнього значення і за допомогою якого повертаються всі значення «вище» нього. upperBound працює навпаки. І, нарешті, метод bound використовується для вказання діапазону, що містить верхню та нижню границі. Давайте поглянемо на деякі приклади:

Після отримання діапазону ви можете передати його методу openCursor об'єкта індексу. У результаті ви отримуєте ітератор (* керуюча структура, що визначає порядок виконання деяких повторюваних дій; пристрій або програма організації циклів) для перебору значень, що відповідають тому діапазону. У дійсності вищеописана операція не є власне пошуком. Ви можете користуватися нею для пошуку контенту залежно від початку рядка, але не його середини або кінця. Давайте поглянемо на повний приклад: Для початку ми створимо просту форму для пошуку людей:

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

Розглянемо код зверху донизу. Ми починаємо з отримання двох полів форми. Далі ми створюємо транзакцію і отримуємо із неї сховище та індекс. Тепер переходимо до складнішої частини. Оскільки нам потрібно підтримувати три типи діапазонів, ми повинні додати трохи умовної логіки, щоб з'ясувати, який діапазон використовувати. Ми вибираємо тип залежно від заповнених вами полів. Чудово те, що після отримання діапазону ми просто передаємо його до індексу та відкриваємо курсор. От і все! Ви можете ознайомитися з повним прикладом у test11.html. Впевніться, що спершу ввели якісь значення, щоб у вас було що шукати.


Що далі?

Хочете – вірте, хочете – ні, але ми тільки почали розглядати IndexedDB. У наступному посібнику ми розглянемо додаткові питання, включаючи оновлення та видалення даних, роботу з властивостями, що є масивами, і деякі загальні поради з роботи з IndexedDB.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.