Todo list_Part1:前端功能


Posted by tzutzu858 on 2020-11-25

Todo list_Part2:UI 拿出資料與串接後端


在 week7 的 hw3 就有做到 Todo list ,不過只有 css 和 js ,那重新整理頁面資料就會都沒有了,所以這次再做一次的最大差別是把 todos 存到資料庫,將資料用 API 讓前端串。

前端功能

  1. 新增
  2. 刪除
  3. 打勾
  4. filter
  5. 移除已完成代辦事項
  6. 更新未完成

版面部分

使用 Bootstrap

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Week12 Todo List</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
        integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
    <script src="index.js"></script>
    <style>
        .btn-delete {
            opacity: 0;
        }

        .todo:hover .btn-delete {
            opacity: 1;
        }

        input[type=checkbox]:checked~label {
            text-decoration: line-through;
            color: rgba(0, 0, 0, 0.3);
        }

        .todo__content-wrapper {
            flex: 1;
        }

        .todo__content {
            display: block;
        }

        .options div,
        .clear-all {
            cursor: pointer;
            border-radius: 6px;
            padding: 4px;
            border: 2px solid transparent;
        }

        .options div.active {
            border-color: rgba(255, 0, 0, 0.3);
        }

        .options div:hover {
            border-color: rgba(255, 0, 0, 0.5);
        }
    </style>

</head>

<body>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-lg-6">
                <h1 class="text-center">Todo List</h1>
                <div class="input-group mb-3">
                    <input type="text" class="input-todo form-control" placeholder="todo">
                    <div class="input-group-append">
                        <button class="btn btn-add btn-outline-secondary" type="button">新增</button>
                    </div>
                </div>
                <div class="todos list-group ">
                    <div
                        class="todo list-group-item list-group-item-action d-flex justify-content-between align-items-center">
                        <div class="todo__content-wrapper custom-control custom-checkbox">
                            <input type="checkbox" class="custom-control-input" id="todo-1">
                            <label class="todo__content custom-control-label" for="todo-1">todo1</label>
                        </div>
                        <button type="button" class="btn-delete btn btn-danger">刪除</button>
                    </div>

                </div>
                <div class="info mt-1 d-flex justify-content-between align-items-center">
                    <div>2 個未完成</div>
                    <div class="options d-flex">
                        <div class="active">全部</div>
                        <div class="ml-2">未完成</div>
                        <div class="ml-2">已完成</div>
                    </div>
                    <div class="clear-all">
                        移除已完成待辦事項
                    </div>
                </div>
                <button type="button" class="btn btn-save btn-primary">儲存</button>

            </div>
        </div>
    </div>
</body>

</html>

新增 todo 功能

JavaScript 部分
抓到 input-todo 的 value,如果沒有值(使用者沒輸入任何東西),那就 return

  $('.btn-add').click(() => {
        const value = $('.input-todo').val();
        if (!value) return;
    })

把抓到 value 給他一個版面,把上面版面 HTML 的這段程式碼剪下放在 js 裡

const template = `
<div class="todo list-group-item list-group-item-action d-flex justify-content-between align-items-center">
    <div class="todo__content-wrapper custom-control custom-checkbox">
        <input type="checkbox" class="custom-control-input" id="todo-{id}">
        <label class="todo__content custom-control-label" for="todo-{id}">{content}</label>
    </div>
    <button type="button" class="btn-delete btn btn-danger">刪除</button>
</div>
`

按下新增按鈕後

$('.btn-add').click(() => {
        const value = $('.input-todo').val();
        if (!value) return;
        $('.todos').append(
            template
                .replace('{content}', escape(value))
                .replace(/{id}/g, id)   //replace('{id}', id) =>這樣只會 replace 第一個,可以用 replaceAll() 或正規表達式    
        )
        id++
        $('.input-todo').val('');
    })

