【Web】 專案三-手作ToDoList

這篇要介紹新手常見的作品 - To Do List,筆者在前端使用了網頁三大技術HTML、CSS、JS,後端則是使用Firebase作為工具。這個專案主要是練習照著設計稿切版,並使用組合技完成工作,故僅完成基礎功能,無太多使用者經驗相關的精緻功能。
 

HTML


什麼是HTML
HTML(Hypertext Makeup Language),中文通常稱為「超文字標示語言」。如同名字所示,HTML主要功能是用來告訴瀏覽器該如何呈現網頁。HTML包含著一系列的元素(Element),元素中包含內容(Content)標籤(Tag),元素看起來會長的像是

<p>Hello World!</p>

其中<p>就是上述說的標籤,因為HTML未經過瀏覽器解讀並渲染前,其實就是一般的文字,故標籤結束時會多個「/」,目的是要告訴瀏覽器這行內容已經結束了,Hello就是內容。給予不同的標籤,會有不同的效果,例如段落文件標題(h1-h6)、語意的強調(strong、em…)等等,想更全面了解Tag可自行google,網路上有很多資訊,都是針對Tag的介紹(參1)。如果想要實際看看瀏覽器渲染的結果,直接開個記事本寫下HTML程式碼,最後將副檔名.txt改為.html再打開,或想要更有質感一點,也可在codepen(參2)上測試唷。

HTML架構
完整的HTML不僅僅要包含跟內容有關的元素,還要包含瀏覽器工作時必須知道的資訊。例如

  1. 文件類型 <!DOCTYPE html>
  2. 根元素 <html>
  3. <head>
  4. <body>

上述4個元素都必須存在,文件類型因為早期存在其他HTML版本,瀏覽器為針對不同版本進行不同工作,故需要指定文件的類型,現在大多都是使用HTML5,也為了方便就簡化成現在看到的樣子了。根元素中會包含<head>與<body>,兩者互相合作分工,<body>內放置跟內容直接相關的元素,<head>則放一些類似設定檔的元素,例如<title>、<meta>、<link>等等。未開始撰寫的HTML架構可能會長這樣

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
</body>
</html>

 

CSS與JavaScript


CSS
廣泛談到網頁技術,除了HTML之外,CSSJavaScript也被視為是同一套技術組合,CSS全名為Cascading Style Sheets,中文稱為階層式樣式表。HTML主要目的是要將網頁用結構化文件的方式表達,CSS則用於添加樣式,例如顏色、字體大小等等。筆者認為CSS不難學,重點在於搞懂選擇器、block及inline特性、CSS來源優先順序,命名規則其實慢慢習慣就好。之前看了Amos的金魚系列(參3),覺得對於HTML與CSS講解的非常清楚,有興趣的自行可以去看看囉。

JavaScript
當網頁想要實現更複雜的功能或使用者體驗時,大部分的情況而言,靠HTML及CSS僅能呈現靜態網頁,加入JavaScript後,可實現更多與使用者互動的寫法,透過event的監聽,決定要做些什麼,例如按下按鈕後顯示圖片。為了要能準確的指定要更動的元素,故JavaScript首要的功課就是練習取Dom,例如

document.getElementByClass("item");

但要如何能準確且快速的取到指定的Dom,其實跟寫出來的HTML架構有關係,而取到Dom後,要如何加入或修改CSS也是要考量的,例如使用classList.addsetAttribute等等,所以才說HTML、CSS、JavaScript通常是視為要一起擁有的技術。但取Dom只是JavaScript基本功而已,筆者看了一些資訊,發現更深的技術與概念,那就是非同步,但等日後有實戰且夠經驗再來寫。
 

 

ToDoList 基礎功能設計


練習過上面的網頁三大技術後,接下來我們要來著手寫第三個專案,此專案主要目的為練習照著設計稿切版,並使用網頁三大技術完成工作,故筆者僅針完成To Do List的基礎功能,不外乎就是新增、讀取、修改、刪除。更精緻的功能及使用者經驗就先Pass了。另外,因這個階段並沒有實際的後端資料庫可儲存,為測試功能,筆者在這小節先使用物件暫時充當ToDoList的資料。
 
