iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

0
自我挑戰組

花三十天找到 JavaScript 沙漠中的綠洲系列 第 35

番外篇(2)一起來做 To Do List!- 實作篇(3)

不知不覺也來到最後一篇啦!

第八步

在 codepen 上可以看到一些酷炫的漢堡選單 code ,但這裡我做的是最陽春的版本:點擊漢堡選單 icon 秀出,點擊選單上的 x 關閉。

const hamburgerMenu = document.querySelector('#hamburger-menu-icon');
const hamburgerExit = document.querySelector('#hamburger-menu-exit');

hamburgerMenu.addEventListener('click',openMenu);
hamburgerExit.addEventListener('click',closeMenu);

function openMenu(){
    let menu = document.getElementById('mobile-filter');
    menu.classList.add('hamburgerMenu-active');
}
function closeMenu(){
    hamburgerExit.classList.toggle('hamburgerMenuExit-active');
    hamburgerExit.addEventListener('animationend',function(){
        let menu = document.getElementById('mobile-filter');
        menu.classList.remove('hamburgerMenu-active');
    });
}

第九步

做完選單,來處理選單上放的東西。篩選器的下拉式選單可以用 <select> <option> 處理,並記得在 <option> 中放置 value 值,這樣等會處理 js 才有辦法讓電腦判別不同值。

我的手機版和電腦版分兩塊去做,在宣告時我使用電腦版和手機版的 class 名稱,搭配 querySelectorAll ,因此會抓到不只一個值,把資料以陣列方式儲存。為此在監聽時必須用 forEach ,不然會跳錯誤訊息。

此外在事件名稱的部分,如果使用 click ,會變成一點擊 <select> ,電腦就紀錄一開始在 <select> 的項目。導致後來不管你選哪個 <option> ,都沒有被記錄到。把事件換成 change 的話,則能避面上述情形。

filterStatus.forEach(function(i){ 
    i.addEventListener('change',showFilterStatus); 
});
filterDate.forEach(function(i){
    i.addEventListener('change',showFilterDate);
});
filterSort.forEach(function(i){
    i.addEventListener('change',showFilterSort);
});

完成監聽後,拆解任務如下:

  1. 要讓電腦以點擊到的 <option> 做判斷
  2. 判斷後顯示相對應的 todo

善用 console.log 能發現 todoList.childNodes 正是我們需要的 todo 項目集合,用 todos 來稱呼它。 e.target 是點擊的 <option> 項目,所以要取它的值自然是用 .value 囉!這邊使用 switch 判斷式,裡面再用 if 判斷式做二次判斷。可以這樣理解:

用 switch 判斷 <option> 選了什麼,用 if 判斷接下來該顯示什麼?

如果不確定我下面寫的 todo 是什麼? sort 是什麼?建議使用 console.log 查看一下喔!

