Работа с базой IndexedDB - Часть 2
() translation by (you can also view the original English article)
Приветствую вас во втором руководстве серии, посвященной IndexedDB. Я настоятельно рекомендую вам ознакомиться с первым, поскольку буду считать, что вы уже знакомы со всеми рассмотренными мною ранее понятиями. В этом руководстве мы завершим рассмотрение аспектов парадигмы CRUD (* Create, Update, Read, Delete – создать, прочесть, обновить, удалить), которые еще не обсудили, и затем создадим приложение из реального мира разработки, которое будем использовать для демонстрации остальных концепций в последнем руководстве.
Обновление записей
Давайте начнем с обсуждения того, как обновить запись при помощи IndexedDB. Если помните, добавить данные в базу довольно просто:
1 |
//Define a person
|
2 |
var person = { |
3 |
name:name, |
4 |
email:email, |
5 |
created:new Date() |
6 |
}
|
7 |
|
8 |
//Perform the add
|
9 |
var request = store.add(person); |
Обновить данные не менее просто. Если мы предположим, что вы определили свойство под названием id
в качестве ключа для вашего хранилища объектов, то можете просто воспользоваться методом put
вместо add
.
1 |
var person = { |
2 |
name:name, |
3 |
email:email, |
4 |
created:new Date(), |
5 |
id:someId |
6 |
}
|
7 |
|
8 |
//Perform the update
|
9 |
var request = store.put(person); |
Как и в случае с методом add
, при использовании метода put вы можете назначать методы для асинхронной обработки результатов операции.
Удаление записей
Удаление записей осуществляется при помощи метода delete. (Как неожиданно.) Вы просто передаете уникальный идентификатор записи, которую хотите удалить. Ниже показан простой пример:
1 |
|
2 |
var t = db.transaction(["people"], "readwrite"); |
3 |
var request = t.objectStore("people").delete(thisId); |
И как и при работе с другими методами IndexedDB, вы можете добавить ваши обработчики для асинхронной обработки результатов операции.
Что ж, как я упомянул, все это выглядит не очень захватывающе, что, вероятно, и хорошо. Вам нужно, чтобы ваши API были простыми, скучными и без неожиданностей. Теперь давайте воспользуемся тем, что уже изучили, и создадим приложение из реального мира разработки.
Приложение для ведения записей
Хорошо, наконец у нас имеются все (ладно, большинство) частей, необходимых для создания приложение из реального мира разработки. Поскольку мы еще этого не сделали (гм), мы разработаем простое приложение для ведения записей. Давайте рассмотрим несколько скриншотов и затем разберем код, необходимый для реализации описанного в них функционала. При запуске приложения происходит инициализация IndexedDB для приложения и отображение пустой таблицы. Изначально, все что вы можете выполнить в приложении, – добавить новую запись. (Вероятно, мы могли бы сделать интерфейс более удобным для пользователя.)



После нажатия кнопки Add Note появляется форма:



После добавления некоторых данных формы вы можете затем сохранить запись.



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



