TodoMVC模板的原生js待办事项卡片

个人待办事项记录簿

简介:

  • 这是基于todoMVC模板的一个记录待办事项页面,模板地址
  • 该模板需要自行通过npm下载初始化页面样式,而且没有各种交互的逻辑代码。
  • 该项目的交互代码是采用原生js结合es6的数组方法实现。
  • 虽然项目要实现的功能多,但实现起来都比较简单,非常适合练手。

实现的功能

  • 待办事项的完成、添加、删除、全选和反选所有待办等功能,单击重新编辑待办功能、实时显示剩余未完成待办的功能、三个按钮的事项筛选功能、将所有事项存储到客户端中、一键清除所有已完成事项功能;
  • 利用单一数据流的特点对项目进行开发。

展示

在这里插入图片描述

具体实现逻辑

  • 事项的渲染(事项的刷新)
    • 这里需要实现事项的初始化渲染和事项的刷新

      • 在li里自定义一个属性来记录当前渲染的这条数据的id,即data-id=${randerData[i].id}

      • li标签里的completed类名是用于控制待办事项是否完成的样式的,如果添加completed类名,那么该li标签就有待办事项完成的css样式,该类名用数据的done属性进行控制,该属性也用于控制input单选框是否选中

      • 初始化渲染只要对数据遍历,然后将拼接的字符串插入页面,

      • 刷新事项就需要每次置空标签和字符串。

// 数据渲染函数
	function rander(data) {
		if (data.length == 0) {
			return;
		}
		let str = '';
		let count = 0;
		let randerData = null;
		oTodoList.innerHTML = '';

		// 对数据进行筛选
		switch (location.hash) {

			// 待办
			case '#/active':
				randerData = data.filter(function (value, index) {
					return !value.done;
				})
				break;

			// 已完成
			case '#/completed':
				randerData = data.filter(function (value, index) {
					return value.done;
				})
				break;
			default:
				randerData = data;
		}




		for (let i = 0; i < randerData.length; i++) {
			str += `
				<li data-id=${randerData[i].id} class=${randerData[i].done ? "completed" : ""} >
					<div class="view">
						<input class="toggle" type="checkbox" ${randerData[i].done ? "checked" : ''}>
						<label>${randerData[i].todo}</label>
						<button class="destroy"></button>
					</div>
					<input class="edit" value="${randerData[i].todo}">
				</li>`
		}

		// 展示剩余未完成待办数量的功能
		randerData.forEach(value => {
			if (value.done == false) {
				count++;
			}
		})

		oTodoCount.innerHTML = count;
		oTodoList.innerHTML = str;
	}

  • 利用事件冒泡原理给事项的父级ul绑定点击事件,然后根据事件对象里记录DOM不同的class类值来实现待办事项的完成、删除、单击编辑待办功能

    • 待办事项的点击完成功能(点击对象为CheckBox单选框)(即事件源对象里记录的DOM的class类为toggle)
      • 待办事项的选中和不选中的切换功能:获取事件源DOM对象的li父级上记录的id值,根据这个id值找到data里的数据,将这条数据的done赋值为事件源DOM对象的checked的值,然后重新渲染修改后data。(checked的值用于控制li标签的class类名completed的有无,class类名值为completed就会让li标签具有待办已完成的样式)然后重新渲染data。
	// 记录当前点击到的li的索引
		let id = e.target.parentElement.parentElement.getAttribute('data-id');
		let temp = data.find(value => value.id == id);

		// 只将完成待办的复选框点击事件冒泡执行
		if (e.target.className == 'toggle') {

			// 待办事项完成,即将点击到的复选框选中,然后再重新渲染
			temp.done = e.target.checked;
			rander(data);
		}
  • 待办事项的点击删除功能(点击对象为删除按钮)(即事件源对象里记录的DOM的class类为destroy)

    • 获取事件源DOM对象的li父级上记录的id值,根据这个id值找到data里的数据,利用indexOf获取到这条数据在data里的索引,然后利用数组方法splice剪切掉这条数据,再重新渲染修改后data。
		// 删除待办功能
		if (e.target.className == 'destroy') {
			data.splice(data.indexOf(temp), 1);

			rander(data);
		}

  • 待办事项的点击重新编辑功能(点击对象为label标签)(即事件源对象里记录的DOM的标签名为LABEL)

    • 给当前点击到的label标签的父级li标签加上editing的class类名(以拼接字符串的方式)。
