pouchdb_使用PouchDB构建离线的第一个应用程序

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数据库。

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表单。

使用PouchDBHTML表单

我们将基于此表单建立数据库结构。

设计模式(各种)

PouchDB的有趣之处在于它具有灵活的架构。 数据库中的每个对象实际上都是一个独立的文档。 PouchDB不使用数据组织的关系模型,因此我们可以根据需要将字段或属性添加到文档中。

PouchDB查询使用MapReduce,而不是SQL /关系数据库的SELECT * FROM tablename语法。 您编写用于过滤和排序数据的函数。 与SQL相比,它需要一些思想上的转变,但是一旦掌握了它,这很容易。 我们稍后会看到一个示例。

添加和更新注释

提交表单后,我们会将注释添加到数据库中。 PouchDB提供了两种保存文档的方法: postput 。 每个方法接受两个参数。

  • document (必填):包含属性及其值的对象。 在这种情况下,它将是表单字段及其值。
  • callback (可选):操作完成时要调用的函数。 它接受两个参数: errorresponse

主要区别在于: 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/pngapplication/pdf
  • callback (可选):操作完成时要调用的函数。 与所有PouchDB回调函数一样,它接受两个参数errorresponse 。 在我们的示例中,我们省略了它。

在此示例中,我们还将onload事件处理程序包装在一个闭包中。 通过闭包,可以从事件处理程序中访问文件属性(例如,使用file.namefile.type )。

现在,我们已经研究了保存便笺和附件,现在让我们来看一下单独和成组地检索记录。

检索所有笔记

如果我们想查看数据库中的注释列表怎么办? 这是PouchDB的allDocs有用的地方。 PouchDB.allDocs允许我们一次检索一批文档。

数据库中所有注释的列表。

allDocs这个名字有点误导。 我们当然可以使用它来检索我们所有的文档。 但是,我们也可以使用它来检索属于一定范围内的文档,或者检索与特定键匹配的文档。 此方法接受两个参数,都不是必需的。

  • options (可选):包含以下一个或多个属性的对象。
    • include_docs (布尔值):为每一行包括整个文档。 当false ,将只返回文档的idrev数。
      * conflicts (布尔值):包括冲突。
    • startkeyendkey :包括带有此范围内的键的文档。
    • 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_rowsoffsetrows 。 我们对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 ,我们已经从搜索表单输入中创建了一个正则表达式对象。 我们将测试我们的notetitlenotetags字段,以查看这些字段是否与我们的正则表达式匹配。 如果是这样,我们将使用emit方法返回notetitleid (是一个时间戳)和修改后的属性。 该emit方法被内置到PouchDB。 顾名思义,它以指定的格式选择并返回指定的属性。 emit的第一个参数成为我们结果的关键。

我们的map函数成为query的第一个参数。 正如您可能已经猜到的那样, query的第二个参数是回调函数。 假设一切正常,我们的response参数将是一个包含三个属性的对象: total_rowsoffsetrows 。 我们要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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值