Todos Storage

-- 待办应用(存储版本)

课程概要

本课程主要讲解本地存储,概括了 JavaScript 在 Web 页面中的存储操作的注意事项,包括 保存、获取数据,及 JSON 数据格式的转换。通过本课程,完成一个存储版 Todos 待办应用,大家将对本地数据存储的操作有更深入的了解。

主要包含以下功能:

  • 页面打开时获取本地数据
  • 页面关闭时保存本地数据

知识点

本课程涉及到的主要知识点有:

  1. JSON.stringify 转换为一个 JSON 字符串
let arr = [1, 2, 3, 4]
JSON.stringify(arr);  // "[1, 2, 3, 4]"
  1. JSON.parse 解析 JSON 字符
let str = "[1, 2, 3, 4]";
JSON.parse(str);    // [1, 2, 3, 4]
  1. localStorage.setItem 存储数据
localStorage.setItem("name", "Nicholas");

注意:localStorage 存储格式为字符串,其他格式的数据请 JSON.stringify 一下

  1. localStorage.getItem 读取数据
let name = localStorage.getItem("name");

注意:如果 localStorage 之前被 JSON.stringify 过,需要是用 JSON.parse 还原

实现步骤

  1. 页面关闭时获取 todos 数据,存储到本地
  2. 页面打开时获取本地数据,更新 todos

存储本地数据

  1. 绑定页面卸载事件 saveTodo
  2. 新增 saveTodo 事件
  3. 获取 PAGE.data.todos
  4. 对 PAGE.data.todos 转化为 JSON 字符串
  5. 将 todos 字符串存储在本地存储 localStorage 的 todos 中
const PAGE = {
  ...,
  bind: function() {
    ...,
    // 1. 绑定页面卸载事件 saveTodo
    window.addEventListener('unload',this.saveTodos);
  },
  ...,
  // 2. 新增 saveTodo 事件
  saveTodos: function() {
    // 3. 获取 PAGE.data.todos
    let todos = PAGE.data.todos;
    // 4. 对 PAGE.data.todos 转化为 JSON 字符串
    let todosStr = JSON.stringify(todos);
    // 5. 将 todos 字符串存储在本地存储 localStorage 的 todos 中
    localStorage.setItem('todos',todosStr);
  }
}

PAGE.init();

获取本地数据

  1. 新增 getTodos 事件
  2. 获取本地存储 localStorage 中 todos 的值
  3. 对 todos 进行 JSON 转化,同时如果为空时候显示为 [] 空数组。
  4. 把 todos 设置到 PAGE.data.todos 中
  5. 渲染页面
  6. 在 init 方法中的 render 替换为 getTodos 方法,获取数据后再 render
const PAGE = {
  ...,
  init: function() {
    this.bind();
    // 6. 在 init 方法中的 render 替换为 getTodos 方法,获取数据后再 render
    this.getTodos();
  },
  ...,
  // 1. 新增 getTodos 事件
  getTodos: function() {
    // 2. 获取本地存储 localStorage 中 todos 的值
    let todos = localStorage.getItem('todos');
    // 3. 对 todos 进行 JSON 转化,同时如果为空时候显示为 [] 空数组。
    todos = JSON.parse(todos) || [];
    // 4. 把 todos 设置到 PAGE.data.todos 中
    PAGE.data.todos = todos;
    // 5. 渲染页面
    this.render();
  },
}

PAGE.init();

代码示例

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Todes</title>
  <style type="text/css">
    *{
      margin: 0;
      padding: 0;
      font: 24px Helvetica, Arial, sans-serif;
      font-weight: 400;
      box-sizing: border-box;
      color: #666;
    }
    .todos-container{
      margin: 100px auto;
      width: 550px;
    }
    .todos-title{
      font-size: 100px;
      text-align: center;
      font-weight: 100;
      color: rgba(175, 47, 47, 0.15);
      margin-bottom: 20px;
    }
    .todos-content{
      position: relative;
      background: #fff;
      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
    }

    .todos-input{
      display: block;
      width: 100%;
      padding: 16px 16px 16px 60px;
      border: none;
      outline: none;
      font-weight: 200;
    }
    .todo-item{
      padding: 16px;
      display: flex;
      border-top: 1px solid #e4e4e4;
    }
    .todo-item-hd{
      position: relative;
      width: 28px;
      height: 28px;
      border: 1px solid #e4e4e4;
      border-radius: 50%;
      margin-right: 16px;
      cursor: pointer;
    }
    .todo-item-bd{
      flex: 1;
      color: #333;
    }
    .todo-item-ft{
      display: none;
      cursor: pointer;
    }
    .todo-item:hover .todo-item-ft{
      display: inline-block;
      color: #999;
    }
    .todo-item.active .todo-item-hd:before{
      content: '';
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%,-50%);
      width: 12px;
      height: 12px;
      border-radius: 50%;
      background: rgba(175, 47, 47, 0.15);
    }
    .todo-item.active .todo-item-bd{
      text-decoration: line-through;
      color: #999;
    }

    /*筛选*/
    .todos-filter{
      border-top: 1px solid #e4e4e4;
      padding: 8px 16px;
      text-align: center;
    }
    .todos-filter .filter-item{
      display: inline-block;
      margin: 0 10px;
      padding: 4px 8px;
      font-size: 14px;
      color: #999;
      border: 1px solid #999;
      border-radius: 4px;
      cursor: pointer;
    }
    .todos-filter .filter-item.active{
      border-color: #333;
      color: #333;
    }
  </style>
