Part 8 JavaScript – Post-It Notes
記事功能(便利貼)
設計與指定日期記事的UID
接下來,我們要在日期上實現單擊後跳出便利貼的輸入對話方塊,輸入文字送出資料,寫入資料庫…
叫出對話方塊,上次學習過。這次的學習要實現一個觀念,就是每個日期的記事應該是唯一的,因此,我們必須要建立一個主鍵(primary key, unique key),主鍵必須出一個值,此值必須唯一,在這個月曆App中,我們可以選擇年月日組合來當做每一個日期的唯一值。
因此,我們要在產生月曆日期表格的同時,把每一個格子的鍵值放在TD元素裏,以便我們在叫出便利貼對話方塊時可以使用,那麼,我們要如何把鍵值放在TD元素裏呢?然後又要透過何種方式來取到特定日期的鍵值呢? 我們先看看 HTML5 中的 data-* attribute 屬性及HTMLElement.dataset。
function getUID(month, year, day){ if (month == 0) { //上個月減1,進到去年份 month = 12; year--; } if (month == 13) { //下個月加1,進到下年份 month = 1; year++; } // console.log(month.toString() + year.toString() + day.toString()) return month.toString() + year.toString() + day.toString(); } function fillInMonth(){ document.getElementById("cal-year").innerHTML = calendarData.calendar.year; document.getElementById("cal-month").innerHTML = getMonthName ( calendarData.calendar.month ); var monthDays = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; //第一個元素放個啞巴元素,好讓我們可以用1~12來存取月份的天數 //判斷今年是否是閏年 if ( ((calendarData.calendar.year % 4 == 0) && (calendarData.calendar.year % 100 != 0)) || (calendarData.calendar.year % 400 == 0) ) monthDays[2] = 29; var weekDay = (new Date(calendarData.calendar.year, calendarData.calendar.month - 1 ,1)).getDay() ; //取得今年今月1日為禮拜幾 // console.log(calendarData.calendar.year + "," + calendarData.calendar.month + "," + weekDay); var days = document.getElementsByTagName("td"); //下面迴圈將整個月曆表格元素去除掉color類別屬性,也就是把顏色的設定去掉。 for (let i = 0; i < days.length; i++){ if (days[i].classList.contains("color")) days[i].classList.remove("color"); if (days[i].classList.contains("prev-month-last-day")) days[i].classList.remove("prev-month-last-day"); //框線的處理 } //中間段,當月 for (let i = 0; i < monthDays[calendarData.calendar.month]; i++){ days[weekDay + i].innerHTML = (i+1); days[weekDay + i].setAttribute("data-uid", getUID(calendarData.calendar.month, calendarData.calendar.year, i+1)); //index-8-1 // days[weekDay + i].style.backgroundColor = "GoldenRod "; } //上個月段 if (weekDay > 0) days[weekDay-1].classList.add("prev-month-last-day"); //框線的處理,上個月的最後1天 var preMonth = calendarData.calendar.month-1; if (preMonth == 0) preMonth = 12; for (let i = (weekDay-1), day = monthDays[preMonth]; i >=0; i--, day--){ days[i].innerHTML = day; days[i].classList.add("color"); days[i].setAttribute("data-uid", getUID(calendarData.calendar.month-1, calendarData.calendar.year, day)); //index-8-1 } //下個月段 for (let i = (weekDay+monthDays[calendarData.calendar.month]), day = 1; i <days.length; i++, day++){ days[i].innerHTML = day; days[i].classList.add("color"); days[i].setAttribute("data-uid", getUID(calendarData.calendar.month+1, calendarData.calendar.year, day)); //index-8-1 } //處理今日元素表格的顯著背景設定 if (document.getElementById("current-day")) { document.getElementById("current-day").removeAttribute("id"); } if (calendarData.currentDate.year == calendarData.calendar.year && calendarData.currentDate.month == calendarData.calendar.month) { days[weekDay + calendarData.currentDate.date - 1].setAttribute("id", "current-day"); } changeColor(); }
透過console中的element來檢視程式對TD加上data-uid的結果:
<tbody id="table-body" class="border-color" style="border-color: rgb(27, 25, 205);"> <tr> <td class="color" data-uid="2201924" style="background-color: rgb(27, 25, 205);">24</td> <td class="color" data-uid="2201925" style="background-color: rgb(27, 25, 205);">25</td> <td class="color" data-uid="2201926" style="background-color: rgb(27, 25, 205);">26</td> <td class="color" data-uid="2201927" style="background-color: rgb(27, 25, 205);">27</td> <td class="prev-month-last-day color" data-uid="2201928" style="background-color: rgb(27, 25, 205);">28</td> <td data-uid="320191" style="">1</td> <td data-uid="320192" style="">2</td> </tr> <tr> <td data-uid="320193" style="">3</td> <td data-uid="320194" style="">4</td> <td data-uid="320195" style="">5</td> <td data-uid="320196" style="">6</td> <td data-uid="320197" style="">7</td> <td data-uid="320198" style="">8</td> <td data-uid="320199" style="">9</td> </tr> <tr> <td data-uid="3201910" style="">10</td> <td data-uid="3201911" id="current-day" style="">11</td> <td data-uid="3201912" style="">12</td> <td data-uid="3201913" style="">13</td> <td data-uid="3201914" style="">14</td> <td data-uid="3201915" style="">15</td> <td data-uid="3201916" style="">16</td> </tr> <tr> <td data-uid="3201917" style="">17</td> <td data-uid="3201918" style="">18</td> <td data-uid="3201919" style="">19</td> <td data-uid="3201920" style="">20</td> <td data-uid="3201921" style="">21</td> <td data-uid="3201922" style="">22</td> <td data-uid="3201923" style="">23</td> </tr> <tr> <td data-uid="3201924" style="">24</td> <td data-uid="3201925" style="">25</td> <td data-uid="3201926" style="">26</td> <td data-uid="3201927" style="">27</td> <td data-uid="3201928" style="">28</td> <td data-uid="3201929" style="">29</td> <td data-uid="3201930" style="">30</td> </tr> <tr> <td data-uid="3201931" style="">31</td> <td class="color" data-uid="420191" style="background-color: rgb(27, 25, 205);">1</td> <td class="color" data-uid="420192" style="background-color: rgb(27, 25, 205);">2</td> <td class="color" data-uid="420193" style="background-color: rgb(27, 25, 205);">3</td> <td class="color" data-uid="420194" style="background-color: rgb(27, 25, 205);">4</td> <td class="color" data-uid="420195" style="background-color: rgb(27, 25, 205);">5</td> <td class="color" data-uid="420196" style="background-color: rgb(27, 25, 205);">6</td> </tr> </tbody>
目前的程式連結:Part 8
加入日期表格的事件函式處理
<script language="JavaScript"> function openMakeNote(){ var modal = document.getElementById("modal"); modal.open = true; modal.classList.remove("fade-out"); //淡出效果 modal.classList.add("fade-in"); //淡入效果 var template = document.getElementById("make-note"); template.removeAttribute("hidden"); } function closeMakeNote(){ //關閉對話方塊 var modal = document.getElementById("modal"); modal.classList.remove("fade-in"); modal.classList.add("fade-out"); //淡出效果 modal.open = false; var template = document.getElementById("make-note"); template.setAttribute("hidden", "hidden"); } function dayClicked(elm) { console.log(elm.dataset.uid) openMakeNote(); } </script>
在日期表格td中加入onclick=”dayClicked(this);”,當按了日期表格元素後會呼叫dayClicked這個函式,其中參數為this,this在物件導向世界中為指向目前的物件,在這邊來說,this是指向td這個元素物件。在上面的 dayClicked函式裏,我們把這個this當成參考傳進去,就可以用this這個物件裏的dataset物件來存取data-*成員資料,在這邊,我們用elm.dataset.uid來存取td的data-uid資料,有了這個uid資料後,我們就可以用這個資料做為鍵值來查詢、插入、修改、刪除儲存在資料庫裏的日期記事資料(使用SQL與後端MySQL)。
<tbody id="table-body" class="border-color"> <tr> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> </tr> <tr> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> <td onclick="dayClicked(this);">1</td> </tr> (以下略)
為了要讓打開的便利貼對話方塊關掉,我們把對話方塊裏的二個按鈕加上事件函式closeMakeNote()呼叫(二個按鈕叫用同一個函式,只是為測試關閉對話方塊,之後再變化)。
<div id="make-note" hidden> <div class="popup"> <h4>Add a note to the calendar</h4> <textarea id="edit-post-it" class="font" name="post-it" autofocus></textarea> <div> <button class="button font post-it-button" id="add-post-it" onclick="closeMakeNote();">Post It</button> <button class="button font post-it-button" id="delete-button" onclick="closeMakeNote();">Delete It</button> </div> </div> </div>
目前的程式連結:Part 8
加入記事資料 (Post It按鈕的處理)
這個階段,我們加入/修改幾個函式:
- dayClicked,點擊日期格子後執行的方法
- currentDayHasNote,判斷特定UID是否已經有記事資料
- getRandom,計算介於min與max間的亂數值
- submitPostIt,點擊Post It按鈕所執行的方法
我們宣告一個postIts陣列(之後會移到updateData.js)來記錄所有的記事資料,postIts陣列就是一個資料集合,我們用uid來比對postITs資料集合是否有符合的資料,如果有的話,表示該uid已經有記事資料,如果沒有,我們設置一個newCurrentPostIt 布林變數為真的,按了Post It按鈕後,就將新的一筆postIt物件資料推入(push)postIts記事資料集合中,若該uid已經有資料,則直接更新該資料裏的note資料。刪除資料的話,即是透過陣列的splice方法,刪除PostIts資料集合裏的記事資料。
JavaScript的操作請看JavaScript Array Reference@W3 School。
<script language="JavaScript"> updateData(); //更新日期相關資料,預計改個名字,預叫buildCalendar,比較貼切! var postIts = []; //記事陣列,用來放置月曆中的記事物件資料 //current 目前點擊的日期 var currentPostItID = 0; //目前的記事ID var newCurrentPostIt = false; //目前的記事是否為新?也就是:目前點選的日期尚未有任何的記事資料 var currentPostItIndex = 0; //目前的記事在postIts陣列中的位置索引 function dayClicked(elm) { //日期表格元素按了之後,會呼叫這個函式 // console.log(elm.dataset.uid) currentPostItID = elm.dataset.uid; //目前的記事ID為所點擊的日期表格上的uid currentDayHasNote(currentPostItID);//判斷目前點蠕擊的日期是否有記事資料 openMakeNote(); } function currentDayHasNote(uid){ //測試特定UID是否已經有記事 for(var i = 0; i < postIts.length; i++){ if(postIts[i].id == uid){ newCurrentPostIt = false; currentPostItIndex = i; return; } } newCurrentPostIt = true; } function getRandom(min, max) { //min <= 亂數值 < max return Math.floor(Math.random() * (max - min) ) + min; } function submitPostIt(){ //按了PostIt按鍵後,所要執行的方法 const value = document.getElementById("edit-post-it").value; document.getElementById("edit-post-it").value = ""; let num = getRandom(1, 6); //取得1~5的亂數,用來標示便利貼顏色的檔案代號 let postIt = { id: currentPostItID, note_num: num, note: value } if(newCurrentPostIt){ //如果是新記事的話 postIts.push(postIt); //將新記事postIT物件推入postIts陣列 } else { postIts[currentPostItIndex].note = postIt.note; //更新現有記事物件的記事資料 } console.log(postIts) fillInMonth(); //這個方法可以改成fillInCalendar比較貼切,之後,我們再來統一大改 (refactoring) closeMakeNote(); } function openMakeNote(){ //打開記事對話方塊 var modal = document.getElementById("modal"); modal.open = true; modal.classList.remove("fade-out"); modal.classList.add("fade-in"); var template = document.getElementById("make-note"); template.removeAttribute("hidden"); if(!newCurrentPostIt){ document.getElementById("edit-post-it").value = postIts[currentPostItIndex].note; } } function closeMakeNote(){ //關閉記事對話方塊 //關閉對話方塊 var modal = document.getElementById("modal"); modal.classList.remove("fade-in"); modal.classList.add("fade-out"); modal.open = false; var template = document.getElementById("make-note"); template.setAttribute("hidden", "hidden"); } </script>
記得按鈕事件的繫結也要一併處理:
<div> <button class="button font post-it-button" id="add-post-it" onclick="submitPostIt();">Post It</button> <button class="button font post-it-button" id="delete-button" onclick="deleteNote();">Delete It</button> </div>
目前的程式連結:Part 8
月曆上顯示現有日期記事
在submitPostIt這個函式裏末中,我們叫用了fillInMonth這個函式,為的就是讓這個函式再一次更新一次月曆表格,以便更新記事的圖示及ToolTip。
因此,我們回到fillInMonth這個方法,加入記事圖示及ToolTip的處理。
我們在updateData.js裏要用到postIts這個陣列變數,變數在使用前要先宣告,所以,我們把下列這行移到updateData.js最上方:
var calendarData = { currentDate : { day : "", date : "", month : "", year : "", }, calendar:{ month : "", year : "" } }; var postIts = []; //記事陣列,用來放置月曆中的記事物件資料
加入appendSpriteToCellAndTooltip方法
//記事圖示與ToolTip處理 function appendSpriteToCellAndTooltip(uid, elem){ for(let i = 0; i < postIts.length; i++){ if(uid == postIts[i].id){ elem.innerHTML += `<img src='images/note${postIts[i].note_num}.png' alt='A post-it note'>`; elem.classList.add("tooltip"); elem.innerHTML += `<span>${postIts[i].note}</span>`; } } }
在fillInMonth方法裏,加入呼叫appendSpriteToCellAndTooltip方法(標示index-8-3):
//中間段,當月 var uid; //index-8-1 for (let i = 0; i < monthDays[calendarData.calendar.month]; i++){ days[weekDay + i].innerHTML = (i+1); uid = getUID(calendarData.calendar.month, calendarData.calendar.year, i+1); //index-8-1 days[weekDay + i].setAttribute("data-uid", uid); //index-8-1 // days[weekDay + i].style.backgroundColor = "GoldenRod "; appendSpriteToCellAndTooltip(uid, days[weekDay + i]); //index-8-3 } //上個月段 if (weekDay > 0) days[weekDay-1].classList.add("prev-month-last-day"); //框線的處理,上個月的最後1天 var preMonth = calendarData.calendar.month-1; if (preMonth == 0) preMonth = 12; for (let i = (weekDay-1), day = monthDays[preMonth]; i >=0; i--, day--){ days[i].innerHTML = day; days[i].classList.add("color"); uid = getUID(calendarData.calendar.month-1, calendarData.calendar.year, day); //index-8-1 days[i].setAttribute("data-uid", uid); //index-8-1 appendSpriteToCellAndTooltip(uid, days[i]); //index-8-3 } //下個月段 for (let i = (weekDay+monthDays[calendarData.calendar.month]), day = 1; i <days.length; i++, day++){ days[i].innerHTML = day; days[i].classList.add("color"); uid = getUID(calendarData.calendar.month+1, calendarData.calendar.year, day); //index-8-1 days[i].setAttribute("data-uid", uid); //index-8-1 appendSpriteToCellAndTooltip(uid, days[i]); //index-8-3 }
刪除記事資料 (Delete It按鈕的處理)
加入deleteNote函式,利用陣列的splice方法刪去記事資料。(splice說明資料)
function deleteNote(){ document.getElementById("edit-post-it").value = ""; let indexToDel; if(!newCurrentPostIt){ indexToDel =currentPostItIndex; } if(indexToDel != undefined){ postIts.splice(indexToDel, 1); } fillInMonth(); //這個方法可以改成fillInCalendar比較貼切,之後,我們再來統一大改 (refactoring) closeMakeNote(); }
記得按鈕事件的繫結也要一併處理:
<div> <button class="button font post-it-button" id="add-post-it" onclick="submitPostIt();">Post It</button> <button class="button font post-it-button" id="delete-button" onclick="deleteNote();">Delete It</button> </div>
目前的程式連結:Part 8
註:一個狀況,當記事本方塊跳出後,游標沒有跳到文字輸入方塊,我們在openMakeNote函式加上:
document.getElementById("edit-post-it").focus(); //游標跳至文字輸入方塊中
最後,我們將這個階段的程式碼放入postIts.js中:
//current 目前點擊的日期 var currentPostItID = 0; //目前的記事ID var newCurrentPostIt = true; //目前的記事是否為新?也就是:目前點選的日期尚未有任何的記事資料 var currentPostItIndex = 0; //目前的記事在postIts陣列中的位置索引 function openMakeNote(){ var modal = document.getElementById("modal"); modal.open = true; modal.classList.remove("fade-out"); //淡出效果 modal.classList.add("fade-in"); //淡入效果 var template = document.getElementById("make-note"); template.removeAttribute("hidden"); document.getElementById("edit-post-it").focus(); //游標跳至文字輸入方塊中… if(!newCurrentPostIt){ document.getElementById("edit-post-it").value = postIts[currentPostItIndex].note; } } function closeMakeNote(){ //關閉對話方塊 var modal = document.getElementById("modal"); modal.classList.remove("fade-in"); modal.classList.add("fade-out"); //淡出效果 modal.open = false; var template = document.getElementById("make-note"); template.setAttribute("hidden", "hidden"); } function currentDayHasNote(uid){ //測試特定UID是否已經有記事 for(var i = 0; i < postIts.length; i++){ if(postIts[i].id == uid){ newCurrentPostIt = false; currentPostItIndex = i; document.getElementById("edit-post-it").value = postIts[i].note; return; } } newCurrentPostIt = true; } function getRandom(min, max) { //傳回介於min與max間的亂數值 return Math.floor(Math.random() * (max - min) ) + min; } function submitPostIt(){ //按了PostIt按鍵後,所要執行的方法 const value = document.getElementById("edit-post-it").value; document.getElementById("edit-post-it").value = ""; let num = getRandom(1, 6); //取得1~6的亂數,用來標示便利貼顏色的檔案代號 let postIt = { id: currentPostItID, note_num: num, note: value } if(newCurrentPostIt){ //如果是新記事的話 postIts.push(postIt); //將新記事postIT物件推入postIts陣列 } else { postIts[currentPostItIndex].note = postIt.note; //更新現有記事物件的記事資料 } // console.log(postIts) fillInMonth(); //這個方法可以改成fillInCalendar比較貼切,之後,我們再來統一大改 (refactoring) closeMakeNote(); } function deleteNote(){ document.getElementById("edit-post-it").value = ""; let indexToDel; if(!newCurrentPostIt){ indexToDel =currentPostItIndex; } if(indexToDel != undefined){ postIts.splice(indexToDel, 1); } fillInMonth(); //這個方法可以改成fillInCalendar比較貼切,之後,我們再來統一大改 (refactoring) closeMakeNote(); } function dayClicked(elm) { // console.log(elm.dataset.uid) currentPostItID = elm.dataset.uid; //目前的記事ID為所點擊的日期表格上的uid currentDayHasNote(currentPostItID);//判斷目前點蠕擊的日期是否有記事資料 openMakeNote(); }
目前的程式連結:Part 8,新加入的js程式碼移入postIts.js, 並且加入postIts.js的使用宣告:
<script src="js/updateData.js"></script> <script src="js/changeTheme.js"></script> <script src="js/postIts.js"></script> <script language="JavaScript"> updateData(); </script>