function escape(toOutput) {
    return toOutput.replace(/\&/g, '&amp;')
        .replace(/\</g, '&lt;')
        .replace(/\>/g, '&gt;')
        .replace(/\"/g, '&quot;')
        .replace(/\'/g, '&#x27')
        .replace(/\//g, '&#x2F');
}

新增 todo 功能時不只按新增按鈕可以新增,按 enter 也可以

因為要做的事情和按新增按鈕一樣,所以直接包成一個 function

function addTodo() {
    const value = $('.input-todo').val();
    if (!value) return;
    $('.todos').append(
        template
            .replace('{content}', escape(value))
            .replace(/{id}/g, id)     
    )
    id++
    $('.input-todo').val('');
}
$('.input-todo').keydown(e => {
        if (e.key === 'Enter') {
            addTodo();
        }
    })

刪除 todo 功能

因為 todos 是動態產生的,所以要用事件代理人
所以用外層的 $('.todos')當代理人,去監聽,把 e.target 的 parent 移除掉

$('.todos').on('click', '.btn-delete', (e) => {
    $(e.target).parent().remove(); //後面用 jQuery ,前面就不能用原生的 e.target.parent().remove();
})

確認 todo 是否有被勾選

這樣就可以計算幾個 todo 完成,幾個 todo 未完成

$('.todos').on('change', '.check-todo', (e) => {
    const target = $(e.target)
    const isChecked = target.is(":checked")
    console.log(isChecked);
})

filter

先把 count (要計算完成、未完成) 做成一個 function ,因為勾選時會變,新增和刪除也會變,所以乾脆做成一個 function
先宣告

let todoCount = 0
let uncompleteTodoCount = 0

在各個地方 todoCount 和 uncompleteTodoCount 做 ++ 或 --,要特別注意的是在按刪除按鈕時,要先判斷前面的 checkbox 是否已被勾選,如果已經勾選代表已完成,就算刪除,uncompleteTodoCount 的總數也不用動,但如果沒完成又按刪除那 uncompleteTodoCount 就要 --

// 刪除 todo
    $('.todos').on('click', '.btn-delete', (e) => {
        $(e.target).parent().remove(); //後面用 jQuery ,前面就不能用原生的 e.target.parent().remove();
        todoCount--
        const isChecked = $(e.target).parent().find('.check-todo').is(":checked");
        if (!isChecked) {
            uncompleteTodoCount--
        } 
        updateCounter();
    })
function updateCounter() {
    $('.uncomplete-count').text(uncompleteTodoCount)
}

移除已完成代辦事項

找出所有已完成的東西,有幾種方法,
第一個是找到 checkbox (也就是 <input type="checkbox" class="check-todo custom-control-input" id="todo-{id}"> )看他是不是已完成,如果是的話就找他的 parent 刪掉。
第二個是 checkbox 打勾的時候,幫他的 class name 加上東西,這樣當按下 移除已完成代辦事項 時,就可以用這個 class name 一起刪掉
用第二種比較方便,所以在確認 todo 是否有被勾選那邊的程式,有勾加上 target.parents('.todo').addClass('checked'); ,沒勾移除 class name target.parents('.todo').removeClass('checked');

$('.clear-all').click(() => {
    $('.todo.checked').each(function (i, el) {
        todoCount--;
        el.remove();
    })
})

加上 data-* attribute 資料屬性,這樣就可以利用 JavaScript 中的 dataset 物件,取得物件的屬性值, jQuery 是用內建的 .data( )
HTML 裡面加 data-* attribute

<div class="active" data-filter="all">全部</div>
<div class="ml-2" data-filter="uncomplete">未完成</div>
<div class="ml-2" data-filter="done">已完成</div>

設事件代理 $('.options'),如果是顯示全部,那就先把所有 $('.todo') show 出來,如果是顯示未完成,一樣先把所有 $('.todo') show 出來,在隱藏掉已經打勾的 todo,那如果只顯示完成,則剛好相反,先把所有 $('.todo') hide 起來,再把已經打勾的顯示出來

$('.options').on('click', 'div', e => {
    const target = $(e.target)
    const filter = target.attr('data-filter')
    $('.options div.active').removeClass('active')
    target.addClass('active')
    if (filter === 'all') {
        $('.todo').show()
    } else if (filter === 'uncomplete') {
        $('.todo').show()
        $('.todo.checked').hide()
    } else { // done
        $('.todo').hide()
        $('.todo.checked').show()
    }
})










Related Posts

[ Day 01 ] Python unittest 單元測試 | 專案應用分享

[ Day 01 ] Python unittest 單元測試 | 專案應用分享

【隨堂筆記】運算思維與流程圖

【隨堂筆記】運算思維與流程圖

【JS學習筆記】如何檢查物件的屬性?

【JS學習筆記】如何檢查物件的屬性?


Comments