function showFilterStatus(e){
    const todos = todoList.childNodes; 
    todos.forEach(function(todo){
        switch(e.target.value){ 
            case "all":
                todo.style.display = 'flex'; 
                break;
            case "completed": //
                if(todo.classList.contains('completed')){
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
            case "uncompleted":
                if(!todo.classList.contains('completed')){
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
        }
    });
}
function showFilterDate(e){
    const todos = todoList.childNodes; 
    todos.forEach(function(todo){
    //console.log(todo.childNodes[0].childNodes[0]); 從這句找到月份所在 dom 位置
    const month = todo.childNodes[0].childNodes[0].innerHTML;
        switch(e.target.value){ 
            case "allMonth":
                todo.style.display = 'flex'; //全都秀
                break;
            case "jan":
                if (month.includes('01/')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "feb":
                if (month.includes('02/')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "mar":
                if (month.includes('03/')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "apr":
                if (month.includes('04/')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "may":
                if (month.includes('05/')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "jun":
                if (month.includes('06/')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "jul":
                if (month.includes('07/')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "aug":
                if (month.includes('08/')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "sep":
                if (month.includes('09/')){ 
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
            case "oct":
                if (month.includes('10/')){ 
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
            case "nov":
                if (month.includes('11/')){ 
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
            case "dec":
                if (month.includes('12/')){ 
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
        }
    });
}
function showFilterSort(e){
    const todos = todoList.childNodes;
    todos.forEach(function(todo){
        const sort = todo.childNodes[0].childNodes[2].childNodes[0];
        switch(e.target.value){ 
            case "allSort": 
                todo.style.display = 'flex'; 
                break;
            case "jobSort": 
                if (sort.classList.contains('fa-briefcase')){ 
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
            case "houseworkSort":
                if (sort.classList.contains('fa-home')){ 
                    todo.style.display = 'flex'; 
                } else {
                    todo.style.display = 'none';
                }
                break;
            case "sportSort":
                if (sort.classList.contains('fa-futbol')){ 
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
            case "routineSort": 
                if(sort.classList.contains('fa-hourglass')){
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
            case "othersSort": 
                if(sort.classList.contains('fa-palette')){
                    todo.style.display = 'flex'; 
                } else { 
                    todo.style.display = 'none';
                }
                break;
        }
    });
}

最後一步

最後的最後要來做個分析按鈕,按下去會跳出視窗,顯示種類的圓餅圖。 bootstrap 就有 modal 可以使用,但既然都要練習了...

因為是要點擊按鈕就彈視窗並跳出分析,所以我直接放在同一個函式中處理。在 modal 下層插入一個背景,讓下面的畫面不會太花,也避免使用者在手機版時,誤會關閉漢堡選單的 x 是關閉 modal 的 x 。

const container = document.querySelector('.container');
const analyzeBtn = document.querySelectorAll('.analyze-btn');
const modal = document.querySelector('.modal');
const modalExit = document.querySelector('.modal-exit');

analyzeBtn.forEach(function(i){
    i.addEventListener('click',analyzeSort);
});
modalExit.addEventListener('click',closeModal);

function analyzeSort(){
    modal.classList.toggle("modal-active");
    const modalBackground = document.createElement('div');
    modalBackground.classList.add("modal-background");
    container.appendChild(modalBackground);

然後要將 JSON 抓到的資料轉成 d3.js 圖表、放進 modal 中。從本地端抓 todos 這個 key ,若是空的,顯示暫無待辦事項。否則,把抓到的種類放到 todoSortArray 陣列中。

    let todos;
    todos = JSON.parse(localStorage.getItem('todos'));
    if(todos.length == 0){
        const modalContent = document.querySelector('.modal-content');
        modalContent.innerHTML = '暫無待辦事項';
    }else{
        let todoSortArray = [];
        todos.forEach(function(todo){
            todoSortArray.push(todo[2]);
        })

問題是,抓到了全部的代辦事項種類後,我要怎樣讓電腦計算每種種類有幾個呢?估狗後我在 stackoverflow 上找到解答:用 for 迴圈搭配物件比較去做。

宣告 i=0 ,當 i 小於 todoSortArray.length 就跑下面的函式,跑完 i+1 ,直到它等於 todoSortArray.length 為止。然後宣告 num 就是 todoSortArray[i] ,也就是說 num 會等於 todoSortArray[0]、todoSortArray[1]...而 todoSortArray[0] 、 todoSortArray[1] 是什麼呢?正是我們剛剛抓的 job 、 choose 等等種類名稱。看要比較哪個字,就在 num 的位置輸入,會比較 counts 物件,如相同則該物件存值 +1 ,如不同則存值設為 1 。

        let counts = {};
        for(let i=0;i<todoSortArray.length;i++){
            let num = todoSortArray[i];
            counts[num] = counts[num]?counts[num]+1:1;
        }

最後就很簡單了,把要比較的值代入,等它算好請它同步轉成數字,再畫成圓餅圖即可。

        let jobNum = parseInt(counts["job"]);
        let houseworkNum = parseInt(counts["housework"]);
        let sportNum = parseInt(counts["sport"]);
        let routineNum = parseInt(counts["routine"]);
        let othersNum = parseInt(counts["others"]);
        let chart = c3.generate({
            bindto: '.modal-body',
            data:{
                columns:[
                    ['工作',jobNum],
                    ['家事',houseworkNum], 
                    ['運動',sportNum],
                    ['例行公事',routineNum], 
                    ['其他',othersNum],
                ],
                type:'pie', //圖的種類是圓餅圖
                onclick:function(d,i){ //點擊圖時的效果
                    console.log("onclick",d,i); 
                },
                onmouseover:function(d,i){ //滑鼠滑進圖的效果
                    console.log("onmouseover",d,i); 
                },
                onmouseout:function(d,i){ //滑鼠滑出圖的效果
                    console.log("onmouseout",d,i);
                }
            }
        });
    }
}

別忘了在 modal 加離開鍵函式。

function closeModal(){
    modal.classList.remove('modal-active');
    const modalBackground = document.querySelector('.modal-background');
    container.removeChild(modalBackground);
}

以上就是所有製作過程!謝謝收看。


上一篇
番外篇(2)一起來做 To Do List!- 實作篇(2)
系列文
花三十天找到 JavaScript 沙漠中的綠洲35

尚未有邦友留言

立即登入留言