实战·基于客户端存储的可离线使用web应用——myTasks(3)

接上回

上回说到,我们已经完成了应用的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 = '
' + '没有搜索到未完成的任务' + '
' } else { emptyItem.innerHTML = '
' + '没有任务可以展示添加任务' + '
' } taskList.appendChild(emptyItem) }
然后是展示任务列表
let showTask = (task, list) => {
      var newItem = document.createElement('li'),
      checked = (task.complete == 1) ? 'checked="checked"' : ''
      // 插入dom元素
      newItem.innerHTML = '
' + '' + '
' + '
' + '
' + task.desc + '
' + '
' + task.due + '
' + '
' + '
' + '删除' + '
' 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
实现搜索功能
 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)
        })
      }
    }

这时,我们已经完成了应用的所有功能。但是,他还不能离线使用!请听下回分解
未完。。。

你可能感兴趣的:(实战·基于客户端存储的可离线使用web应用——myTasks(3))