js的TodoList元件開發
阿新 • • 發佈:2020-08-06
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);
}
}
}