pouchdb
客户端数据库仍然是跨浏览器离线应用程序开发中的一个痛点。 一个角落是Safari和Opera≤12。这两种浏览器都专门支持Web SQL。 在另一个角落,我们有Firefox和Internet Explorer(10+),它们仅支持IndexedDB。 Chrome(和Opera 15+)值得,两者都支持。
现在,如果Web SQL和IndexedDB并不是完全不同的具有不同模型来存储数据的数据库,那么这种分裂将不会那么可怕。 支持两者都是一项艰巨的任务。 对我们来说幸运的是, PouchDB存在。
PouchDB是客户端数据库API。 它用JavaScript编写,并以CouchDB API为模型。 它甚至能够与CouchDB实例同步。 但是,我们对PouchDB感兴趣,因为它抽象了Web SQL和IndexedDB之间的差异,并将它们包装在一个接口中。
在本文中,我们将通过构建一个脱机工作的简单笔记应用程序来了解PouchDB。 这里仅覆盖部分代码。 为了易于阅读,简化了许多功能。 您可以从GitHub 下载整个内容 。
您需要什么
对于此项目,您将需要以下内容。
- PouchDB脚本的副本
- 支持IndexedDB或Web SQL的Web浏览器。 当前版本的Opera,Safari,Internet Explorer,Chrome和Firefox符合要求。
- HTTP服务器,例如Nginx,Lighttpd或Apache HTTP。
确实不是必需的,但是如果您想查看存储在本地数据库中的数据,请使用带有数据库检查器工具的浏览器。 Chrome,Opera和Safari均通过其本地开发人员工具支持数据库检查。 下图显示了Chrome中的PouchDB数据库。
由于IndexedDB和Web SQL中的起源限制,您还需要使用HTTP服务器进行开发。 使用你想要的任何服务器- 阿帕奇 , Nginx的和lighttpd的三种固体选项。 或者,您可以使用软件包,例如Mac OS X的MAMP ,Windows的WAMP或Mac,Windows和Linux的XAMPP 。
与其他JavaScript文件一样,将PouchDB添加到HTML文档中:
<script src="pouchdb-nightly.min.js"></script>
创建一个PouchDB数据库
所有PouchDB数据库或数据库连接都是使用PouchDB
构造函数创建的:
var pdb = new PouchDB('pouchnotes');
这将创建一个名为_pouch_pouchnotes
的数据库。 PouchDB在每个数据库名称前添加_pouch_
。 如果您还在网站的其他区域使用“原始” IndexedDB或Web SQL,请避免对这些数据库使用_pouch_
前缀。
规划我们的应用
那么做笔记的应用程序是什么样的? 好吧,我们可能希望每个音符都有一个标题。 每个便笺还将包含组成便笺正文的文本。 我们可能也想标记我们的笔记,因此我们将为此添加一个字段。 如果能够附加文件,那不是很好吗? 我们将使用以下HTML表单。
我们将基于此表单建立数据库结构。
设计模式(各种)
PouchDB的有趣之处在于它具有灵活的架构。 数据库中的每个对象实际上都是一个独立的文档。 PouchDB不使用数据组织的关系模型,因此我们可以根据需要将字段或属性添加到文档中。
PouchDB查询使用MapReduce,而不是SQL /关系数据库的SELECT * FROM tablename
语法。 您编写用于过滤和排序数据的函数。 与SQL相比,它需要一些思想上的转变,但是一旦掌握了它,这很容易。 我们稍后会看到一个示例。
添加和更新注释
提交表单后,我们会将注释添加到数据库中。 PouchDB提供了两种保存文档的方法: post
和put
。 每个方法接受两个参数。
-
document
(必填):包含属性及其值的对象。 在这种情况下,它将是表单字段及其值。 -
callback
(可选):操作完成时要调用的函数。 它接受两个参数:error
和response
。
主要区别在于: post
添加了一个新文档并生成了一个标识符( _id
); 使用put
,我们需要提供一个。 这意味着您可以使用put
添加或更新文档。 但是post
严格用于向数据库添加新文档。 现在,让我们看一下使用put
的示例。
var form, savenote;
form = document.getElementById('addnote');
savenote = function(event) {
var o = {};
o.notetitle = form.notetitle.value;
o.note = form.note.value;
o.tags = form.tags.value;
/* Generate an _id if we don't have one.
It should be a string, which is why we're
adding '' to it. */
if (event.target._id.value == '') {
o._id = new Date().getTime() + '';
} else {
o._id = event.target._id.value;
}
pdb.put(o, function(error, response) {
if (error) {
console.log(error);
return;
} else if(response && response.ok) {
/* Do something with the response. */
}
});
}
/* Add the event handler */
form.addEventListener('submit', savenote);
如果我们的表单中没有_id
值,我们将生成一个时间戳以供使用。 否则,我们将使用form._id
的值。 我们的其他表单字段将成为文档对象的属性和值。 通过使用put
而不是post
,我们可以使用savenote
函数添加和更新注释。
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
如果一切顺利,我们的回调将收到JSON格式的响应。 成功响应的示例如下所示。
{ok: true, id: "1391406871281", rev: "1-1d95025598a94304a87ef14c108db7be"}
我们没有做出任何回应。 根据您的应用程序,您可能不想这样做。 但是,对于我们的笔记应用程序,我们希望能够将文件与笔记相关联。 PouchDB将这些文件称为附件 。
保存附件
保存附件比保存文本复杂得多。 我们不能只查询input type="file"
字段的value
属性。 相反,我们必须使用File API读取文件数据,然后使用PouchDB的putAttachment
方法保存它。 让我们将上一部分添加到我们的savenote
方法中。
savenote = function(event) {
var o = {};
o.notetitle = form.notetitle.value;
o.note = form.note.value;
o.tags = form.tags.value;
/* Generate an _id if we don't have one.
It should be a string, which is why we're
adding '' to it. */
if (event.target._id.value == '') {
o._id = new Date().getTime() + '';
} else {
o._id = event.target._id.value;
}
pdb.put(o, function(error, response) {
if (error) {
console.log(error);
return;
}
/* New code for saving attachments */
if (response && response.ok) {
if (form.attachment.files.length) {
var reader = new FileReader();
/* Using a closure so that we can extract the
File's attributes in the function. */
reader.onload = (function(file) {
return function(e) {
pdb.putAttachment(response.id, file.name, response.rev, e.target.result, file.type);
};
})(form.attachment.files.item(0));
reader.readAsDataURL(form.attachment.files.item(0));
}
}
});
}
每种文件输入类型还具有一个files
属性,该属性返回FileList
对象。 在这种情况下,它是form.attachment.files
。 顾名思义, FileList
对象是一个数组,其中包含使用该字段提交的文件。 我们可以使用length
属性确定列表中的文件数。 列表中的每个文件都可以使用其索引和item
方法进行引用,就像我们在这里所做的那样( form.attachment.files.item(0)
)。 另外,您可以使用方括号语法( form.attachment.files[0]
)。
如果注释添加成功,我们将获得response.id
。 然后,我们可以检查是否还有要另存为附件的文件。 如果存在,我们将使用FileReader
对象( var reader = new FileReader()
)读取它。 PouchDB附件必须是base64编码的。 编码文件的最简单方法是使用readAsDataURL()
。 加载文件后,我们可以使用putAttachment
将其保存到数据库中。
PouchDB的putAttachment
方法最多接受六个参数。 五个是必需的,一个是可选的。
-
docID
(必填):此附件将与之关联的文档的标识符。 在这种情况下,它是response.id
。 -
Attachment ID
(必填):附件的名称。 在这里,我们使用文件的名称。 -
rev
(必填):父文档的修订号。 -
attachment_doc
(必填):base64编码的文件数据。 在这种情况下,FileReader
对象的result
属性。 -
type
(必填):此数据的MIME类型。 例如,image/png
或application/pdf
。 -
callback
(可选):操作完成时要调用的函数。 与所有PouchDB回调函数一样,它接受两个参数error
和response
。 在我们的示例中,我们省略了它。
在此示例中,我们还将onload
事件处理程序包装在一个闭包中。 通过闭包,可以从事件处理程序中访问文件属性(例如,使用file.name
和file.type
)。
现在,我们已经研究了保存便笺和附件,现在让我们来看一下单独和成组地检索记录。
检索所有笔记
如果我们想查看数据库中的注释列表怎么办? 这是PouchDB的allDocs
有用的地方。 PouchDB.allDocs
允许我们一次检索一批文档。
allDocs
这个名字有点误导。 我们当然可以使用它来检索我们所有的文档。 但是,我们也可以使用它来检索属于一定范围内的文档,或者检索与特定键匹配的文档。 此方法接受两个参数,都不是必需的。
-
options
(可选):包含以下一个或多个属性的对象。-
include_docs
(布尔值):为每一行包括整个文档。 当false
,将只返回文档的id
和rev
数。
*conflicts
(布尔值):包括冲突。 -
startkey
和endkey
:包括带有此范围内的键的文档。 -
descending
(布尔值):而是按降序对结果进行排序。
*options.keys
(数组):仅返回与指定键匹配的文档。
*options.attachments
(布尔值):返回带有文档的附件。
*callback
(可选):检索完成时调用的函数。 与其他PouchDB回调一样,它接收error
参数和response
参数。
-
在下面的简化示例中,我们检索了数据库中的所有文档。 为了检索文档标题,创建日期和修改日期,我们需要将include_docs
的值设置为true
。 这是我们的viewnoteset
函数。
var viewnoteset = function() {
var df = document.createDocumentFragment(),
options = {},
nl = document.querySelector('#notelist tbody');
options.include_docs = true;
this.pdb.allDocs(options, function(error, response) {
var row = response.rows.map(addrow); // Calls an addrow() function
row.map(function(f) {
if (f) {
df.appendChild(f);
}
});
nl.appendChild(df);
});
};
response
的值是一个包含三个属性的对象: total_rows
, offset
和rows
。 我们对response.rows
最为感兴趣,因为它是文档对象的数组。 在这里,我们在response.rows
上使用了map
,这是JavaScript的内置数组方法之一。 使用map
为每个注释调用我们的addrow
函数,并将其添加到列出我们的注释的表中。
检索个别笔记
检索单个便笺要容易一些,因为我们可以使用PouchDB的get
方法。 唯一需要的参数是文档ID。 但是,我们可以包含options
参数和回调函数来处理结果。
我们的选项参数{attachments: true}
确保特定笔记包含任何附件,并且在查看时将与笔记一起显示。 在这里,我们的回调函数获取笔记数据,并使用其填写表单字段并显示任何附件。
var viewnote = function(noteid) {
var noteform = document.querySelector('#noteform');
pdb.get(noteid, {attachments: true}, function(error, response) {
var fields = Object.keys(response), o, link, attachments, li;
if (error) {
return;
} else {
/* Since our note field names and form field names match,
We can just iterate over them. */
fields.map(function(f) {
if (noteform[f] !== undefined && noteform[f].type != 'file') {
noteform[f].value = response[f];
}
if (f == '_attachments') {
attachments = response[f];
for (o in attachments) {
li = document.createElement('li');
link = document.createElement('a');
link.href = 'data:' + attachments[o].content_type + ';base64,' + attachments[o].data;
link.target = "_blank";
link.appendChild(document.createTextNode(o));
li.appendChild(link);
}
document.getElementById('attachmentlist').appendChild(li);
}
});
}
});
}
在演示应用程序中,我们使用链接传递每个注释的id
。 每个href
指向/#/view/xxxxx
,其中xxxxx
是注释id
。 单击一个链接将触发一个hashchange
事件,而hashchange
事件处理程序(如下所示)是我们将id
传递给viewnote
。
window.addEventListener('hashchange', function(e) {
var noteid;
/* Replacing # for compatibility with IE */
if (window.location.hash.replace(/#/,'')) {
noteid = window.location.hash.match(/\d/g).join('');
viewnote(noteid);
}
});
使便笺可搜索
便笺在可搜索时特别有用。 因此,让我们向应用程序中添加搜索功能。 我们将从搜索表单中获取输入,并将其用作搜索查询的基础。 下图显示了使用搜索功能时我们的应用程序的外观。
PouchDB查询看起来与SQL有很大不同。 使用SQL,您可以指定要选择的内容,从哪个表中选择的内容以及根据什么条件。 例如,一个简单的便笺搜索查询可能看起来像这样: SELECT * FROM notes WHERE title, text, tags LIKE %interview%
like SELECT * FROM notes WHERE title, text, tags LIKE %interview%
。 但是使用PouchDB,我们使用函数运行查询。
要运行查询,我们将使用PouchDB的query
方法。 它接受三个参数。
-
fun
(必填):函数的名称。 -
options
(可选):包含搜索结果选项的对象。 您可以指定化简函数或将结果限制为特定的键或键范围。 -
callback
(可选):查询完成时要调用的函数。
让我们看看下面的搜索功能。
var search = function(searchkey) {
var map = function(doc) {
var searchkey, regex;
/* Escape characters with special RegExp meaning */
searchkey = document.getElementById('q').value.replace(/[$-\/?[-^{|}]/g, '\\$&');
regex = new RegExp(searchkey,'i');
/* If the notetitle, note, or tags fields match,
return only the fields we need to create the result list. */
if (doc.notetitle.match(regex) ||
doc.note.match(regex) ||
doc.tags.match(regex)) {
emit(doc._id, {notetitle: doc.notetitle, note: doc.note, tags: doc.tags});
}
}
db.query(map, function(err, response) {
if (err) {
console.log(err);
}
if (response) {
var df, rows, nl, results;
/* Rewrite the response so that our object has the
correct structure for our addrow function. */
results = response.rows.map(function(r) {
r.doc = r.value;
delete r.value;
return r;
});
nl = document.querySelector('#notelist tbody');
df = document.createDocumentFragment(),
rows = results.map(addrow, that);
rows.map(function(f) {
if (f) {
df.appendChild(f);
}
});
nl.innerHTML = '';
nl.appendChild(df);
}
});
}
在搜索功能中,我们定义了一个map
功能,这就是我们查找和过滤记录的方式。 map
函数始终将PouchDB文档作为其唯一参数。 我们不必命名此函数map
,但它必须是第一个参数。
在map
,我们已经从搜索表单输入中创建了一个正则表达式对象。 我们将测试我们的notetitle
, note
和tags
字段,以查看这些字段是否与我们的正则表达式匹配。 如果是这样,我们将使用emit
方法返回notetitle
, id
(是一个时间戳)和修改后的属性。 该emit
方法被内置到PouchDB。 顾名思义,它以指定的格式选择并返回指定的属性。 emit
的第一个参数成为我们结果的关键。
我们的map
函数成为query
的第一个参数。 正如您可能已经猜到的那样, query
的第二个参数是回调函数。 假设一切正常,我们的response
参数将是一个包含三个属性的对象: total_rows
, offset
和rows
。 我们要rows
。 它是一个包含与我们的搜索词匹配的注释的数组。 以下代码示例显示了响应的外观。
[{
value: {
id: "1388703769529",
modified: 1391742787884,
notetitle: "Fluffernutter sandwich recipe"
},
id:"1388703769529",
key:"1388703769529"
},
{
value: {
id: "1391656570611",
modified: 1391656570611,
notetitle: "Browned-butter Rice Krispie Treats recipe"
},
id:"1391656570611",
key:"1391656570611"
}]
因为我们的响应是一个数组,所以我们可以使用本机Array.prototype
方法来操纵结果。 在这种情况下,我们使用Array.prototype.map
重写每个note对象,以便我们的value
属性变为doc
,并再次为每个结果调用addrow
。
脱机使用应用程序缓存
为了使该应用程序完全脱机工作,我们还需要使用Application Cache脱机保存HTML,CSS和JavaScript。 Application Cache是一个纯文本文件,带有Content-type: text/cache-manifest
标头,用于告知浏览器要在本地存储哪些资产。 我们将不在这里对Application Cache进行“深入研究”,而是让我们来看一下演示应用程序的清单文件pouchnotes.cache
。
CACHE MANIFEST
# Version 2014.02.10.01
CACHE:
index.html
css/style.css
js/pouchdb-nightly.min.js
js/application.js
我们从CACHE MANIFEST
行开始,这是所有缓存清单必须开始的方式。 第二行告诉我们该文件的版本。 浏览器仅在缓存清单更改时才更新缓存。 如果我们修改CSS,JavaScript或HTML文件,更改版本号是触发更新的最简单方法。
不过,我们仍然需要做一件事。 我们需要将清单添加到HTML文档中。 这需要将manifest
属性添加到我们的<html>
标签,如下所示:
<html lang="en-us" manifest="pouchnotes.manifest">
现在,即使我们处于离线状态,我们的数据库和文件也将可用。
警告:应用程序缓存增加了开发的复杂性。 因为必须更改缓存清单才能使浏览器下载新文件,所以您应该等到准备发布应用程序的版本之前再添加它。
结论
PouchDB还有更多我们这里没有涉及的内容。 例如,您可以将PouchDB与CouchDB服务器同步。 与数据库服务器同步使我们可以构建可轻松在多个浏览器和计算机之间共享数据和文件的应用程序。
我希望本文使您对PouchDB是什么以及如何使用它来构建即使我们的Internet连接不起作用也能运行的软件有所了解。
翻译自: https://www.sitepoint.com/building-offline-first-app-pouchdb/
pouchdb