iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0
Modern Web

Modern Web × AI《拖延怪日記》:語錄陪伴擺脫拖延系列 第 16

【Day 16】- 入門 JavaScript 網頁架設:appendChild 移動 DOM(任務卡 × 時段格子)

  • 分享至 

  • xImage
  •  

摘要
Day 16 我們把「任務」轉成可操作的任務卡,並提供三個時段格子(早/午/晚)。
使用者可以用點擊把任務卡放入某個時段格子;系統會記錄安排的時段(localStorage),下次開啟仍能看到結果。

學習重點

  • 點擊互動 → appendChild 完成簡單 DOM 移動。
  • 狀態保存:把「任務卡安排在哪個時段」寫進 localStorage。
  • a11y:讓格子可聚焦、選定後有 aria-live 友善回饋。

核心流程

  1. 顯示任務卡(使用者最新輸入的任務)。
  2. 顯示三個時段格子(早/午/晚,含 emoji 對應)。
  3. 點擊任一格子 → appendChild(taskCard) 放入該格。
  4. 同步寫入 localStorage:{ [recordId]: 'morning' | 'afternoon' | 'evening' }。
  5. 重新開啟頁面時,自動將任務卡放回已安排的格子。

實作(新增或調整片段)

假設已具備:

  • Day 4–5 → readRecords / writeRecords / addRecord(紀錄存取)
  • Day 12 → showPage(頁面切換骨架)
  • Day 13 → onSubmit(新增紀錄來源)
  • Day 14 → PERIOD_LABEL(顯示用 label)
  • Day 15 → btnStartNow click handler(分支導流到安排頁)

HTML(新增):
放在 </section><!-- Day 15 新增:條件分支 --> 之後、historySection 之前:

<!-- ★ Day16-NEW:任務安排頁 -->
<section id="taskScheduleSection" hidden role="region" aria-labelledby="task-schedule-title">
  <h2 id="task-schedule-title">把任務放進你今天最有精神的時段</h2>

  <!-- 任務卡容器(載入時會把最新任務渲染在這) -->
  <div id="taskCardWrap" aria-live="polite"></div>

  <!-- 三個時段格子 -->
  <div class="time-grid" style="display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem;">
    <button id="morningSlot"   class="time-slot" type="button" aria-label="早上格子(☀️)">☀️ 早上</button>
    <button id="afternoonSlot" class="time-slot" type="button" aria-label="下午格子(🧠)">🧠 下午</button>
    <button id="eveningSlot"   class="time-slot" type="button" aria-label="晚上格子(🌃)">🌃 晚上</button>
  </div>

  <p id="scheduleFeedback" aria-live="polite" style="margin-top:.5rem;"></p>

  <div style="margin-top:1rem;">
    <button id="btnBackFromSchedule" aria-controls="recordFormSection">← 回到表單</button>
  </div>
  <div style="margin-top:1rem;">
    <button id="btnHistoryFromSchedule" aria-controls="historySection">歷史回顧 →</button>
  </div>
</section>

註:此處用 button 當格子,可聚焦、可用鍵盤操作,也便於 aria-label。

JavaScript 常數與狀態(新增):
放在常數區塊之後(或靠近其他 key 區域):

// ★ Day16-NEW:任務安排狀態
const SCHEDULE_KEY = 'task_schedule_map'; // { [recordId]: 'morning'|'afternoon'|'evening' }
let currentTaskId = null;                  // 目前顯示為任務卡的 recordId

// Day16-NEW:任務安排頁節點
const taskScheduleSection = document.getElementById('taskScheduleSection');
const taskCardWrap        = document.getElementById('taskCardWrap');
const morningSlotEl       = document.getElementById('morningSlot');
const afternoonSlotEl     = document.getElementById('afternoonSlot');
const eveningSlotEl       = document.getElementById('eveningSlot');
const scheduleFeedback    = document.getElementById('scheduleFeedback');
const btnBackFromSchedule = document.getElementById('btnBackFromSchedule');
const btnHistoryFromSchedule = document.getElementById('btnHistoryFromSchedule'); 

JavaScript 儲存/讀取工具(新增):

// ★ Day16-NEW:讀/寫安排映射
function readScheduleMap() {
  try { return JSON.parse(localStorage.getItem(SCHEDULE_KEY)) || {}; }
  catch { return {}; }
}
function writeScheduleMap(map) {
  localStorage.setItem(SCHEDULE_KEY, JSON.stringify(map || {}));
}

// 取得最新一筆紀錄(用於生成任務卡)
function getLatestRecord() {
  const list = readRecords();
  if (!list.length) return null;
  // 預設以 createdAt 由新到舊
  return list.slice().sort((a,b)=>b.createdAt - a.createdAt)[0];
}

JavaScript:渲染任務卡與放回既有安排(新增):

