JavaScript

纯JS实现TodoList

2023-07-13  本文已影响0人  h2coder

效果图

代办事项.png

列表条目样式

<li>
  <div>
    <input class="chk" type="checkbox">
    <span class="title">事项1</span>
  </div>
  <button>X</button>
</li>

根据数据,渲染列表页面

// 渲染函数,根据数据渲染页面
function render() {
    // 数据转标签字符串数组
    let newArr = todos.map(function (item, index) {
        return `
          <li>
            <div>
              <input data-index="${index}" class="chk" type="checkbox" ${item.finished ? "checked" : ""}>
              <span class="title">${item.title}</span>
            </div>
            <button data-index="${index}">X</button>
          </li>
        `;
    });
    // 拼接每个标签
    let html = newArr.join("");
    // 渲染列表
    ul.innerHTML = html;

    // 统计未完成数量
    let unfinishCountValue = 0;
    // 统计完成数量
    let finishCountValue = 0;
    todos.forEach(function (item, index) {
        if (item.finished) {
            finishCountValue++;
        } else {
            unfinishCountValue++;
        }
    });

    console.log(`${unfinishCountValue}个未完成`);
    console.log(`${finishCountValue}个已完成`);

    unfinishCount.innerText = unfinishCountValue;
    finishCount.innerText = finishCountValue;
}

// 首次渲染
render();

处理键盘事件

    // 监听输入框的键盘事件
    wordInput.addEventListener('keyup', function (e) {
    // 限定只处理回车
    if (e.key === 'Enter') {
    // 添加代办事项到数组中
    let word = wordInput.value;

    // 没有输入内容,不处理
    if (word.trim() === '') {
    return;
    }

    // 插入到数组的最前面
    todos.unshift({
    title: word,
    finished: false
    });

    // 清空输入框
    wordInput.value = '';

    // 同步数据到本地
    syncData2Local();
    // 重新渲染
    render();
    }
});

处理条目点击

// 事件委托,处理条目内的点击
ul.addEventListener('click', function (e) {
  let targetEl = e.target;
  // 通过自定义属性,取出这一行数据的索引
  let index = targetEl.dataset.index;
  // 勾选框
  if (targetEl.classList.contains('chk')) {
    let todo = todos[index];
    // 切换选中和未选中
    todo.finished = !todo.finished;
    // 同步数据到本地
    syncData2Local();
    // 重新渲染
    render();
  } else if (targetEl.tagName === 'BUTTON') {
    // 删除数组中的数据
    todos.splice(index, 1);
    // 同步数据到本地
    syncData2Local();
    // 重新渲染
    render();
  }
});

清理已完成的事项