// 单击可以编辑待办
if (e.target.tagName == 'LABEL') {

	// 待办编辑框展示
	e.target.parentElement.parentElement.className += ' editing';

	// 让文本框自动选中
	let oInput = e.target.parentElement.parentElement.getElementsByClassName('edit')[0];
	oInput.focus();

	// 调整输入框的光标到文字最右边
	oInput.setSelectionRange(-1, -1);
}

- 当修改完成后就需要将这个修改后的值赋值到data数组的todo里:首先需要给class类名为todo-list绑定一个回车事件,再获取到事件源对象上的DOM对象,然后去找父级的li利用getAttridute()来获取到保存的id值,再在data数组里找到这条数据,并将它的todo值,当前这个input框的value值,然后重新渲染data。

	oTodoList.addEventListener('keyup', (e) => {

		// 获取当前选中的重新编辑输入框的索引
		let id = e.target.parentElement.getAttribute('data-id');

		// 根据索引找到待办项li
		let temp = data.find(function (value) {
			return value.id == id;
		})

		// 将重新编辑过的待办项进行赋值
		if (e.target.className == 'edit' && e.keyCode == 13) {
			temp.todo = e.target.value;

			rander(data);
		}
	})

  • 待办事项的全选和反选功能
    • 给toggle-all的label标签绑定change事件,该事件是用于监听input框聚焦和失焦的变化。

    • 只需要将与label绑定的input标签的checked值赋值到data里的每条数据的done的值中,然后重新渲染data,就可以根据一个按钮实现一键全选和反选的功能。


	// 实现待办的全选和反选功能
	oToggleAll.addEventListener('change', () => {
		data.forEach(value => value.done = oToggleAll.checked);

		rander(data)
	})


  • 因为每完成一个待完成都有可能触发全选功能或全不选功能,所以就需要在每次完成一项待办任务时都要对data里的每条数据的done值进行检测,如果所有的事项全部完成就要让input上的checked为true,否则就为false。
// 当先代办完成后判断是否触发全选按钮
if (data.every(value => value.done) == true) {
		oToggleAll.checked = true;
	} else {
		oToggleAll.checked = false;
	}
  • 添加待办功能

    • 给class类名为new-todo的input标签绑定回车事件,然后在data的最后一位添加一个新数据,todo值为输入的input的value值,然后重新渲染data,创建完毕后需要清空input的value值。
	// 添加待办功能
	oNewTodo.addEventListener('keyup', e => {
		if (e.key == 'Enter') {
			let obj = {
				id: data.length == 0 ? 1 : data[data.length - 1].id + 1,
				todo: e.target.value,
				done: false,
			}
			data.push(obj);

				rander(data);

			// 待办创建完毕清除输入框
			e.target.value = '';
		}
	})

  • 3个按钮的筛选功能

    • 3个按钮的样式改变
      • 在这个下载的模板里这几个按钮的功能都是a标签以hash跳转的方式来写的,所以给window绑定监听页面以hash跳转就触发的hashchange事件。
      • 遍历这3个a标签的hash值是否与当前显示页面的hash值相同,相同就要给这个a标签加上一个值为selected的class类名,该类名是用于控制选中状态样式

	// 三个按钮的筛选功能
	window.onhashchange = function () {

		// a标签样式class类名的修改
		Array.from(oFiltersList).forEach(function (value, index) {
			if (value.hash == location.hash) {
				document.getElementsByClassName('selected')[0].className = '';
				oFiltersList[index].className = 'selected';
			}
		})


		// 重新渲染数组
		rander(data);
	}

  • 筛选功能的实现

    • 该功能不需要再去写方法去实现,只需要在事项渲染阶段时就对事项进行根据当前打开页面的hash值来判断需要展示什么类型的数据,然后用数组的filter方法对数组进行筛选。
		// 对数据进行筛选
		switch (location.hash) {

			// 待办
			case '#/active':
				randerData = data.filter(function (value, index) {
					return !value.done;
				})
				break;

			// 已完成
			case '#/completed':
				randerData = data.filter(function (value, index) {
					return value.done;
				})
				break;
			default:
				randerData = data;
		}


  • 一键清除完成事项功能

    • 只需要将data里done状态为false的都筛选出来,然后把这些重新筛选出来的数据进行渲染。
	// 一键清除完成事项功能
	oClearCompleted.onclick = function (e) {
		data = data.filter(function (value) {
			return !value.done;
		})

		rander(data);
	}

  • 将数据存储到客户端功能

    • 以localStorage.setItem(‘todoList’, JSON.stringify(data))的形式将数据存储到localstorage中,并且在每次重新渲染data时都要存储一次
  • 给修改待办的input框绑定一个失焦事件

    • 该事件绑定在class类名为todo-list的标签上,所以不能绑定blur事件,但可用focuout代替。当失焦时将当前li上editing的类名删掉。
