本篇為 [BE101] 用 PHP 與 MySQL 學習後端基礎 這門課程的學習筆記。如有錯誤歡迎指正。
hw2:Todo List
前端實作
目標功能:
- 新增功能:add
- 刪除功能:delete
- 編輯功能:update
- 更新狀態功能:checked / unchecked
- 切換列表:All / Active / Completed
- 清除所有 todo:Clear
以上功能只要利用前端就能達成,程式碼如下:
前端介面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Week12 Todo List</title> <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"> <link rel="stylesheet" href="css/style.css"> </head>
<body>
<div class="wrapper"> <div class="todo__header"> <button type="button" class="btn btn-save" href="#">Save</button> <button type="button" class="btn clear-all" href="#">Reset</button> </div> <h1>Todo List</h1> <div class="todo__input-block"> <input class="todo__input" type="text" placeholder="Add New Todo Here..." minlength="1" maxlength="128"> <button class="btn-new"></button> </div> <ul class="nav nav-middle justify-content-center todo__status"> <li class="nav-item"> <a class="nav-link active" href="#" data-filter="all">All</a> </li> <li class="nav-item"> <a class="nav-link" href="#" data-filter="in-progress">In Progress</a> </li> <li class="nav-item"> <a class="nav-link" href="#" data-filter="completed">Completed</a> </li> </ul>
<ul class="todo__list"> <!-- 要新增 template 的區塊 --> <li class="todo"> <input class="todo__check" type="checkbox" id="todo-0"> <label class="todo__title" for="todo">Coding</label> <button class="btn-delete"></button> </li> </ul> </div>
</body> </html>
|
前端 JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| <script> let id = 1;
const template = ` <li class="todo"> <input class="todo__check" type="checkbox" id="todo-{id}"> <label class="todo__title" for="todo-{id}">{content}</label> <button class="btn-delete"></button> </li> ` $('.btn-new').click(() => { addTodo() }); $('.todo__input').keydown(e => { if (e.key === 'Enter') { addTodo() } });
$('.todo__list').on('click', '.btn-delete', (e) => { $(e.target).parent().remove(); });
$('.todo__list').on('change', '.todo__check', (e) => { const target = $(e.target); const isChecked = target.is(":checked"); if (isChecked) { target.parents('.todo').addClass('checked'); } else { target.parents('.todo').removeClass('checked'); } });
$('.todo__status').on('click', 'a', e => { const target = $(e.target); const filter = target.attr('data-filter'); $('.todo__status a.active').removeClass('active'); target.addClass('active');
if (filter === 'all') { $('.todo').show(); } else if (filter === 'in-progress') { $('.todo').show(); $('.todo.checked').hide(); } else { $('.todo').hide(); $('.todo.checked').show(); } });
$('.clear-all').click(() => { $('.todo').remove(); });
$('.btn-save').click(() => { let todos = []; $('.todo').each((i, element) => { const input = $(element).find('.todo__check'); const label = $(element).find('.todo__title'); todos.push({ id: input.attr('id').replace('todo-', ''), content: label.text(), isDone: $(element).hasClass('checked') }); }); JSON.stringify(todos); });
function addTodo() { const value = $('.todo__input').val(); if (!value) return; $('.todo__list').prepend( template .replace('{content}', escape(value)) .replace(/{id}/g, id) ); id += 1; $('.todo__input').val(''); }
function escape(toOutput) { return toOutput .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } </script>
|
前後端串接
目標功能
PHP API
將前端得到的資料 JSON.stringify(todos),利用 Ajax 方式 POST 到資料庫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| $('.btn-save').click(() => { let todos = []; $('.todo').each((i, element) => { const input = $(element).find('.todo__check'); const label = $(element).find('.todo__title'); todos.push({ id: input.attr('id').replace('todo-', ''), content: label.text(), isDone: $(element).hasClass('checked') }); }); const data = JSON.stringify(todos); $.ajax({ type: 'POST', url: 'http://localhost/heidi/week12_local/hw2/api_add_todo.php', data: { todo: data }, success: function(resp) { const respId = resp.id window.location = 'index.html?id=' + respId; }, error: function () { alert('Error!'); } }); });
|
如此即可利用前端 JavaScript 從後端拿取 JSON 格式的資料,並用 JSON.parse() 將 JSON 字串轉換成 JavaScript 物件:
1 2 3 4 5 6 7 8 9 10
| const serchParams = new URLSearchParams(window.location.search); const todoId = serchParams.get('id');
if (todoId) { $.getJSON('http://localhost/heidi/week12_local/hw2/api_get_todo.php?id=' + todoId, function (data) { const todos = JSON.parse(data.data.todo); restoreTodos(todos); }); }
|
將拿到的資料再以 JS 處理新增到頁面,把模版 template 加上 content、id、todoClass:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function restoreTodos(todos) { if (todos.length === 0) return; id = todos[todos.length - 1].id + 1; for(let i = 0; i < todos.length; i++) { const todo = todos[i]; $('.todo__list').prepend( template .replace('{content}', escape(todo.content)) .replace(/{id}/g, todo.id) .replace('{todoClass}', todo.isDone ? 'checked' : '') ); } }
|
Single Page Application
Single Page Application(單頁面應用程式),簡稱 SPA。是前端利用 Ajax 以非同步方式串接後端 API,如此可將前後端分離,在交換資料時不需換頁,可透過動態方式更新部分頁面。
而早期的網頁主要採用 Multiple Page Application(多頁式應用程式)設計,與 SPA 概念相對應,每次交換資料時都需換頁。
SPA 的優缺點
優點
- 增進使用者體驗
不需換頁即可載入新的資訊。例如 Gmail 或影音播放網站,可以在播放音樂的同時,繼續瀏覽網站其他資訊。
- 前後端分離
後端只需負責制定 API 文件,提供前端資料。前端則利用 Ajax 從後端拿取資料,並以 JavaScript 在 html 動態產生內容。
缺點
- SEO(搜尋引擎最佳化)較差
由於 SPA 是利用 JavaScript 動態產生內容,檢視原始碼會發現原始內容是空的,
解決方法:第一次頁面由 Server side render,之後的操作都改用 Client side render,就可以保證搜尋引擎也能爬到完整的 HTML。
- 前端工作複雜化
原先是利用不同路由處理不同功能,改成由單一頁面統一管理,就像在網頁上實作 APP。
- 初次載入頁面費時
初次瀏覽頁面時會需要下載 JavaScript 或是其他頁面的 template。
由後端負責提供只輸出資料的 API vs PHP 直接輸出內容
後端負責提供只輸出資料的 API
- Server 端接收到請求,會回傳 JSON 或其他特定格式的資料給前端,瀏覽器再將資料動態更新至頁面
- 因為是動態產生資料,檢視原始碼會發現動態更新的內容是空的
PHP 直接輸出內容
- Server 端接收到請求,會將所需資料與頁面經處理後回傳 html 檔給前端,瀏覽器透過重整頁面顯示
- 因此回傳的頁面,檢視原始碼是有包含資料的
參考資料:
- 跟著小明一起搞懂技術名詞:MVC、SPA 與 SSR
- 前後端分離與 SPA
- Day20– 前端小字典三十天【每日一字】– SPA