</head>
<body>
  <div class="todos-container">
    <h1 class="todos-title">todos</h1>
    <div class="todos-content">
      <div class="todos-input-cell">
        <input id="todo-input" class="todos-input" type="text" name="todo" placeholder="请输入计划事项">
      </div>
      <div id="todos-list" class="todos-list">
        <div class="todo-item">
          <div class="todo-item-hd"></div>
          <div class="todo-item-bd">打一瓶酱油</div>
          <div class="todo-item-ft">x</div>
        </div>
        <div class="todo-item active">
          <div class="todo-item-hd"></div>
          <div class="todo-item-bd">跑步800米</div>
          <div class="todo-item-ft">x</div>
        </div>
      </div>
      <div class="todos-filter" id="todos-filter">
        <span class="filter-item active">全部</span>
        <span class="filter-item">待办</span>
        <span class="filter-item">已办</span>
      </div>
    </div>
  </div>

  <script type="text/javascript">
    const PAGE = {
      data: {
        todos: [{
          title: '打一瓶酱油',
          completed: false
        },{
          title: '跑步800米',
          completed: true
        }],
        filter: 1,
        filters: {
          1: '全部',
          2: '待办',
          3: '已办',
        }
      },
      init: function() {
        this.bind();
        this.getTodos();
      },
      bind: function() {
        let input = document.getElementById('todo-input');
        let todoList = document.getElementById('todos-list');
        let todoFilter = document.getElementById('todos-filter');
        input.addEventListener('keyup',this.addTodo);
        this.onEventLister(todoList,'click','todo-item-hd',this.toggleTodo);
        this.onEventLister(todoList,'click','todo-item-ft',this.removeTodo);
        this.onEventLister(todoFilter,'click','filter-item',this.filterTodo);
        window.addEventListener('unload',this.saveTodos);
      },
      getTodos: function() {
        let todos = localStorage.getItem('todos');
        todos = JSON.parse(todos) || [];
        PAGE.data.todos = todos;
        this.render();
      },
      saveTodos: function() {
        let todos = PAGE.data.todos;
        let todosStr = JSON.stringify(todos);
        localStorage.setItem('todos',todosStr);
      },
      onEventLister: function(parentNode,action,childClassName,callback) {
        parentNode.addEventListener(action,function(e){
          e.target.className.indexOf(childClassName) >= 0 && callback(e);
        })
      },
      render: function() {
        let todos = this.data.todos;
        let filters = this.data.filters;
        let filter = this.data.filter;
        todos.forEach((data,index)=> data.index = index);

        let showTodos;
        switch (filter) {
          case 2:
            showTodos = todos.filter( data => !data.completed );
            break;
          case 3:
            showTodos = todos.filter( data => data.completed );
            break;
          default:
            showTodos = todos;
            break
        }

        let todosElement = showTodos.map((data)=>{
          return `
            <div class="todo-item ${data.completed ? 'active' : ''}" data-index="${data.index}">
              <div class="todo-item-hd"></div>
              <div class="todo-item-bd">${data.title}</div>
              <div class="todo-item-ft">x</div>
            </div>
          `
        }).join('');

        let filterElement = Object.keys(filters).map( key => {
          return `<span class="filter-item ${filter == key ? 'active' : ''}" data-id="${key}">${filters[key]}</span>`
        }).join('');

        let todoList = document.getElementById('todos-list');
        let todoFilter = document.getElementById('todos-filter');
        todoList.innerHTML = todosElement;
        todoFilter.innerHTML = filterElement;
      },
      addTodo: function(e){
        let value = this.value.trim();
        if (e.which !== 13 || !value) {
          return;
        }
        let todos = PAGE.data.todos;
        todos.push({
          title: value,
          completed: false
        })
        this.value = '';
        PAGE.render();
      },
      toggleTodo: function(e) {
        let todos = PAGE.data.todos;
        let todoItem = e.target.parentNode;
        let index = todoItem.dataset.index;
        todos[index].completed = !todos[index].completed;
        PAGE.render();
      },
      removeTodo: function(e) {
        let todos = PAGE.data.todos;
        let todoItem = e.target.parentNode;
        let index = todoItem.dataset.index;
        todos.splice(index,1);
        PAGE.render();
      },
      filterTodo: function(e) {
        let filterItem = e.target;
        let filter = filterItem.dataset.id;
        PAGE.data.filter = Number(filter);
        PAGE.render();
      }
    }

    PAGE.init();
  </script>
</body>
</html>