// ★ Day16-NEW:渲染任務卡(以最新紀錄為主)
function renderTaskCard() {
  const rec = getLatestRecord();
  taskCardWrap.innerHTML = '';
  if (!rec) {
    taskCardWrap.innerHTML = '<div class="muted">尚無任務,請先到表單新增。</div>';
    currentTaskId = null;
    return;
  }
  currentTaskId = rec.id;

  // 建立任務卡
  const card = document.createElement('div');
  card.id = 'taskCard';
  card.setAttribute('role','article');
  card.setAttribute('aria-label', `任務卡:${rec.task}`);
  card.style.border = '1px solid #ccc';
  card.style.padding = '.5rem';
  card.style.borderRadius = '.5rem';
  card.style.background = '#fff';
  card.textContent = rec.task;

  taskCardWrap.appendChild(card);

  // 若已有安排,放回對應格子
  const map = readScheduleMap();
  const period = map[rec.id];
  if (period) {
    const slot = period === 'morning' ? morningSlotEl
              : period === 'afternoon' ? afternoonSlotEl
              : period === 'evening' ? eveningSlotEl
              : null;
    if (slot) slot.appendChild(card);
    scheduleFeedback.textContent = `已安排到:${PERIOD_LABEL[period] || period}`;
  } else {
    scheduleFeedback.textContent = '點擊上方任一格子,把任務排進今天的時段。';
  }
}

JavaScript:點擊格子 → 佈署任務卡並保存(新增):

// ★ Day16-NEW:把任務卡放進某時段 + 保存
function placeTaskTo(period) {
  const card = document.getElementById('taskCard');
  if (!card || !currentTaskId) return;

  const target = period === 'morning' ? morningSlotEl
               : period === 'afternoon' ? afternoonSlotEl
               : period === 'evening' ? eveningSlotEl
               : null;
  if (!target) return;

  target.appendChild(card);

  // 寫入安排映射
  const map = readScheduleMap();
  map[currentTaskId] = period;
  writeScheduleMap(map);

  scheduleFeedback.textContent = `已安排到:${PERIOD_LABEL[period] || period}`;
}

// 綁定三個格子
morningSlotEl?.addEventListener('click',   () => placeTaskTo('morning'));
afternoonSlotEl?.addEventListener('click', () => placeTaskTo('afternoon'));
eveningSlotEl?.addEventListener('click',   () => placeTaskTo('evening'));

JavaScript:導流與頁面切換(調整):
把分支頁的「馬上開始!」改為導到任務安排頁並渲染任務卡:

// [Day16-CHANGED] 分支互動:導到任務安排頁
btnStartNow?.addEventListener('click', () => {
  decisionFeedback.textContent = '就是這種精神!來安排看看吧 💪';
  showPage('taskSchedule');
});

// [Day16-NEW] 從安排頁回到表單
btnBackFromSchedule?.addEventListener('click', () => showPage('home'));
btnHistoryFromSchedule?.addEventListener('click', () => showPage('history'));

在 showPage(page) 中加入 taskSchedule 分支(新增):

} else if (page === 'taskSchedule') {     // ★ Day16-NEW
  if (taskScheduleSection) {
    taskScheduleSection.hidden = false;
    renderTaskCard(); // 顯示最新任務卡,並把既有安排放回格子
    const first = document.getElementById('taskCard') || morningSlotEl;
    if (first && typeof first.focus === 'function') first.focus();
  }

JavaScript:提交後可直接安排(可選):
若你希望送出表單後就能直接安排,也可在 onSubmit() 的尾端補一行(選擇性):

// onSubmit 末尾(選擇性導流)
showPage('taskSchedule');

驗證

  1. 顯示任務卡:新增任務後進入安排頁,看到一張任務卡(文字 = 最新任務)。
  2. 點擊放入:點「☀️ 早上」→ 任務卡進入該格,底下顯示「已安排到:早上」。
  3. 儲存狀態:重新整理頁面 → 再進安排頁 → 任務卡仍在先前的格子。
  4. a11y:以鍵盤 Tab 移動到各格子、Enter 也能放入;scheduleFeedback 會被讀出。

常見錯誤 & 排查

  1. 點擊格子沒反應
    檢查三個 slot 的 ID 是否為 morningSlot/afternoonSlot/eveningSlot,與 JS 綁定一致。
  2. 任務卡消失或沒生成
    沒有任何紀錄可供渲染。請先到表單新增一筆任務。
  3. 重新整理後安排消失
    檢查 SCHEDULE_KEY 是否被清空,或 recordId 是否變了(本實作用最新紀錄做任務卡)。
  4. 閱讀器沒讀到更新
    確認 scheduleFeedback 具 aria-live="polite",且是在顯示中的節點上更新文字。

上一篇
【Day 15】- 入門 JavaScript 網頁架設:多時段狀態匯總與分支導航
下一篇
【Day 17】- 入門 JavaScript 網頁架設:Drag & Drop API(拖曳互動)
系列文
Modern Web × AI《拖延怪日記》:語錄陪伴擺脫拖延18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言