js的TodoList组件开发
需求:
1.页面中头部有输入框,下面有待办的内容块,和办完的内容块
2.输入框中输入内容,回车键后,输入框内容清空,代办内容块中添加一条输入内容
3.代办条中有选择框,内容区,删除按钮
4.点击代办条中选择框,说明内容转变为办完事项,代办块中这条内容消失,办完块中有这条语句
5.办完块中点击选择框后,这条内容消失,出现在代办块中
6.本地存储功能,重新打开页面后,之前的事项依旧存在
效果图:
-
初始页面:
-
输入框有内容,回车后
-
点击内容条中选择框
-
点击办完中内容条中的选择框
分析:
- 两个js文件,List是内容条组件,等待数据传入,todoList是外部操作组件,操作后传入数据到List中,List开始执行
- List中的内容条设置点击事件,抛发出事件,在todoList的外部操作中执行.
- 每个页面创建时,要判断localStorage中有无存储的数据,如果有,这些数据直接传入List中,当输入框中输入内容,或者内容区改变后,数据从新进行存储
List.js文件代码
import Utils from "./Utils.js";
export default class List{
mask;
static TODO_LIST_CHANGE="todo_list_change";
static TODO_LIST_REMOVE="todo_list_remove";
constructor(_mask=false){
this.mask=_mask;
this.elem=this.createElem();
this.elem.addEventListener("click",e=>this.clickHandler(e)); //事件委托
}
createElem(){
if(this.elem) return this.elem;
return Utils.ce("ul",{
listStyle:"none",
margin:"0px",
padding:0,
"600px",
});
}
appendTo(parent){
if(typeof parent==="string") parent=document.querySelector(parent);
parent.appendChild(this.elem);
}
setData(list){ //输入数据回车后的数组或者遮罩后的数据的数组
this.elem.innerHTML="";
for(var i=0;i<list.length;i++){ //li中有多选框,文本span,a标签(删除) 多选框和a标签要获取其下标和checked
let li=Utils.ce("li",{
display: "list-item",
textAlign: "-webkit-match-parent",
userDrag: "element",
userSelect: "none",
height: "32px",
lineHeight: "32px",
background: "#fff",
position: "relative",
marginBottom: "10px",
padding: "0 45px",
borderRadius: "3px",
boxShadow: "0 1px 2px rgba(0,0,0,0.07)",
borderLeft:!this.mask ? "5px solid #999" :"5px solid #629A9C",
opacity:!this.mask ? "0.5" : "1", //进行时this.mask为true,值为1
});
let ck=Utils.ce("input",{
position:'absolute',
top:'2px',
left:'10px',
'22px',
height:'22px',
cursor:'pointer',
backgroundColor:'initial',
cursor:'default',
appearance:'checkbox',
boxSizing:'border-box',
margin:'3px 3px 3px 4px',
padding:'initial',
border:'initial',
WebkitWritingMode:'horizontal-tb !important',
textRendering:'auto',
color:'-internal-light-dark(black, white)',
letterSpacing:'normal',
wordSpacing:'normal',
textTransform:'none',
textIndent:'0px',
textShadow:'none',
display:'inline-block',
textAlign:'start',
font:'400 13.3333px Arial',
});
ck.type="checkbox";
ck.index=i; //获取了 多选框的index和checked
ck.checked=!this.mask //进行时传进来的是true,遮罩时传进来的是false,所以去反
li.appendChild(ck);
let span=Utils.ce("span",{
display:"inline-block",
"500px",
overflow:"hidden"
});
span.textContent=list[i];
li.appendChild(span);
let a=Utils.ce("a",{
position: "absolute",
top: "2px",
right: "5px",
display: "inline-block",
"14px",
height: "12px",
borderRadius: "14px",
border: "6px double #FFF",
background: "#CCC",
lineHeight: "14px",
textAlign: "center",
color: "#FFF",
fontWeight: "bold",
fontSize: "14px",
cursor: "pointer",
textDecoration: "underline",
});
a.textContent="-";
a.index=i;
a.checked=this.mask;
li.appendChild(a);
this.elem.appendChild(li);
}
}
clickHandler(e){
if(e.target.constructor!==HTMLInputElement && e.target.constructor!==HTMLAnchorElement) return;//必须点checkbox和a才有用
if(e.target.constructor===HTMLInputElement){ //点击checkbox
var evt=new Event(List.TODO_LIST_CHANGE);
evt.index=e.target.index;
evt.checked=e.target.checked;
document.dispatchEvent(evt); //抛发给document
return;
}
var evt=new Event(List.TODO_LIST_REMOVE);
evt.index=e.target.index;
evt.checked=e.target.checked;
document.dispatchEvent(evt);
}
}
todoList.js文件代码
import Utils from "./Utils.js";
import List from "./List.js";
export default class TodoList{
arr=[]; //存储实例的List 一共就两个
todoArr=[]; //进行时的数据
doneArr=[]; //完成的数据
constructor(){
if(localStorage.todoArr) this.todoArr=JSON.parse(localStorage.todoArr); //localStorage的获取
if(localStorage.doneArr) this.doneArr=JSON.parse(localStorage.doneArr);
this.elem=this.creatElem(); //最外层容器
this.createListCon("正在进行");//创建内容区
this.createListCon("已经完成");
document.addEventListener(List.TODO_LIST_CHANGE,e=>this.todoListChange(e))//点击后抛发出来的两个事件
document.addEventListener(List.TODO_LIST_REMOVE,e=>this.todoListChange(e))
document.addEventListener("keyup",e=>this.keyHandler(e));//回车
}
creatElem(){
if(this.elem) return this.elem;
let div=Utils.ce("div",{ //this.elem
position:"absolute",
"100%",
left:0,
top:0,
right:0,
bottom:0,
backgroundColor:"#CDCDCD"
});
let head=Utils.ce("div",{ //头部容器
position:"relative",
left:0,
right:"0px",
height:"50px",
backgroundColor:"rgba(47,47,47,0.98)",
padding:"0 321px",
})
let label=Utils.ce("label",{ //头部文字
float: "left",
"100px",
lineHeight: "50px",
color: "#DDD",
fontSize: "24px",
cursor: "pointer",
fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif',
})
label.textContent="ToDoList";
this.input=Utils.ce("input",{ //头部输入框
textRendering: "auto",
color: "-internal-light-dark(black, white)",
letterSpacing: "normal",
wordSpacing: "normal",
textTransform: "none",
textShadow: "none",
display: "inline-block",
textAlign: "start",
appearance: "textfield",
backgroundColor: "-internal-light-dark(rgb(255, 255, 255), rgb(59, 59, 59))",
cursor: "text",
marginLeft: "100px",
font: "400 13.3333px Arial",
float: "left",
"360px",
height: "24px",
marginTop: "12px",
textIndent: "10px",
borderRadius: "5px",
boxShadow: "0 1px 0 rgba(255,255,255,0.24), 0 1px 6px rgba(0,0,0,0.45) inset",
border: "none",
padding: "1px 2px",
})
this.input.setAttribute("placeholder","添加ToDo");
head.appendChild(label);
head.appendChild(this.input);
div.appendChild(head);
return div;
}
appendTo(parent){
if(typeof parent==="string") parent=document.querySelector(parent);
parent.appendChild(this.elem);
}
createListCon(title){ //创建内容区
let div=Utils.ce("div",{
"600px",
margin:"auto",
});
let h2=Utils.ce("h2");
h2.textContent=title; //同一个函数执行两次,传参不同,所以页面上有两个h2
let list=new List(title=="正在进行");//如果传参title=="正在进行",创建进行时的li,同一个函数执行两次,两次传参,所以要判断,返回false/true
this.arr.push(list); //arr中有两个数据(进行时和遮罩时的)
if(title==="正在进行")list.setData(this.todoArr); //插入localStorage中的数据
else list.setData(this.doneArr);
div.appendChild(h2);
list.appendTo(div);
this.elem.appendChild(div);
}
keyHandler(e){
if(e.keyCode!==13) return;
if(this.input.value.trim().length===0) return; //判断input中内容不为0
this.todoArr.push(this.input.value);//输入的数据传入到进行时数组中
this.input.value="";//输入框清空
this.arr[0].setData(this.todoArr);
this.saveData();
}
todoListChange(e){
if(e.checked){ //删除进行时e.checked为true,改变进行时,传进去的是false,e.ckecked=!mask,也是true
let arr=this.todoArr.splice(e.index,1);
if(e.type===List.TODO_LIST_CHANGE) this.doneArr.push(arr[0]);
}else{
let arr=this.doneArr.splice(e.index,1);
if(e.type===List.TODO_LIST_CHANGE) this.todoArr.push(arr[0]);
}
this.arr[0].setData(this.todoArr);
this.arr[1].setData(this.doneArr);
this.saveData();
}
saveData(){ //数据存储到本地
localStorage.todoArr=JSON.stringify(this.todoArr);
localStorage.doneArr=JSON.stringify(this.doneArr);
}
}
HTML页面只要new todoList对象,就能实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import TodoList from './js/TodoList.js';
let todo=new TodoList();
todo.appendTo("body");
</script>
</body>
</html>
Utils.js是工具包
export default class Utils{
static time=0;
static ids=0;
static timeManage={};
// 在静态方法中调用的变量都需要写成静态的
// 在静态方法中理论上不能使用this的,我们需要坚决贯彻这个思想
// 实际在静态方法中this就是当前类名
static timeStart(){
if(Utils.time) return;
Utils.time=new Date().getTime();
}
static timeEnd(){
var t=new Date().getTime()-Utils.time;
Utils.time=0;
return t;
}
static ts(){
Utils.ids++;
Utils.timeManage[Utils.ids]=new Date().getTime();
return ids;
}
static te(id){
if(!Utils.timeManage[Utils.id]) return 0;
var t=new Date().getTime()-Utils.timeManage[Utils.id];
delete Utils.timeManage[Utils.id];
return t;
}
static randomColor(){
var col="#";
for(var i=0;i<6;i++){
col+=Math.floor(Math.random()*16).toString(16);
}
return col;
}
static random(min,max){
return Math.floor(Math.random()*(max-min)+min);
}
static ce(type,style,parent){
var elem=document.createElement(type);
if(style){
for(var prop in style){
elem.style[prop]=style[prop];
}
}
if(typeof parent==="string") parent=document.querySelector(parent);
if(parent) parent.appendChild(elem);
return elem;
}
static setStyle(styles){
var style=document.createElement("style");
document.head.appendChild(style);
var styleSheet=document.styleSheets[document.styleSheets.length-1];
for(var prop in styles){
Utils.addCss(styleSheet,prop,styles[prop]);
}
}
static addCss(styleSheet,selector,style){
var str=selector+" {";
for(var prop in style){
var value=style[prop]
prop=prop.replace(/([A-Z])/g,function($1){
return "-"+$1.toLowerCase();
})
str+=prop+":"+value+";"
}
str+=" }";
styleSheet.insertRule(str,styleSheet.cssRules.length);
}
static CSStoString(str){
return str.replace(/(?<=:)(.*?)(?=;)|-[a-z](?=.+:)|;/g,function(item){
if(item===";") return ","
if(item[0]==="-") return item[1].toUpperCase();
return "'"+item.trim()+"'";
});
}
// TODO 将CSS转换为对象
static CSStoObject(str){
str=Utils.CSStoString(str);
return str.split(",").reduce((value,item)=>{
item=item.replace(/
/g,"");
var arr=item.split(":");
arr[0]=arr[0].replace(/s/g,"");
if(arr[1]===undefined) return value;
arr[1]=arr[1].replace(/'/g,"");
value[arr[0]]=arr[1];
return value;
},{})
}
static getCookie(){
return document.cookie.split(/;s*/).reduce((value,item)=>{
var arr=item.split("=");
value[arr[0]]=isNaN(arr[1]) ? arr[1] : Number(arr[1]);
return value;
},{})
}
static getCookieValue(key){
return Utils.getCookie()[key];
}
static setCookie(key,value,date){
if(!date){
document.cookie=`${key}=${value}`;
return;
}
document.cookie=`${key}=${value};expires=${date.toUTCString()}`;
}
static setCookies(obj,date){
for(var key in obj){
Utils.setCookie(key,obj[key],date);
}
}
// TODO 删除Cookie
static removeCookie(key){
Utils.setCookie(key,"",new Date());
}
static clearCookie(){
for(var key in Utils.getCookie()){
Utils.removeCookie(key);
}
}
}