主要畫面
1、顯示所有計畫(My Tasks):
可調整的參數包含(1)標題、(2)日期與時間、(3)上傳檔案、(4)工作內容註記

2、僅顯示執行中的計畫(In Progress)
若CheckBox被打勾,會視為已完成,並放置到Completed中。

3、僅顯示已完成的計畫(Completed):
可修改上述ToDoList內容、標示已完成、標示重要行程

4、垃圾桶(Trash):
若不要的計畫,可直接刪除,放在垃圾桶中待7天後自動刪除。
 

<body>
   <div class="header">
       <div class="header-box">
           <div id="header-item_0" class="header-item header-current" onclick="navselect(0)">My Tasks</div>
           <div id="header-item_1" class="header-item" onclick="navselect(1)">In Progress</div>
           <div id="header-item_2" class="header-item" onclick="navselect(2)">Completed</div>
           <div id="header-item_3" class="header-item" onclick="navselect(3)">Trash</div>
       </div>
   </div>
   <div id="wrap"></div>
</body>
可能是顯示的文字是「 My Tasks Progress Completed Trash Type eSomething Here... 曲 Deadline - : ® + ®I Comment Cancel Add Task - test1 1999-01-01 R 口 test2 1999-02-02 ® test3 1999-03-0 ® 口 test4 1999-04-04 ® 」的圖像



JavaScript在這個專案做到的事情
發現了嗎?在這個專案內,header的按鈕是固定不會動的,所以筆者就直接用HTML寫入,當使用者按下按鈕時,再根據按鈕回傳的index,決定要放什麼內容在<wrap>中。這樣就可以初步完成簡單的網頁囉。

1、在同網頁顯示不同內容:
針對使用者按下nav時,作出不同的回應,這裡大家可能會好奇是如何動態加入HTML程式碼的,示範程式碼如下

function initialization(i) {
	var target = object[i];
	var htmlTarget = document.getElementById("wrap");
	htmlTarget.insertAdjacentHTML("beforeend",
		`
			<div id="container_${i}" class="container ${container_css}">
                <div id="list_header_${i}" class="list-header new-list">
                    <div class="list-box-left">
                        <input id="checkbox_${i}" class="checkbox" type="checkbox">
                        <input id="title_${i}" class="listname ${container_css}" type="text" placeholder="Type Something Here…" value=${target.name}>
                    </div>
                    <div class="list-box-right">
                        <i id="star_${i}" class="far fa-star" onclick="starClick('${target.id}',${i})"></i>
                        <i id="pen_${i}" class="fas fa-pen editing" onclick="penClick(${i})"></i>
                        <i class="fas fa-trash-alt" onclick="deleteTask('${target.id}',${i})"></i>
                    </div>
                    <div id="subheader_${i}"></div>
                </div>
            </div>
		`
	)
}

 

object為筆者暫時放資料的物件,可能長的會像是

var object = [
  {
    id: _uuid(),
	name: "",
	date: "",
	time: "",
	comment: "",
	hasAttr: {
		important: false,
		hasDate: false,
		hasTime: false,
		hascomment: false
	},
	isnew: true,
	isDelete: false
  },
  {
    id: _uuid(),
	name: "test1",
	date: "1999-01-01",
	time: "12:01",
	comment: "這是test1",
	hasAttr: {
		important: true,
		hasDate: true,
		hasTime: true,
		hascomment: true
	},
	isnew: false,
	isDelete: false
  }
]

2、使用者對頁面操作時產生的動畫
例如正在編輯的計畫icon改為藍色、點擊星星icon時讓div顏色變顯眼,這裡使用object當作範例。

function starClick(id, i) {
       var target = object.find(x => x.id == id);
       target.hasAttr.important = !target.hasAttr.important;
       var star_css = target.hasAttr.important == true ? 'fas ' : 'far ';
       var container_css = target.hasAttr.important == true ? 'important-to-do ' : '';
       var title_css = target.hasAttr.important == true ? 'important-to-do ' : '';

       var star = document.getElementById("star_" + i);
       var container = document.getElementById("container_" + i);
       var title = document.getElementById("title_" + i);       
       star.setAttribute("class", star_css + "fa-star");
       container.setAttribute("class", container_css + "container");
       title.setAttribute("class", "listname " + title_css);
   }

