本文同时发布于知乎专栏:前端指南
转载需提前联系作者,未经允许不得转载。
最近比较忙,这两天终于抽时间写了我们系列的第二篇文章,大家有什么意见和建议欢迎评论
本来是想再给大家详细介绍一下chrome扩展的许多文档细节和一些定义,后来考虑到这个扩展本身的意义就是在于做出应用,过多的介绍API反而会让大家失去兴趣,故而,今天给大家带来就是一个基于Chrome的一个ToDoList的纯前端小玩具。
同学们在跟着我把所有代码都实现之后,会对js的ES6、基本DOM操作,事件监听,逻辑数据分离思想以及Chrome的本地存储都会有一个比较大的进步
本系列课程源码都在我的github上:miaoihan/chrome-extensions
前期准备
上节课已经介绍过改文件里各个文件的作用了,这次的应用并没有多出任何的文件,都是在基础之上做的代码的扩充。
app.js:TodoList的主文件
background.js:后台数据通信(后来版本中用chrome.storage替换了background的数据传递)
Task.js:task类,包含task属性和方法
在介绍代码之前,我们先理一下我们的思路,我们要实现一个可以增删查改的任务清单,最后实现类似于滴答清单这样的一个效果。
我们需要做的功能:
在打开该应用之前,需要获取所有task信息,并分别展现到两个列表里(查)
在文本框能输入需要完成的任务,回车后保存任务信息到浏览器中,任务跳到清单列表,状态是未完成(增)
在勾选清单列表里的某一项后,该任务跳转到已完成列表,状态是已完成(改)
同样在勾选已完成列表中某项,可以跳到清单列表。
可以修改未完成的任务内容和删除内容(删、改)
这样一个看似很简单的应用做起来其实背后也有一套挺复杂的思想,我们再不借用任何外部库的情况下独立完成该应用。
起步
编写todo.html 前端页面
页面部分没啥好讲的,大家自己动手,先简单布局一个版本,类似于这样:
当前版本并不支持在html页面里写js代码,不是不推荐,是不支持,所以大家必须把文件单独出来在页面中引入
编写app.js 主文件
首先我们完成文本框回车添加的功能,代码如下
// 监听回车
$('task-input').addEventListener('keyup', function(event) {
if (event.keyCode == "13") {
let content = $('task-input').value
let taskItemHtml =
`
${task}
`
$('task-list').innerHTML += taskItemHtml
$('task-input').value = ''
}
})
这里keyCode是是数字键盘的键码值,13对应回车,JavaScript Event KeyCodes 这个网站你可以找到所有的键盘对应值。
这里的代码同学们可能会问了,你不是说不用任何库么,怎么又有“$‘’了,其实这个‘’$‘’是我自己定义的简版选择器,只是方便id选择的。
let $ = function(id){
return document.getElementById(id)
}
$('task-input').addEventListener('keyup', function)
这里的addEventListener是添加监听器的意思,当我们发生了'keyup'即键盘抬起事件后就会触发一个function,我们这里暂时的思路是直接操作视图,这里的${}这种写法是ES6的字面量的写法,免去了各种加号拼接字符串了,非常方便。在我们成功插入了一个html片段之后,我们再把input里的内容给清空,最后的效果是这样的:
那么我们前面为什么要说是暂时的思路呢,因为现阶段已经不推荐直接操作DOM了,现在的大部分框架已经不用去关系操作DOM,数据和view是实时更新的,即双向的数据流动。
在完成了视图操作之后,我们添加的数据并没有保存下来,在一般的应用中,这时候的数据保存是和后端服务器进行交互的,也就是刚刚添加的任务会保存在数据库中,我们这个应用不涉及后端部分,所以我们使用HTML5的API localStorage进行数据的存取,但是localStorage并不直接支持对象的存取,所以在操作之前我们需要对数据进行JSON.stringify 和 JSON.parse操作进行格式的转换。
let task = {}; let taskList = []
task.content = content; task.isFinish = false
taskList.push(task)
// 将对象转换成字符串后保存
localStorage.taskList = JSON.stringify(taskList)
正常情况下我们这样存储就已经可以了,再通过taskList = JSON.parse(localStorage.taskList)就可以获取到task对象了,但是。。。localStroage的存储是基于域名的,所以我们的扩展如果直接这样使用,在切换网页的时候域名不一样这个存储空间就是失效了,那这样当然不行了,那么怎么解决呢?
两种方案
一种还是用localStorage,app.js通过runtime.sendMessage和background通信,由background读写扩展所在域(通常是chrome-extension://extension-id/)的localStorage,这时候域相同localStorage值有效,然后再传递给app.js。
app.js:
chrome.runtime.sendMessage('fetchData', function(res){
// res.taskList 即传回来的list
console.log(res);
});
background.js:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
if(message == 'fetchData'){
let taskList = JSON.parse(localStorage.taskList)
sendResponse(taskList);
}
});
通过上面两个文件之间的通信,我们就可以完成数据的存储。
另一种是通过使用chrom.storage这个API完成对数据存储的操作,他的好处有几点:
用户数据可以自动与Chrome同步(使用storage.sync)同步。
content script可以直接访问数据,不需要通过background中转
无痕模式,用户数据也能保存下来
因为是异步操作,所以比localStorage更快
可以直接存储对象
说这么多好处,其实就是让你去用它。使用Chrome存储API必须要在Manifest的permissions中声明"storage",之后才有权限调用。
{
"name": "todo",
...
"permissions": [
"storage"
],
...
}
对于每种储存区域,Chrome又提供了5个方法,分别是get、getBytesInUse、set、remove和clear。
chrome.storage.sync.get('taskList', function(res){
updateView(res.taskList)
});
Chrome存储API提供了2种储存区域,分别是sync和local。两种储存区域的区别在于,sync储存的区域会根据用户当前在Chrome上登陆的Google账户自动同步数据,当无可用网络连接可用时,sync区域对数据的读写和local区域对数据的读写行为一致。
好,那么现在我们的存储逻辑就可以写出来了
chrome.storage.sync.get('taskList', function(res) {
let taskList = res.taskList || []
taskList.push(obj)
chrome.storage.sync.set({taskList:taskList});
});
这里我们先get了一下,因为直接set的话,会把原数据覆盖,这里API并没有类似sql的update操作,所以只能取出来,push进去,再set。
好了,到目前为止,我们已经不怎么优雅的实现了数据的添加和视图的展现,接下来我们,接下来我们。。。下一期见
下期预告:
使用ES6 class分离数据逻辑的处理,把一些公共模块抽象出来。
完成清单的勾选,完成和未完成任务的处理