js的TodoList组件开发

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);
        }
    }
}
原文地址:https://www.cnblogs.com/94-Lucky/p/13446085.html