Что ж, это не совсем относится к высшему пилотажу, однако является готовым рабочим примером использования спецификации IndexedDB. Добавленные записи будут продолжать существовать. Вы можете закрыть ваш браузер, перезапустить компьютер, поразмышлять несколько лет над вашей жизнью и посочинять стихи, и когда вы откроете ваш браузер снова, то ваши данные по-прежнему будут там. Теперь давайте взглянем на код:
Сначала – отказ от ответственности. Рассматриваемое приложение было бы отличным кандидатом, для реализации которого стоило бы воспользоваться одним из многих фреймворков JavaScript. Уверен, что те из вас, кто используют Backbone или Angular, уже могут представить, как бы реализовали наше приложение. Однако, я принял смелое решение – не пользоваться фреймворком. Я переживал и за тех, кто, возможно, использует другой фреймворк, так и за тех, кто не использует их вообще. Я хотел бы, чтобы мы сфокусировались здесь только на вопросах, связанных с IndexedDB. Я уверен, что найдутся люди, не согласные с этим решением, однако, давайте обсудим это в разделе для комментариев.
Наш первый образец – HTML-файл. У нас он только один, и большая часть его содержимого – стандартный код для Bootstrap (* веб-фреймворк для создания дизайна веб-сайтов и веб-приложений).
1 |
<!DOCTYPE html>
|
2 |
<html lang="en"> |
3 |
<head>
|
4 |
<meta charset="utf-8"> |
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
6 |
|
7 |
<title>Note Database</title> |
8 |
|
9 |
<link href="bootstrap/css/bootstrap.css" rel="stylesheet"> |
10 |
<link href="css/app.css" rel="stylesheet"> |
11 |
|
12 |
</head>
|
13 |
|
14 |
<body>
|
15 |
|
16 |
<div class="navbar navbar-inverse navbar-fixed-top"> |
17 |
<div class="container"> |
18 |
<div class="navbar-header"> |
19 |
<a class="navbar-brand" href="#">Note Database</a> |
20 |
</div>
|
21 |
</div>
|
22 |
</div>
|
23 |
|
24 |
<div class="container"> |
25 |
|
26 |
<div id="noteList"></div> |
27 |
<div class="pull-right"><button id="addNoteButton" class="btn btn-success">Add Note</button></div> |
28 |
<div id="noteDetail"></div> |
29 |
|
30 |
<div id="noteForm"> |
31 |
<h2>Edit Note</h2> |
32 |
<form role="form" class="form-horizontal"> |
33 |
<input type="hidden" id="key"> |
34 |
<div class="form-group"> |
35 |
<label for="title" class="col-lg-2 control-label">Title</label> |
36 |
<div class="col-lg-10"> |
37 |
<input type="text" id="title" required class="form-control"> |
38 |
</div>
|
39 |
</div>
|
40 |
<div class="form-group"> |
41 |
<label for="body" class="col-lg-2 control-label">Body</label> |
42 |
<div class="col-lg-10"> |
43 |
<textarea id="body" required class="form-control"></textarea> |
44 |
</div>
|
45 |
</div>
|
46 |
<div class="form-group"> |
47 |
<div class="col-lg-offset-2 col-lg-10"> |
48 |
<button id="saveNoteButton" class="btn btn-default">Save Note</button> |
49 |
</div>
|
50 |
</div>
|
51 |
</form>
|
52 |
</div>
|
53 |
|
54 |
</div>
|
55 |
|
56 |
<script src="js/jquery-2.0.0.min.js"></script> |
57 |
<script src="bootstrap/js/bootstrap.min.js"></script> |
58 |
<script src="js/app.js"></script> |
59 |
</body>
|
60 |
</html>
|
Как упоминалось выше, значительная часть этого кода – стандартный код для Bootstrap. Интересующие нас части – элементы div с идентификаторами noteList
, noteDetail
и noteForm
. Вы, вероятно, можете догадаться, что это те элементы div, которые мы будем обновлять после действий со стороны пользователя.
Пишем код главного файла приложения
Теперь давайте взглянем на app.js
, главный файл, в котором содержится логика (* последовательность операций, выполняемых программно или аппаратно) нашего приложения.
1 |
/* global console,$,document,window,alert */
|
2 |
var db; |
3 |
|
4 |
function dtFormat(input) { |
5 |
if(!input) return ""; |
6 |
var res = (input.getMonth()+1) + "/" + input.getDate() + "/" + input.getFullYear() + " "; |
7 |
var hour = input.getHours(); |
8 |
var ampm = "AM"; |
9 |
if(hour === 12) ampm = "PM"; |
10 |
if(hour > 12){ |
11 |
hour-=12; |
12 |
ampm = "PM"; |
13 |
}
|
14 |
var minute = input.getMinutes()+1; |
15 |
if(minute < 10) minute = "0" + minute; |
16 |
res += hour + ":" + minute + " " + ampm; |
17 |
return res; |
18 |
}
|
Вы можете проигнорировать первую функцию, поскольку это просто вспомогательная функция для преобразования формата дат. Давайте перескочим к блоку кода, который добавляется в методе jQuery ready, зарегистрированный для объекта document.
Проверка поддержки IndexedDB браузером
1 |
|
2 |
$(document).ready(function() { |
3 |
|
4 |
if(!("indexedDB" in window)) { |
5 |
alert("IndexedDB support required for this demo!"); |
6 |
return; |
7 |
}
|
8 |
|
9 |
var $noteDetail = $("#noteDetail"); |
10 |
var $noteForm = $("#noteForm"); |
11 |
|
12 |
var openRequest = window.indexedDB.open("nettuts_notes_1",1); |
13 |
|
14 |
openRequest.onerror = function(e) { |
15 |
console.log("Error opening db"); |
16 |
console.dir(e); |
17 |
};
|
18 |
|
19 |
openRequest.onupgradeneeded = function(e) { |
20 |
|
21 |
var thisDb = e.target.result; |
22 |
var objectStore; |
23 |
|
24 |
//Create Note OS
|
25 |
if(!thisDb.objectStoreNames.contains("note")) { |
26 |
console.log("I need to make the note objectstore"); |
27 |
objectStore = thisDb.createObjectStore("note", { keyPath: "id", autoIncrement:true }); |
28 |
}
|
29 |
|
30 |
};
|
31 |
|
32 |
openRequest.onsuccess = function(e) { |
33 |
db = e.target.result; |
34 |
|
35 |
db.onerror = function(event) { |
36 |
// Generic error handler for all errors targeted at this database's
|
37 |
// requests!
|
38 |
alert("Database error: " + event.target.errorCode); |
39 |
console.dir(event.target); |
40 |
};
|
41 |
|
42 |
displayNotes(); |
43 |
|
44 |
};
|
Первое, что нам необходимо выполнить, – проверить поддержку IndexedDB браузером. Если браузер пользователя не поддерживает ее, то мы выводим сообщение пользователю и прерываем выполнение функции. Вероятно, было бы лучше, если бы мы перенаправили пользователей на страницу с подробными объяснениями причины, по которой они не могут использовать приложение. (И если честно, мы могли бы также добавить в наше приложение возможность использования в качестве запасного варианта WebSQL. Но опять-таки, в этом руководстве я буду упрощать.)
После получения пары селекторов jQuery, которые мы будем использовать в коде нашего приложения, мы открываем соединение с базой данных IndexedDB. Код для работы с базой данных довольно прост. Вы видите, что мы создали одно хранилище объектов под названием note
в обработчике onupgradeneeded
. После того как код вышеуказанного обработчика выполнен, в обработчике onsuccess
будет запущена функция displayNotes
.
Функция displayNotes
1 |
|
2 |
function displayNotes() { |
3 |
|
4 |
var transaction = db.transaction(["note"], "readonly"); |
5 |
var content="<table class='table table-bordered table-striped'><thead><tr><th>Title</th><th>Updated</th><th>&nbsp;</td></thead><tbody>"; |
6 |
|
7 |
transaction.oncomplete = function(event) { |
8 |
$("#noteList").html(content); |
9 |
};
|
10 |
|
11 |
var handleResult = function(event) { |
12 |
var cursor = event.target.result; |
13 |
if (cursor) { |
14 |
content += "<tr data-key=\""+cursor.key+"\"><td class=\"notetitle\">"+cursor.value.title+"</td>"; |
15 |
content += "<td>"+dtFormat(cursor.value.updated)+"</td>"; |
16 |
|
17 |
content += "<td><a class=\"btn btn-primary edit\">Edit</a> <a class=\"btn btn-danger delete\">Delete</a></td>"; |
18 |
content +="</tr>"; |
19 |
cursor.continue(); |
20 |
}
|
21 |
else { |
22 |
content += "</tbody></table>"; |
23 |
}
|
24 |
};
|
25 |
|
26 |
var objectStore = transaction.objectStore("note"); |
27 |
|
28 |
objectStore.openCursor().onsuccess = handleResult; |
29 |
|
30 |
}
|
Функция displayNotes
выполняет то, что вы и ожидаете, – получает все данные и отображает их. Мы обсудили, как получить все данные в предыдущем руководстве, однако я хочу обратить здесь ваше внимание на один отличительный момент. Обратите внимание, что у нас имеется новый обработчик событий, oncomplete
, который мы подвязали к самой транзакции (* групповая операция). Ранее вы использовали обработчики только внутри методов для выполнения действий, внутри транзакции, однако при помощи IndexedDB мы также можем использовать их и на верхнем уровне. Это становится особенно полезным в случаях вроде нашего. У нас имеется огромная строка, наша HTML-таблица, которую создали при итерации по нашим данным. Мы можем воспользоваться обработчиком транзакции oncomplete
, чтобы обернуть порцию данных для отображения и отобразить ее при помощи единственного вызова jQuery.
Функции для удаления, редактирования и добавления записей
1 |
|
2 |
$("#noteList").on("click", "a.delete", function(e) { |
3 |
var thisId = $(this).parent().parent().data("key"); |
4 |
|
5 |
var t = db.transaction(["note"], "readwrite"); |
6 |
var request = t.objectStore("note").delete(thisId); |
7 |
t.oncomplete = function(event) { |
8 |
displayNotes(); |
9 |
$noteDetail.hide(); |
10 |
$noteForm.hide(); |
11 |
};
|
12 |
return false; |
13 |
});
|
14 |
|
15 |
$("#noteList").on("click", "a.edit", function(e) { |
16 |
var thisId = $(this).parent().parent().data("key"); |
17 |
|
18 |
var request = db.transaction(["note"], "readwrite") |
19 |
.objectStore("note") |
20 |
.get(thisId); |
21 |
request.onsuccess = function(event) { |
22 |
var note = request.result; |
23 |
$("#key").val(note.id); |
24 |
$("#title").val(note.title); |
25 |
$("#body").val(note.body); |
26 |
$noteDetail.hide(); |
27 |
$noteForm.show(); |
28 |
};
|
29 |
|
30 |
return false; |
31 |
});
|
32 |
|
33 |
$("#noteList").on("click", "td", function() { |
34 |
var thisId = $(this).parent().data("key"); |
35 |
var transaction = db.transaction(["note"]); |
36 |
var objectStore = transaction.objectStore("note"); |
37 |
var request = objectStore.get(thisId); |
38 |
|
39 |
request.onsuccess = function(event) { |
40 |
var note = request.result; |
41 |
$noteDetail.html("<h2>"+note.title+"</h2><p>"+note.body+"</p>").show(); |
42 |
$noteForm.hide(); |
43 |
};
|
44 |
});
|
45 |
|
46 |
$("#addNoteButton").on("click", function(e) { |
47 |
$("#title").val(""); |
48 |
$("#body").val(""); |
49 |
$("#key").val(""); |
50 |
$noteDetail.hide(); |
51 |
$noteForm.show(); |
52 |
});
|
Наши следующие два метода (delete
и edit
) реализуются согласно тому же принципу. Поскольку в этом коде нет новых вызовов IndexedDB, то мы не будем на них обращать внимание. Большей частью «мяса» здесь являеются простые манипуляции с DOM (* Document Object Model – объектная модель документа) для выполнения определенных действий. Обработчик события click для кнопки, при помощи которой добавляются записи, как раз для этого и служит.
Функция Save
1 |
|
2 |
$("#saveNoteButton").on("click",function() { |
3 |
|
4 |
var title = $("#title").val(); |
5 |
var body = $("#body").val(); |
6 |
var key = $("#key").val(); |
7 |
|
8 |
var t = db.transaction(["note"], "readwrite"); |
9 |
|
10 |
if(key === "") { |
11 |
t.objectStore("note") |
12 |
.add({title:title,body:body,updated:new Date()}); |
13 |
} else { |
14 |
t.objectStore("note") |
15 |
.put({title:title,body:body,updated:new Date(),id:Number(key)}); |
16 |
}
|
17 |
|
18 |
t.oncomplete = function(event) { |
19 |
$("#key").val(""); |
20 |
$("#title").val(""); |
21 |
$("#body").val(""); |
22 |
displayNotes(); |
23 |
$noteForm.hide(); |
24 |
};
|
25 |
|
26 |
return false; |
27 |
});
|
28 |
|
29 |
});
|
Следующий лакомый кусочек – функция save
. В нее необходимо добавить немного логики, чтобы определить, происходит ли обновление или добавление данных, однако даже это довольно просто. И на этом все! Теперь у нас имеется готовое приложение, использующее IndexedDB. Вы можете поэкспериментировать с этой демоверсией, скачав прикрепленные файлы с исходный кодом.
В заключение
На этом вторая часть серии подошла к концу! В третьем руководстве мы продолжим работать с этим приложением и начнем добавлять дополнительные возможности, включая возможность поиска данных и возможности, реализуемые при помощи массива.