1. 程式人生 > 實用技巧 >js的TodoList元件開發

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,
            width:"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',
                width:'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",
                width:"500px",
                overflow:"hidden"
            });
            span.textContent=list[i];
            li.appendChild(span);
            let a=Utils.ce("a",{
                position: "absolute",
                top: "2px",
                right: "5px",
                display: "inline-block",
                width: "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",
            width:"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",
            width: "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",
            width: "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",{
            width:"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(/\n/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);
        }
    }
}