iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
Modern Web

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

【Day 9】- 入門 JavaScript 網頁架設:Undo

  • 分享至 

  • xImage
  •  

摘要
Day 8 我們加上了刪除前的確認視窗,避免誤刪。
今天再進一步:刪除後,允許使用者「復原」上一次刪除的資料(Undo)。
刪除時把「被刪的物件」暫存到 deletedStack:多筆堆疊(Stack)Undo。
使用者按「回復上一筆」→ 跳出 confirm → 按確認就把上一筆放回 LocalStorage 並重繪。

為什麼要加 Undo?

  • 降低錯誤成本:手滑刪掉,也能快速復原。
  • 學習常見設計模式:Undo 是幾乎所有產品的基本功能(Gmail、Word、待辦清單)。

核心流程

  1. 刪除:以 id 移除並把該筆推入 deletedStack。
  2. 回復:按「回復上一筆」→ confirm → 從 deletedStack.pop() 取出最後刪除的一筆 → 檢查是否已存在 → 存檔 → renderHistory()。
  3. 多次回復:每次 Undo 移除 deletedStack 已復原的該筆資料,支援連續復原。

deletedStack 只在記憶體,不在 LocalStorage,換分頁或重整會消失。

實作步驟

HTML:

<!-- 回復按鈕 -->
<button id="undoBtn" disabled>回復上一筆</button>

JavaScript:
假設已存在 STORAGE_KEY、readRecords()、writeRecords()(Day 5/6 的函式)。

// ===== 狀態 =====
let deletedStack = []; // 可暫存多筆已刪除資料
const undoBtn = document.getElementById('undoBtn');

// ===== 渲染(新到舊),每筆帶 data-id =====
function renderHistory() {
  const el = document.getElementById('historyList');
  const records = readRecords().slice().sort((a,b) => b.createdAt - a.createdAt);

  el.innerHTML = records.length
    ? records.map(r => `
        <li>
          [${new Date(r.createdAt).toLocaleString()}] ${r.task} — ${r.reason} |「${r.quote}」
          <button class="btn-delete" data-id="${r.id}">刪除</button>
        </li>
      `).join('')
    : '<li class="muted">尚無紀錄</li>';

  // stack 有東西才啟用回復按鈕
  undoBtn.disabled = deletedStack.length === 0;
}

// ===== 刪除(依 id;不可變性)=====
function deleteRecordById(id) {
  const list = readRecords();
  const idx = list.findIndex(r => r.id === id);
  if (idx === -1) return false; // 可能被其他分頁刪除,從 Day 8 布林值換為索引值,方便調整資料

  const deleted = list[idx];
  deletedStack.push(deleted);        // 多筆暫存:推入堆疊
  const next = list.filter(r => r.id !== id);
  writeRecords(next);
  return true;
}

// ===== 事件委派:在 <ul> 監聽刪除按鈕點擊 =====
document.getElementById('historyList').addEventListener('click', (e) => {
  const btn = e.target.closest('button.btn-delete');
  if (!btn) return;
  const id = btn.dataset.id;

  // Day 9:不再使用 Day 8 的 confirm(本日重點在 Undo)
  const ok = deleteRecordById(id);
  if (!ok) {
    alert('找不到這筆紀錄,可能已被移除或在其他分頁修改。');
    return renderHistory();
  }
  renderHistory(); // 立刻刷新
});

// ===== 回復上一筆(回復最後刪除的那筆)=====
undoBtn.addEventListener('click', () => {
  if (deletedStack.length === 0) return; // 保險

  const peek = deletedStack[deletedStack.length - 1]; // 看一下即將回復的那筆
  const sure = window.confirm(`要回復上一筆刪除嗎?(${peek.task} — ${peek.reason})`);
  if (!sure) return;

  const rec = deletedStack.pop(); // 真的取出並從堆疊移除,避免重複回復
  const list = readRecords();
  list.push(rec);                 // 基礎版:直接 push 回去(若要回原位置可記錄 index 並 splice 插回)
  writeRecords(list);
  renderHistory();
});

// ===== 多分頁同步 =====
window.addEventListener('storage', (e) => {
  if (e.key === STORAGE_KEY) renderHistory();
});

// 保留 Day 8 清空記錄功能:
document.getElementById('clearBtn').addEventListener('click', () => {
  if (!window.confirm('確定要清空所有紀錄嗎?清空後無法回復')) return;
  localStorage.removeItem(STORAGE_KEY);
  renderHistory();
});

// ===== 初次載入 =====
renderHistory();

驗證

  1. 新增 2 筆資料。

  2. 刪除 1 筆 → 「回復上一筆」按鈕變成可用。
    https://ithelp.ithome.com.tw/upload/images/20250824/20177913XN6Z1WRMC5.png
    https://ithelp.ithome.com.tw/upload/images/20250824/20177913s6JjMeArnZ.png

  3. 再將另一筆刪除 → 按「回復上一筆」→ 跳出 confirm → 按「確定」:該筆回到清單。
    https://ithelp.ithome.com.tw/upload/images/20250824/20177913pVc1D4bvLC.png
    https://ithelp.ithome.com.tw/upload/images/20250824/201779130ut8Pkbqwa.png

  4. 再按「回復上一筆」→ 按「確定」:回復上上筆刪除的資料。
    https://ithelp.ithome.com.tw/upload/images/20250824/20177913Frd8NqpnkF.png


上一篇
【Day 8】- 入門 JavaScript 網頁架設:刪除清單資料前的確認視窗(Confirm)
下一篇
【Day 10】- 入門 JavaScript 網頁架設:排序
系列文
Modern Web × AI《拖延怪日記》:語錄陪伴擺脫拖延19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言