接上回
上回說到,我們已經(jīng)完成了應(yīng)用的1/3线欲,目前應(yīng)用已經(jīng)能夠?qū)崿F(xiàn)保存用戶的設(shè)置汽摹。
現(xiàn)在,我們動(dòng)手開始完成剩下的模塊之一——任務(wù)列表頁趴泌。
本章我們主要實(shí)現(xiàn)的功能
- 創(chuàng)建并連接到一個(gè)indexedDB數(shù)據(jù)庫
- 實(shí)現(xiàn)模糊搜索功能
- 實(shí)現(xiàn)增拉庶,刪,查氏仗,改
- 刪除indexedDB中單個(gè),全部數(shù)據(jù)
首先介紹
任務(wù)開始
首先我們偵測(cè)瀏覽器對(duì)數(shù)據(jù)庫的支持呐舔,我們已經(jīng)創(chuàng)建了三個(gè)變量展示瀏覽器的兼容能力(恐怖的兼容問題)
const indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB || false,
IDBKeyRange = window.IDBKeyRange || window.webkitIDKeyRange || window.mozIDKeyRange || window.msIDKeyRange || false,
// webSQL對(duì)象并未實(shí)現(xiàn)為window成員珊拼,可以偵察window成員的openDatabase是否存在以檢測(cè)web sql
webSQLSupport = ('openDatabase' in window)
接下來我們會(huì)創(chuàng)建一個(gè)indexedDB數(shù)據(jù)庫,連接該數(shù)據(jù)庫搁进,并創(chuàng)建一個(gè)存儲(chǔ)對(duì)象昔头。
- 首先,我們調(diào)用一個(gè)open方法
indexedDB.open(DBName, version)是一個(gè)異步方法莱革,接受兩個(gè)參數(shù)第一個(gè)參數(shù)為數(shù)據(jù)庫名讹开,第二個(gè)參數(shù)為版本號(hào),在不指定的情況下闹击,默認(rèn)的版本號(hào)是1成艘,在需要更新數(shù)據(jù)庫的schema(模式)時(shí)淆两,需要更新版本號(hào)。此時(shí)我們指定一個(gè)高于之前版本的版本號(hào)秋冰,就會(huì)觸發(fā)onupgradeneeded事件剑勾。(該事件會(huì)在創(chuàng)建數(shù)據(jù)庫,或者數(shù)據(jù)庫版本號(hào)增加時(shí)觸發(fā)) - 在open方法成功之后暂刘,會(huì)觸發(fā)upgradeNeeded事件
我們根據(jù)回掉函數(shù)的事件參數(shù)洲赵,獲取數(shù)據(jù)庫實(shí)例,并且創(chuàng)建一個(gè)存儲(chǔ)對(duì)象芝发。
let openDB = () => {
// 判斷是否支持indexedDB
if (indexedDB) {
// open是一個(gè)異步方法,當(dāng)請(qǐng)求開始后,open會(huì)立刻返回一個(gè)IDBrequest對(duì)象苛谷,如果數(shù)據(jù)庫不存在腹殿,則創(chuàng)建一個(gè),然后創(chuàng)建一個(gè)與該數(shù)據(jù)庫的連接
const request = indexedDB.open('tasks', 1),
upgradeNeeded = ('onupgradeneeded' in request)
// 成功創(chuàng)建連接之后的回調(diào)函數(shù)
request.onsuccess = function (e) {
db = e.target.result
// 排除異己
if (!upgradeNeeded && db.version != '1') {
// 只有在版本變更時(shí)才能創(chuàng)建存儲(chǔ)對(duì)象
var setVersionRequest = db.setVersion('1')
setVersionRequest.onsuccess = function (e) {
// createObjectStore 方法接受兩個(gè)參數(shù) 第一個(gè)是倉庫名 第二個(gè)是可選參數(shù) keyPath 為主鍵
var objectStore = db.createObjectStore('tasks', {
// 主鍵名
keyPath: 'id'
})
// creataIndex 方法接受三個(gè)參數(shù)刻炒,第一個(gè)參數(shù)為索引名自沧,第二個(gè)為數(shù)據(jù)對(duì)象的屬性,第三個(gè)為可選參數(shù)
objectStore.createIndex('desc', 'descUpper', {
unique: false
})
loadTasks()
}
} else {
loadTasks()
}
}
request.onerror = (e) => {
console.log('數(shù)據(jù)庫連接失敗')
}
// 首次創(chuàng)建時(shí)會(huì)觸發(fā)該事件
if (upgradeNeeded) {
request.onupgradeneeded = function (e) {
// 獲取數(shù)據(jù)庫實(shí)例
db = e.target.result
// 創(chuàng)建存儲(chǔ)對(duì)象
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)
})
}
}
接下來,創(chuàng)建我們的任務(wù)列表
在實(shí)際的使用中孝偎,一定存在列表為空衣盾,或者未搜索到數(shù)據(jù)的情況
首先我們先處理此類情況
let createEmptyItem = (query, taskList) => {
// query參數(shù)對(duì)應(yīng)的是搜索結(jié)果 taskList是li的父節(jié)點(diǎn)ul
var emptyItem = document.createElement('li')
if (query.length > 0) {
emptyItem.innerHTML = '<div class="item_title"></div>' +
'沒有搜索到未完成的任務(wù)' +
'</div>'
} else {
emptyItem.innerHTML = '<div class="item_title"></div>' +
'沒有任務(wù)可以展示<a href="#add">添加任務(wù)</a>' +
'</div>'
}
taskList.appendChild(emptyItem)
}
然后是展示任務(wù)列表
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)
// 將任務(wù)標(biāo)記為已完成
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('確認(rèn)刪除該任務(wù)?', '刪除')) {
deleteTask(task.id)
}
}
document.querySelector('#chk_' + task.id).onchange = markAsComplete
document.querySelector('#del_' + task.id).onclick = remove
}
萬事具備势决,我們現(xiàn)在需要從indexedDB中查詢數(shù)據(jù)徽龟,再配合之前的方法,將查詢到的數(shù)據(jù)展示在頁面上
搜索indexedDB數(shù)據(jù)庫
indexedDB需要?jiǎng)?chuàng)建一個(gè)事物來定義一個(gè)要掃描的對(duì)象存儲(chǔ)數(shù)組传透,以及要執(zhí)行的事物類型极颓。indexedDB提供了兩種選擇, “只讀”與“讀與寫”兵琳,對(duì)于搜索來說,事物類型應(yīng)該為“只讀”者春,設(shè)置為“讀與寫”會(huì)降低搜索的效率清女。
接下來,開始我們的操作吧
let loadTasks = (q) => {
const taskList = document.querySelector('#task_list'),
query = q || ''
// 列表置空,每次搜索都重新置空列表拴袭,然后重新添加dom元素曙博,這是最簡(jiǎn)單的方法,當(dāng)然有很大的優(yōu)化空間
taskList.innerHTML = ''
if (indexedDB) {
var tx = db.transaction(['tasks'], 'readonly'), //創(chuàng)建事物
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)
}
}
// 對(duì)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)
})
}
}
實(shí)現(xiàn)搜索功能
let searchTasks = (e) => {
e.preventDefault()
var query = searchForm.query.value
if (query.length > 0) {
loadTasks(query)
} else {
loadTasks()
}
}
settingForm.addEventListener('submit', saveSettings, false)
目前,我們的搜索功能已經(jīng)能夠正常使用了
實(shí)現(xiàn)添加任務(wù)
這次我們的事物類型就應(yīng)該為readwrite了
let insertTask = (e) => {
e.preventDefault()
var desc = addForm.desc.value,
dueDate = addForm.due_date.value
if (!dueDate || !desc) {
alert('請(qǐng)將描述,時(shí)間填寫完整')
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)
})
}
}
添加操作完成之后逝她,我們應(yīng)用的基本功能就完成了黔宛。
還差最后一步——?jiǎng)h除操作
在用戶刪除點(diǎn)擊刪除按鈕時(shí)擒贸,刪除單個(gè)任務(wù)
在用戶重置的時(shí)候,刪除indexedDB介劫,及l(fā)ocalStorage中的全部數(shù)據(jù)座韵。
從indexedDB中刪除任務(wù)。
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)
})
}
}
這時(shí)宦棺,我們已經(jīng)完成了應(yīng)用的所有功能黔帕。但是,他還不能離線使用呐芥!請(qǐng)聽下回分解
未完。荸百。潮太。