// 清理已完成事项
cleanFinishLink.addEventListener('click', function (e) {
  // 阻止默认行为
  e.preventDefault();
  // 是否有一个已完成的代办事项
  let hasFinished = false;
  // 删除所有已勾选的事项
  for (let i = todos.length - 1; i >= 0; i--) {
    const todo = todos[i];
    if (todo.finished) {
      hasFinished = true;
      todos.splice(i, 1);
    }
  }
  // 如果没有可清理的已完成代办事项,直接弹窗提示
  if (!hasFinished) {
    alert('没有一个已完成的代办事项可清理');
    return;
  }
  // 同步数据到本地
  syncData2Local();
  // 重新渲染
  render();
});

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport"
    content="width=device-width, initial-scale=1.0,maximum-scale=1,minimum-scale=1,user-scalable=no" />
  <title>04-待办列表.html</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background-color: #ccc;
    }

    ul {
      list-style: none;
    }

    li {
      padding: 20px;
      text-align: left;
      font-size: 30px;
      border-bottom: 1px dashed #ccc;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    li input {
      margin-right: 10px;
    }

    li button {
      display: none;
      padding: 5px;
    }

    li:hover button {
      display: inline-block;
      cursor: pointer;
    }

    /* 兄弟选择器,chk是给checkbox加的类名,当checkbox勾选时,让兄弟元素span加上中划线 */
    .chk:checked+span {
      text-decoration: line-through;
    }

    h1 {
      margin-bottom: 10px;
    }

    /* .chk+span {} */

    .box {
      background-color: #fff;
      width: 60vw;
      padding: 20px 20px 0;
      margin: 50px auto;
    }

    .box .tool input {
      width: 100%;
      height: 50px;
      text-indent: 20px;
      font-size: 20px;
      font-style: italic;
      color: #666;
      font-weight: 700;
    }

    section {
      height: 50px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    a {
      text-decoration-color: #666;
      color: inherit;
    }
  </style>
</head>

<body>
  <div id="app" data-v-app="">
    <div class="box">
      <h1>待办列表</h1>
      <div class="tool">
        <input id="word" autofocus="" type="text" placeholder="请输入代办事项" />
      </div>
      <ul>
        <!-- 
          <li>
            <div>
              <input class="chk" type="checkbox">
              <span class="title">事项1</span>
            </div>
            <button>X</button>
          </li>
       -->
      </ul>
      <section>
        <span><i>0</i> 未完成</span><a href="#">清理 <b>0</b> 已完成</a>
      </section>
    </div>
  </div>
  <script>
    /*
      整个页面分为4个功能,它整体的操作流程如下
        1. 第一是输入框,用于给用户填写具体的待办事项,比如吃饭、睡觉等
        2. 第二是切换待办状态,可以实现**已完成**和**未完成**的状态切换
        3. 第三是删除功能,用户可以点击对应的一个待办选项,实现删除功能
        4. 第四是统计功能,可以显示 **已完成**的待办数量,和 **未完成**的待办数量
        5. 第五是清理已完成功能,可以实现 用户移除 **已完成** 的待办事项
    */
    // 代办事项列表
    const ul = document.querySelector('ul');
    // 代办事项输入框
    const wordInput = document.querySelector('#word');
    // 未完成数量
    const unfinishCount = document.querySelector('section span i');
    // 已完成数量
    const finishCount = document.querySelector('section a b');
    // 清理已完成的a标签
    const cleanFinishLink = document.querySelector('section a');

    // 代办事项数据数组
    /*
      结构示例:
        {
          title: '吃饭',
          finished: false
        }
    */
    const todos = JSON.parse(localStorage.getItem('todos')) || [];

    // 同步数据到本地
    function syncData2Local() {
      localStorage.setItem('todos', JSON.stringify(todos));
    }

    // 渲染函数,根据数据渲染页面
    function render() {
      // 数据转标签字符串数组
      let newArr = todos.map(function (item, index) {
        return `
          <li>
            <div>
              <input data-index="${index}" class="chk" type="checkbox" ${item.finished ? "checked" : ""}>
              <span class="title">${item.title}</span>
            </div>
            <button data-index="${index}">X</button>
          </li>
        `;
      });
      // 拼接每个标签
      let html = newArr.join("");
      // 渲染列表
      ul.innerHTML = html;

      // 统计未完成数量
      let unfinishCountValue = 0;
      // 统计完成数量
      let finishCountValue = 0;
      todos.forEach(function (item, index) {
        if (item.finished) {
          finishCountValue++;
        } else {
          unfinishCountValue++;
        }
      });

      console.log(`${unfinishCountValue}个未完成`);
      console.log(`${finishCountValue}个已完成`);

      unfinishCount.innerText = unfinishCountValue;
      finishCount.innerText = finishCountValue;
    }

    // 监听输入框的键盘事件
    wordInput.addEventListener('keyup', function (e) {
      // 限定只处理回车
      if (e.key === 'Enter') {
        // 添加代办事项到数组中
        let word = wordInput.value;

        // 没有输入内容,不处理
        if (word.trim() === '') {
          return;
        }

        // 插入到数组的最前面
        todos.unshift({
          title: word,
          finished: false
        });

        // 清空输入框
        wordInput.value = '';

        // 同步数据到本地
        syncData2Local();
        // 重新渲染
        render();
      }
    });

    // 事件委托,处理条目内的点击
    ul.addEventListener('click', function (e) {
      let targetEl = e.target;
      // 通过自定义属性,取出这一行数据的索引
      let index = targetEl.dataset.index;
      // 勾选框
      if (targetEl.classList.contains('chk')) {
        let todo = todos[index];
        // 切换选中和未选中
        todo.finished = !todo.finished;

        // 同步数据到本地
        syncData2Local();

        // 重新渲染
        render();
      } else if (targetEl.tagName === 'BUTTON') {
        // 删除数组中的数据
        todos.splice(index, 1);

        // 同步数据到本地
        syncData2Local();
        // 重新渲染
        render();
      }
    });

    // 清理已完成事项
    cleanFinishLink.addEventListener('click', function (e) {
      // 阻止默认行为
      e.preventDefault();

      // 是否有一个已完成的代办事项
      let hasFinished = false;

      // 删除所有已勾选的事项
      for (let i = todos.length - 1; i >= 0; i--) {
        const todo = todos[i];
        if (todo.finished) {
          hasFinished = true;
          todos.splice(i, 1);
        }
      }

      // 如果没有可清理的已完成代办事项,直接弹窗提示
      if (!hasFinished) {
        alert('没有一个已完成的代办事项可清理');
        return;
      }

      // 同步数据到本地
      syncData2Local();
      // 重新渲染
      render();
    });

    // 首次渲染
    render();
  </script>
</body>

</html>
上一篇 下一篇

猜你喜欢

热点阅读