番外篇(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)

>>:  道德是资安首要议题

Day 2 set up

今天要来介绍一下如何 set up TypeScript! 请先到 TypeScript 的官网然後...

Day 4 - Array Cardio Day 1

前言 JS 30 是由加拿大的全端工程师 Wes Bos 免费提供的 JavaScript 简单应用...

Day23 - this&Object Prototypes Ch3 Objects - Review

Object content Array 是一种 Object,所以我们也能够用 key valu...

Day05 - Nginx 设定

闲话家常 前面几天都是枯燥乏味的设定,也是很重要的。要有一个稳定的环境,才能够专心在Coding。 ...

实习是进入职场前的探索

现在不管是学校课程规划或是同学主动想要了解职场,对於实习其实是一个可以看清自己的能力跟业界之间的差距...