// 取消重新编辑待办项的聚焦事件,blur失焦事件无法冒泡,可用focusout代替
oTodoList.addEventListener('focusout', function (e) {
	

	if (e.target.className == 'edit') {

 		// 将li上editing的类名删掉
		let str = e.target.parentElement.getAttribute('class')
		e.target.parentElement.className = str.replace(' editing', '')
	}
})

	rander(data);
}

html部分:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title>Template • TodoMVC</title>
		<link rel="stylesheet" href="node_modules/todomvc-common/base.css">
		<link rel="stylesheet" href="node_modules/todomvc-app-css/index.css">
		<link rel="stylesheet" href="css/app.css">
	</head>
	<body>
		<section class="todoapp">
			<header class="header">
				<h1>todos</h1>
				<input class="new-todo" placeholder="创建一个待办事项" autofocus>
			</header>
			<section class="main">
				<input id="toggle-all" class="toggle-all" type="checkbox">
				<label for="toggle-all">Mark all as complete</label>
				<ul class="todo-list"></ul>
			</section>
			<footer class="footer">
				<span class="todo-count"><strong> 0 </strong> 件未完成</span>
				<ul class="filters">
					<li>
						<a class="selected" href="#/">所有事项</a>
					</li>
					<li>
						<a href="#/active">待办事项</a>
					</li>
					<li>
						<a href="#/completed">完成事项</a>
					</li>
				</ul>
				<button class="clear-completed">清除完成事项</button>
			</footer>
		</section>
		
		<script src="js/app.js"></script>
	</body>
</html>

javaScript部分

