接上回
上回说到,我们已经完成了应用的1/3,目前应用已经能够实现保存用户的设置。
现在,我们动手开始完成剩下的模块之一——任务列表页。
本章我们主要实现的功能
- 创建并连接到一个indexedDB数据库
- 实现模糊搜索功能
- 实现增,删,查,改
- 删除indexedDB中单个,全部数据
首先介绍
任务开始
首先我们侦测浏览器对数据库的支持,我们已经创建了三个变量展示浏览器的兼容能力(恐怖的兼容问题)
const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB || false,
IDBKeyRange = window.IDBKeyRange || window.webkitIDKeyRange || window.mozIDKeyRange || window.msIDKeyRange || false,
// webSQL对象并未实现为window成员,可以侦察window成员的openDatabase是否存在以检测web sql
webSQLSupport = ('openDatabase' in window)
接下来我们会创建一个indexedDB数据库,连接该数据库,并创建一个存储对象。
- 首先,我们调用一个open方法
indexedDB.open(DBName, version)是一个异步方法,接受两个参数第一个参数为数据库名,第二个参数为版本号,在不指定的情况下,默认的版本号是1,在需要更新数据库的schema(模式)时,需要更新版本号。此时我们指定一个高于之前版本的版本号,就会触发onupgradeneeded事件。(该事件会在创建数据库,或者数据库版本号增加时触发) - 在open方法成功之后,会触发upgradeNeeded事件
我们根据回掉函数的事件参数,获取数据库实例,并且创建一个存储对象。
let openDB = () => {
// 判断是否支持indexedDB
if (indexedDB) {
// open是一个异步方法,当请求开始后,open会立刻返回一个IDBrequest对象,如果数据库不存在,则创建一个,然后创建一个与该数据库的连接
const request = indexedDB.open('tasks', 1),
upgradeNeeded = ('onupgradeneeded' in request)
// 成功创建连接之后的回调函数
request.onsuccess = function (e) {
db = e.target.result
// 排除异己
if (!upgradeNeeded && db.version != '1') {
// 只有在版本变更时才能创建存储对象
var setVersionRequest = db.setVersion('1')
setVersionRequest.onsuccess = function (e) {
// createObjectStore 方法接受两个参数 第一个是仓库名 第二个是可选参数 keyPath 为主键
var objectStore = db.createObjectStore('tasks', {
// 主键名
keyPath: 'id'
})
// creataIndex 方法接受三个参数,第一个参数为索引名,第二个为数据对象的属性,第三个为可选参数
objectStore.createIndex('desc', 'descUpper', {
unique: false
})
loadTasks()
}
} else {
loadTasks()
}
}
request.onerror = (e) => {
console.log('数据库连接失败')
}
// 首次创建时会触发该事件
if (upgradeNeeded) {
request.onupgradeneeded = function (e) {
// 获取数据库实例
db = e.target.result
// 创建存储对象
var objectStore = db.createObjectStore('tasks', {
keyPath: 'id'
})
objectStore.createIndex('desc', 'descUpper', {
unique: false
})
}
}
// 判断是是否支持webSQL
} else if (webSQLSupport) {
db = openDatabase('tasks', '1.0', 'Task database', (5*1024*1024))
db.transaction(function (tx) {
const sql = 'CREATE TABLE IF NOT EXISTS tasks (' +
'id INTEGER PRIMARY KEY ASC,' +
'desc TEXT,' +
'due DATATIME,' +
'complete BOOLEAN' +
')'
tx.executeSql(sql, [], loadTasks)
})
}
}
接下来,创建我们的任务列表
在实际的使用中,一定存在列表为空,或者未搜索到数据的情况
首先我们先处理此类情况
let createEmptyItem = (query, taskList) => {
// query参数对应的是搜索结果 taskList是li的父节点ul
var emptyItem = document.createElement('li')
if (query.length > 0) {
emptyItem.innerHTML = '<div class="item_title"></div>' +
'没有搜索到未完成的任务' +
'</div>'
} else {
emptyItem.innerHTML = '<div class="item_title"></div>' +
'没有任务可以展示<a href="#add">添加任务</a>' +
'</div>'
}
taskList.appendChild(emptyItem)
}
然后是展示任务列表
let showTask = (task, list) => {
var newItem = document.createElement('li'),
checked = (task.complete == 1) ? 'checked="checked"' : ''
// 插入dom元素
newItem.innerHTML = '<div class="item_complete">' +
'<input type="checkbox" name="item_complete" id=chk_'+
task.id+ " " + checked +
'>' +
'</div>' +
'<div class="item_body">' +
'<div class=item_title>' + task.desc + '</div>' +
'<div class=item_due>' + task.due + '</div>' +
'</div>' +
'<div class="item_delete">' +
'<a href="#" id=del_' + task.id + ' class="delete_button">删除</a>' +
'</div>'
list.appendChild(newItem)
// 将任务标记为已完成
let markAsComplete = (e) => {
e.preventDefault()
let updatedTask = {
id: task.id,
desc: task.desc,
descUpper: task.desc.toUpperCase(),
complete: e.target.checked
}
updateTask(updatedTask)
}
let remove = (e) => {
e.preventDefault()
if (confirm('确认删除该任务?', '删除')) {
deleteTask(task.id)
}
}
document.querySelector('#chk_' + task.id).onchange = markAsComplete
document.querySelector('#del_' + task.id).onclick = remove
}
万事具备,我们现在需要从indexedDB中查询数据,再配合之前的方法,将查询到的数据展示在页面上
搜索indexedDB数据库
indexedDB需要创建一个事物来定义一个要扫描的对象存储数组,以及要执行的事物类型。indexedDB提供了两种选择, “只读”与“读与写”,对于搜索来说,事物类型应该为“只读”,设置为“读与写”会降低搜索的效率。
接下来,开始我们的操作吧
let loadTasks = (q) => {
const taskList = document.querySelector('#task_list'),
query = q || ''
// 列表置空,每次搜索都重新置空列表,然后重新添加dom元素,这是最简单的方法,当然有很大的优化空间
taskList.innerHTML = ''
if (indexedDB) {
var tx = db.transaction(['tasks'], 'readonly'), //创建事物
objectStore = tx.objectStore('tasks'),
cursor,
i = 0
if (query.length > 0) {
var index = objectStore.index('desc'),
upperQ = query.toUpperCase(),
keyRange = IDBKeyRange.bound(upperQ, upperQ + 'z')
cursor = index.openCursor(keyRange)
} else {
cursor = objectStore.openCursor()
}
cursor.onsuccess = (e) => {
var result = e.target.result
if (result === null) return
i++
showTask(result.value, taskList)
result['continue']()
}
tx.oncomplete = (e) => {
if (i === 0) {
createEmptyItem(query, taskList)
}
}
// 对webSQL的支持
} else if (webSQLSupport) {
db.transaction((tx) => {
let sql, args = []
if (query.length > 0) {
sql = 'SELECT * FROM tasks WHERE desc LIKE ?'
args[0] = query + '%'
} else {
sql = 'SELECT * FROM tasks'
}
var iterateRows = (tx, results) => {
var i = 0,
len = results.rows.length
for (; i<len;i++) {
showTask(results.rows.item(i), taskList)
}
if (len === 0) {
createEmptyItem(query, taskList)
}
}
tx.executeSql(sql, args, iterateRows)
})
}
}
实现搜索功能
let searchTasks = (e) => {
e.preventDefault()
var query = searchForm.query.value
if (query.length > 0) {
loadTasks(query)
} else {
loadTasks()
}
}
settingForm.addEventListener('submit', saveSettings, false)
目前,我们的搜索功能已经能够正常使用了
实现添加任务
这次我们的事物类型就应该为readwrite了
let insertTask = (e) => {
e.preventDefault()
var desc = addForm.desc.value,
dueDate = addForm.due_date.value
if (!dueDate || !desc) {
alert('请将描述,时间填写完整')
return
}
if (desc && dueDate) {
var task = {
id: new Date().getTime(),
desc: desc,
descUpper: desc.toUpperCase(),
due: dueDate,
complete: false
}
}
if (indexedDB) {
let tx = db.transaction(['tasks'], 'readwrite')
var objectStore = tx.objectStore('tasks')
// indexedDB提供的api
var request = objectStore.add(task)
// 每次更新完成都更新视图
tx.oncomplete = updateView
} else if (webSQLSupport) {
db.transaction ((tx) => {
let sql = 'INSERT INTO tasks (desc, due, complete)' +
'VALUES (?,?,?)',
args = [task.desc, task.due, task.complete]
tx.executeSql(sql, args, updateView)
})
}
}
添加操作完成之后,我们应用的基本功能就完成了。
还差最后一步——删除操作
在用户删除点击删除按钮时,删除单个任务
在用户重置的时候,删除indexedDB,及localStorage中的全部数据。
从indexedDB中删除任务。
let deleteTask = (id) => {
if (indexedDB) {
let tx = db.transaction(['tasks'], 'readwrite')
let objectStore = tx.objectStore('tasks')
var request = objectStore.delete(id)
tx.oncompelete = loadTasks
} else if (webSQLSupport) {
db.transaction((tx) => {
let sql = 'DELETE FROM tasks WHERE id = ?',
args = [id]
tx.executeSql(sql, args)
})
}
updateView()
}
let dropDatabase = () => {
if (indexedDB) {
let delDBRequest = indexedDB.deleteDatabase('tasks')
delDBRequest.onsuccess = window.location.reload()
} else if (webSQLSupport) {
db.transaction((tx) => {
let sql = 'DELETE FROM tasks'
tx.executeSql(sql, [], loadTasks)
})
}
}
这时,我们已经完成了应用的所有功能。但是,他还不能离线使用!请听下回分解
未完。。。