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按鈕所執行的方法
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>