window.onload = () => {

	let data;
	let dataStr = localStorage.getItem('todoList');
	// 获取要渲染的数据
	if (dataStr) {
		data = JSON.parse(dataStr);
	} else {
		data = [];
	}

	let oTodoList = document.getElementsByClassName('todo-list')[0];
	let oToggleAll = document.getElementsByClassName('toggle-all')[0];
	let oNewTodo = document.getElementsByClassName('new-todo')[0];
	let oTodoCount = document.getElementsByClassName('todo-count')[0].getElementsByTagName('strong')[0];
	let oFiltersList = document.getElementsByClassName('filters')[0].getElementsByTagName('a');
	let oClearCompleted = document.getElementsByClassName('clear-completed')[0];



	// 数据渲染函数
	function rander(data) {
		if (data.length == 0) {
			return;
		}
		let str = '';
		let count = 0;
		let randerData = null;
		oTodoList.innerHTML = '';

		// 对数据进行筛选
		switch (location.hash) {

			// 待办
			case '#/active':
				randerData = data.filter(function (value, index) {
					return !value.done;
				})
				break;

			// 已完成
			case '#/completed':
				randerData = data.filter(function (value, index) {
					return value.done;
				})
				break;
			default:
				randerData = data;
		}




		for (let i = 0; i < randerData.length; i++) {
			str += `
				<li data-id=${randerData[i].id} class=${randerData[i].done ? "completed" : ""} >
					<div class="view">
						<input class="toggle" type="checkbox" ${randerData[i].done ? "checked" : ''}>
						<label>${randerData[i].todo}</label>
						<button class="destroy"></button>
					</div>
					<input class="edit" value="${randerData[i].todo}">
				</li>`
		}

		// 展示剩余未完成待办数量的功能
		randerData.forEach(value => {
			if (value.done == false) {
				count++;
			}
		})

		oTodoCount.innerHTML = count;
		oTodoList.innerHTML = str;
	}




	// 给ul绑定事件,将li里的事件冒泡执行
	oTodoList.addEventListener('click', (e) => {

		// 记录当前点击到的li的索引
		let id = e.target.parentElement.parentElement.getAttribute('data-id');
		let temp = data.find(value => value.id == id);

		// 只将完成待办的复选框点击事件冒泡执行
		if (e.target.className == 'toggle') {

			// 待办事项完成,即将点击到的复选框选中,然后再重新渲染
			temp.done = e.target.checked;

			// 当先代办完成后判断是否触发全选按钮
			if (data.every(value => value.done) == true) {
				oToggleAll.checked = true;
			} else {
				oToggleAll.checked = false;
			}

			// 当数据发生修改就要把数据存储到localStorage中
			localStorage.setItem('todoList', JSON.stringify(data));
			rander(data);
		}


		// 删除待办功能
		if (e.target.className == 'destroy') {
			data.splice(data.indexOf(temp), 1);

			// 当数据发生修改就要把数据存储到localStorage中
			localStorage.setItem('todoList', JSON.stringify(data));
			rander(data);
		}


		// 单击可以编辑待办
		if (e.target.tagName == 'LABEL') {

			// 待办编辑框展示
			e.target.parentElement.parentElement.className += ' editing';

			// 让文本框自动选中
			let oInput = e.target.parentElement.parentElement.getElementsByClassName('edit')[0];
			oInput.focus();

			// 调整输入框的光标到文字最右边
			oInput.setSelectionRange(-1, -1);
		}
	})




	oTodoList.addEventListener('keyup', (e) => {

		// 获取当前选中的重新编辑输入框的索引
		let id = e.target.parentElement.getAttribute('data-id');

		// 根据索引找到待办项li
		let temp = data.find(function (value) {
			return value.id == id;
		})

		// 将重新编辑过的待办项进行赋值
		if (e.target.className == 'edit' && e.keyCode == 13) {
			temp.todo = e.target.value;

			// 当数据发生修改就要把数据存储到localStorage中
			localStorage.setItem('todoList', JSON.stringify(data));
			rander(data);
		}
	})


	// 实现待办的全选和反选功能
	oToggleAll.addEventListener('change', () => {
		data.forEach(value => value.done = oToggleAll.checked);

		// 当数据发生修改就要把数据存储到localStorage中
		localStorage.setItem('todoList', JSON.stringify(data));
		rander(data)
	})




	// 添加待办功能
	oNewTodo.addEventListener('keyup', e => {
		if (e.key == 'Enter') {
			let obj = {
				id: data.length == 0 ? 1 : data[data.length - 1].id + 1,
				todo: e.target.value,
				done: false,
			}
			data.push(obj);

			// 当数据发生修改就要把数据存储到localStorage中
			localStorage.setItem('todoList', JSON.stringify(data));
			rander(data);

			// 待办创建完毕清除输入框
			e.target.value = '';
		}
	})



	// 三个按钮的筛选功能
	window.onhashchange = function () {
		// a标签样式class类名的修改
		Array.from(oFiltersList).forEach(function (value, index) {
			if (value.hash == location.hash) {
				document.getElementsByClassName('selected')[0].className = '';
				oFiltersList[index].className = 'selected';
			}
		})


		// 重新渲染数组
		rander(data);
	}





	// 一键清除完成事项功能
	oClearCompleted.onclick = function (e) {
		data = data.filter(function (value) {
			return !value.done;
		})

		// 当数据发生修改就要把数据存储到localStorage中
		localStorage.setItem('todoList', JSON.stringify(data));
		rander(data);
	}

	
// 取消重新编辑待办项的聚焦事件,blur失焦事件无法冒泡,可用focusout代替
oTodoList.addEventListener('focusout', function (e) {
	

	if (e.target.className == 'edit') {

 		// 将li上editing的类名删掉
		let str = e.target.parentElement.getAttribute('class')
		e.target.parentElement.className = str.replace(' editing', '')
	}
})

	rander(data);
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值