實(shí)戰(zhàn)·基于客戶端存儲(chǔ)的可離線使用web應(yīng)用——myTasks(3)

接上回

上回說到,我們已經(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)聽下回分解
未完。荸百。潮太。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铡买,一起剝皮案震驚了整個(gè)濱河市霎箍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌景埃,老刑警劉巖顶别,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異完慧,居然都是意外死亡剩失,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來演熟,“玉大人,你說我怎么就攤上這事蚕冬∈窃” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵旁蔼,是天一觀的道長棺聊。 經(jīng)常有香客問我,道長限佩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮晕城,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贰锁。我一直安慰自己滤蝠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布锣险。 她就那樣靜靜地躺著所森,像睡著了一般焕济。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晴弃,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天上鞠,我揣著相機(jī)與錄音,去河邊找鬼世曾。 笑死谴咸,一個(gè)胖子當(dāng)著我的面吹牛骗露,可吹牛的內(nèi)容都是我干的血巍。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼柿隙,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼禀崖!你這毒婦竟也來了掀序?” 一聲冷哼從身側(cè)響起惭婿,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤财饥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后钥星,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谦炒,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年缕探,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了还蹲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡潭兽,死狀恐怖山卦,靈堂內(nèi)的尸體忽然破棺而出诵次,到底是詐尸還是另有隱情炫狱,我是刑警寧澤剔猿,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布归敬,位于F島的核電站,受9級(jí)特大地震影響汪茧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呀舔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一媚赖、第九天 我趴在偏房一處隱蔽的房頂上張望珠插。 院中可真熱鬧,春花似錦捻撑、人聲如沸顾患。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膘流。三九已至,卻和暖如春呼股,著一層夾襖步出監(jiān)牢的瞬間彭谁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工则奥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人读处。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓罚舱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親粥脚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子包个,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容