3、針對物件或Firebase的CRUD
所有CRUD的操作結果都要被記錄下來,這裡以修改計畫內容作為範例,Firebase下個章節會說明。

function saveTask(id, i) {
       var target = object.find(x => x.id == id);
       target.name = document.getElementById('title_' + i).value;
       target.important = document.getElementById('star_' + i).classList.contains('fas') ? true : false;
       target.date = document.getElementById('date_' + i).value;;
       target.time = document.getElementById('time_' + i).value;
       target.comment = document.getElementById('comment_' + i).value;
       console.log(target);
       alert("已修改成功!!");
   }

4、其他
與網頁較無直接關係,如GUID製造器。

到這裡有沒有發現,其實程式碼的概念都很類似,因為筆者在動態加入HTML程式碼時,會以資料流的概念寫程式碼,故不管是新增、刪除、修改之纇的,只要批次跑迴圈讀取object,或直接指定id,即可作後續操作,這麼一來就可以專心的寫業務邏輯了,想看示範影片的人可參考這裡
 

 

FireBase是什麼


FireBase是個NoSQL文檔數據庫,使用FireBase就可以輕鬆的實現後端的儲存工作,但不需要做太多Server端的設定或管理,接下來我們就來看看FireBase要怎麼使用。


建立專案及初始設定 

可能是文字的卡通
未提供相片說明。
未提供相片說明。
未提供相片說明。
未提供相片說明。
可能是螢幕和文字的卡通
Firebase中可選擇Realtime Database與Cloud Firestore,兩者資料架構不同,看官往似乎僅有Cloud Firestore支援Web,故選用Cloud Firestore(參4參5)
可能是螢幕的圖像
未提供相片說明。
未提供相片說明。



到這裡已經建立好Firebase的專案囉,接著要在網頁的程式碼中加入程式碼,實現資料庫的CRUD。

點擊左欄的設定icon後,可以找到對應的SDK初始化的程式碼片段,複製貼上到HTML中,就可以開始使用Firebase囉。

可能是顯示的文字是「 Firebase SDK snippet CDN ?‘ 設定 您在使用任何 Firebase 服務之前, 請先複製這些指令碼並貼到 <body> 標記的最 <-- The core Firebase <script sc= SDK is always required and must be listed <script src= TODO: Add SDKs for Firebase products that you want to use <script> // Your web app Firebase configuration For Firebase JS SDK v7.20.0 and later, measurementId is optiona var firebaseConfig apiKey authDomain: projectId: storageBucket: messagingSenderId: appId: measurementId: }; Initialize Firebase firebase initializeApp firebaseConfig); firebase. analytics(); </script> 」的圖像

但因為預設給的是firebase,如果要使用cloud firestore,要加上

<script src="https://www.gstatic.com/firebasejs/8.3.0/firebase-firestore.js"></script>

 

試試上傳資料firebase上
網路上有很多針對firebase的crud提供初步的教學文章(參6參7),筆者的寫法只是包裝成function,再傳入GUID跟資料的物件。

function PushData2FireBase(id, data) {
        db.collection("todolist")
            .doc(id)
            .set(data);
};

 

完整程式碼: https://bitbucket.org/gary781218/todolist/src/master/

 

 

參考資料

  1. https://www.w3schools.com/
  2. https://codepen.io/
  3. https://www.youtube.com/watch?v=ZavL9y4Adrk&list=PLqivELodHt3iL9PgGHg0_EF86FwdiqCre&index=1
  4. https://blog.gcp.expert/cloud-firestore-cloud-native-nosql-introduction/https://blog.gcp.expert/cloud-firestore-cloud-native-nosql-introduction/
  5. https://firebase.google.com/docs/firestore/rtdb-vs-firestore#which_database_is_right_for_your_project
  6. https://ithelp.ithome.com.tw/articles/10224016
  7. https://firebase.google.com/docs/firestore/rtdb-vs-firestore#which_database_is_right_for_your_project