ローカルストレージで永続的保存

### なぜFormDataオブジェクトを使ってるのか？
<p>そうした方が保存しやすいから。
<br>理屈: 入力form全体のidからdata取得し、formオブジェクトにdataを入れてる



### ２種類配列を用意しなきゃいけない

<p>テーブル表示用配列:LOCAL_STORAGE_KEY_TABLE_DATA</p>
<p>入力form用配列:LOCAL_STORAGE_KEY_FORM_DRAFT</p>
<p>dataが保存されたら、入力formのdataをすぐ消去</p>


```js:
//dataが保存されたら、入力formのdataをすぐ消去
localStorage.removeItem(LOCAL_STORAGE_KEY_FORM_DRAFT);
```

### 付け足す設定
<p>入力formに、name属性をつける</p>
<p>formオブジェクトを使う</p>
<h5>formオブジェクト用の配列を用意する</p>
<h5>formオブジェクトにdataを入れるときは、keyと値がワンセット</h5>

```js:
入力formに、name属性をつける
<input type="email" id="editEmail" name="editEmail">

formオブジェクトを使う
const formData = new FormData(formElement);

formオブジェクト用の配列を用意
const dataToSave = {};

entries : 変数を指定し、セットで取り出す
→ keyと値をセットで保存
for (let [key, value] of formData.entries()) {
        dataToSave[key] = value;
      }
```


### Localstorageで保存しない方がいいもの
<p>パスワード</p>
<p>クレジットカード</p>
<p>セッションデータ</p>
<p>マイナンバーカードとか</p>



### code全文

```js
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>テーブル表示練習</title>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <style>
    body {
        font-family: Arial, sans-serif;
        margin: 20px;
    }
    .form-group { margin-bottom: 10px; }
    .label { display: inline-block; width: 80px; }
    .error-message { display:none; color:red; margin-left:5px; }

    /* テーブル全体の基本スタイル */
    table {
        width: 100%;
        border-collapse: collapse;
        margin-top: 20px;
    }
    table, th, td { border:1px solid #000; border-collapse:collapse; padding:5px; }

    /* テーブルヘッダーの枠線を太くする */
    th {
        border: 3px solid #000; /* ヘッダーの枠線を太く */
    }

    /* ボタンの初期スタイル */
    #addButon {
        padding: 8px 15px;
        font-size: 16px;
        cursor: pointer;
        background-color: #007bff; /* 初期の色（青） */
        color: white;
        border: none;
        border-radius: 4px;
        transition: background-color 0.3s ease; /* 色の変化を滑らかに */
    }

    /* ボタンがクリックされた後のスタイル */
    #addButon.clicked-button {
        background-color: #28a745; /* クリック後は別の色（例: 緑） */
    }

    /* 削除・編集ボタンのスタイル */
    .action-button {
        padding: 5px 10px;
        margin-left: 5px; /* ボタン間のスペース */
        border: none;
        border-radius: 3px;
        cursor: pointer;
        font-size: 13px;
        color: white;
    }

    .delete-button {
        background-color: #dc3545; /* 赤色 */
    }

    .edit-button {
        background-color: #ffc107; /* 黄色 */
        color: black; /* 黄色には黒文字が見やすい */
    }

    /* --- モーダルウィンドウのスタイル --- */
    .modal {
      display: none; /* 初期状態では非表示 */
      position: fixed; /* 画面に固定 */
      z-index: 1; /* 最前面に表示 */
      left: 0;
      top: 0;
      width: 100%; /* 全幅 */
      height: 100%; /* 全高 */
      overflow: auto; /* スクロール可能にする */
      background-color: rgba(0,0,0,0.4); /* 半透明の黒背景 */
      justify-content: center;
      align-items: center;
    }

    .modal-content {
      background-color: #fefefe;
      margin: auto; /* 中央寄せ */
      padding: 20px;
      border: 1px solid #888;
      width: 80%; /* 幅 */
      max-width: 500px; /* 最大幅 */
      border-radius: 8px;
      box-shadow: 0 4px 8px rgba(0,0,0,0.2);
      position: relative;
    }

    .close-button {
      color: #aaa;
      float: right;
      font-size: 28px;
      font-weight: bold;
    }

    .close-button:hover,
    .close-button:focus {
      color: black;
      text-decoration: none;
      cursor: pointer;
    }

    .modal-buttons {
      margin-top: 20px;
      text-align: right;
    }
    .modal-buttons button {
      padding: 8px 15px;
      margin-left: 10px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    .modal-buttons .save-button {
      background-color: #28a745;
      color: white;
    }
    .modal-buttons .cancel-button {
      background-color: #6c757d;
      color: white;
    }
  </style>
</head>
<body>

<form id="userForm">
  <div class="form-group">
    <label class="label" for="studentName">名前</label>
    <input type="text" id="studentName" name="studentName">
    <div id="nameError" class="error-message">名前を入力してください</div>
  </div>
  <div class="form-group">
    <label class="label" for="studentAge">年齢</label>
    <input type="number" id="studentAge" name="studentAge">
  </div>
  <div class="form-group">
    <label class="label" for="studentEmail">メール</label>
    <input type="email" id="studentEmail" name="studentEmail">
  </div>
  <div class="form-group">
    <label class="label" for="studentnumber">電話</label>
    <input type="text" id="studentnumber" name="studentnumber">
  </div>
  <button type="button" id="addButon">追加</button>
</form>

<hr>

<table id="studentTable">
  <thead>
    <tr><th>ID</th><th>名前</th><th>年齢</th><th>メール</th><th>電話</th><th>操作</th></tr> </thead>
  <tbody></tbody>
</table>

<div id="editModal" class="modal">
  <div class="modal-content">
    <span class="close-button">&times;</span>
    <h2>データ編集</h2>
    <input type="hidden" id="editId">
    <div class="form-group">
      <label class="label" for="editName">名前</label>
      <input type="text" id="editName" name="editName">
    </div>
    <div class="form-group">
      <label class="label" for="editAge">年齢</label>
      <input type="number" id="editAge" name="editAge">
    </div>
    <div class="form-group">
      <label class="label" for="editEmail">メール</label>
      <input type="email" id="editEmail" name="editEmail">
    </div>
    <div class="form-group">
      <label class="label" for="editPhone">電話</label>
      <input type="text" id="editPhone" name="editPhone">
    </div>
    <div class="modal-buttons">
      <button class="save-button" id="saveEditButton">保存</button>
      <button class="cancel-button" id="cancelEditButton">キャンセル</button>
    </div>
  </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', () => {
  const LOCAL_STORAGE_KEY_TABLE_DATA = 'studentData'; // テーブルデータのローカルストレージキー
  const LOCAL_STORAGE_KEY_FORM_DRAFT = 'userFormDraft'; // フォーム入力値のローカルストレージキー

  const userForm = document.getElementById('userForm'); // フォーム要素全体を取得
  const addButton = document.getElementById('addButon');
  const nameInput = document.getElementById('studentName');
  const ageInput = document.getElementById('studentAge');
  const emailInput = document.getElementById('studentEmail');
  const phoneInput = document.getElementById('studentnumber');
  const nameError = document.getElementById('nameError');
  const tableBody = document.querySelector('#studentTable tbody');

  // モーダル関連要素
  const editModal = document.getElementById('editModal');
  const closeButton = document.querySelector('.close-button');
  const saveEditButton = document.getElementById('saveEditButton');
  const cancelEditButton = document.getElementById('cancelEditButton');
  const editIdInput = document.getElementById('editId');
  const editNameInput = document.getElementById('editName');
  const editAgeInput = document.getElementById('editAge');
  const editEmailInput = document.getElementById('editEmail');
  const editPhoneInput = document.getElementById('editPhone');

  let studentData = []; // アプリケーションのデータ配列
  let nextId = 1; // 次に割り当てるID

  // --- ローカルストレージとデータ管理 ---

  // ローカルストレージからテーブルデータを読み込む
  function loadTableDataFromLocalStorage() {
    const storedData = localStorage.getItem(LOCAL_STORAGE_KEY_TABLE_DATA);
    if (storedData) {
      studentData = JSON.parse(storedData);
      // 既存データから最大のIDを見つけてnextIdを設定
      if (studentData.length > 0) {
        nextId = Math.max(...studentData.map(item => item.id)) + 1;
      }
      renderTable(); // テーブルを初期表示
    }
  }

  // ローカルストレージにテーブルデータを保存する
  function saveTableDataToLocalStorage() {
    localStorage.setItem(LOCAL_STORAGE_KEY_TABLE_DATA, JSON.stringify(studentData));
  }

  // フォーム入力値をローカルストレージに自動保存する
  function setupAutoSaveForm(formElement, localStorageKey) {
    formElement.addEventListener('input', () => {
      const formData = new FormData(formElement);
      const dataToSave = {};
      for (let [key, value] of formData.entries()) {
        dataToSave[key] = value;
      }
      localStorage.setItem(localStorageKey, JSON.stringify(dataToSave));
      // console.log('フォームの入力値をローカルストレージに自動保存しました:', dataToSave);
    });
  }

  // ローカルストレージからフォームの入力値をロードしてフォームに設定する
  function loadAndFillForm(formElement, localStorageKey) {
    const savedData = localStorage.getItem(localStorageKey);
    if (savedData) {
      const data = JSON.parse(savedData);
      for (const key in data) {
        // name属性が一致するinput要素を探して値を設定
        const inputElement = formElement.querySelector(`[name="${key}"]`);
        if (inputElement) {
          inputElement.value = data[key];
        }
      }
      // console.log('ローカルストレージからフォームの入力値をロードしました:', data);
    }
  }

  // --- 関数定義 ---

  // 入力データ取得
  function getFormData() {
    return {
      id: nextId, // 現在のnextIdを割り当てる
      name: nameInput.value.trim(),
      age: parseInt(ageInput.value.trim()) || 0, // 数値に変換、無効な場合は0
      email: emailInput.value.trim(),
      phone: phoneInput.value.trim()
    };
  }

  // バリデーション
  function validateForm(data) {
    if (data.name === '') {
      nameError.style.display = 'inline';
      setTimeout(() => { nameError.style.display = 'none'; }, 2000);
      return false;
    }
    if (isNaN(data.age) || data.age <= 0) {
      alert('正しい年齢を入力してください');
      return false;
    }
    return true;
  }

  // バリデーションエラー初期化
  function clearErrors() {
    nameError.style.display = 'none';
  }

  // --- CRUD操作（Axiosを使用） ---

  // データ新規追加 (Create)
  async function createData(data) {
    try {
      const response = await axios.post('https://jsonplaceholder.typicode.com/users', data); // ダミーAPI
      console.log('APIから新規データが追加されました:', response.data);
      
      // 成功したらローカルデータとLocalStorageを更新
      studentData.push({ ...data, id: nextId++ }); // IDを更新
      saveTableDataToLocalStorage();
      renderTable();
      clearFormInputs();
      localStorage.removeItem(LOCAL_STORAGE_KEY_FORM_DRAFT); // フォーム送信成功後、下書きをクリア
    } catch (error) {
      console.error('データ追加に失敗しました:', error);
      alert('データ追加に失敗しました。');
    }
  }

  // データ編集 (Update)
  async function updateData(id, updatedData) {
    try {
      // ダミーAPIはPUT/PATCHしても実際のデータは更新しないため、擬似的な成功とします
      const response = await axios.put(`https://jsonplaceholder.typicode.com/users/${id}`, updatedData); // ダミーAPI
      console.log('APIでデータが更新されました:', response.data);

      // 成功したらローカルデータとLocalStorageを更新
      const index = studentData.findIndex(item => item.id === id);
      if (index !== -1) {
        studentData[index] = { ...studentData[index], ...updatedData };
        saveTableDataToLocalStorage();
        renderTable();
      }
      closeEditModal(); // 編集成功後にモーダルを閉じる
    } catch (error) {
      console.error('データ更新に失敗しました:', error);
      alert('データ更新に失敗しました。');
    }
  }

  // データ削除 (Delete)
  async function deleteData(id) {
    const confirmDelete = confirm('本当に削除しますか？');
    if (!confirmDelete) return;

    try {
      await axios.delete(`https://jsonplaceholder.typicode.com/users/${id}`); // ダミーAPI
      console.log(`APIからID ${id} のデータが削除されました。`);

      // 成功したらローカルデータとLocalStorageを更新
      studentData = studentData.filter(item => item.id !== id);
      saveTableDataToLocalStorage();
      renderTable();
    } catch (error) {
      console.error('データ削除に失敗しました:', error);
      alert('データ削除に失敗しました。');
    }
  }

  // --- UI操作 ---

  // テーブルに行追加（編集・削除ボタン付き）
  function addTableRow(data) {
    const row = document.createElement('tr');
    row.setAttribute('data-id', data.id); // 行にデータのIDを設定
    row.innerHTML = `
      <td>${data.id}</td>
      <td>${data.name}</td>
      <td>${data.age}</td>
      <td>${data.email}</td>
      <td>${data.phone}</td>
      <td>
        <button class="action-button edit-button">編集</button>
        <button class="action-button delete-button">削除</button>
      </td>
    `;
    tableBody.appendChild(row);

    // 追加したばかりのボタンにイベントリスナーを設定
    const editButton = row.querySelector('.edit-button');
    const deleteButton = row.querySelector('.delete-button');

    editButton.addEventListener('click', () => {
      openEditModal(data.id); // 編集モーダルを開く
    });

    deleteButton.addEventListener('click', () => {
      deleteData(data.id); // データ削除関数を呼び出す
    });
  }

  // 入力欄クリア
  function clearFormInputs() {
    nameInput.value = '';
    ageInput.value = '';
    emailInput.value = '';
    phoneInput.value = '';
  }

  // テーブルを再描画する関数
  function renderTable() {
    tableBody.innerHTML = ''; // テーブルの中身をクリア
    studentData.forEach(data => addTableRow(data)); // 全てのデータを再描画
  }

  // 編集モーダルを開く関数
  function openEditModal(id) {
    const dataToEdit = studentData.find(item => item.id === id);
    if (dataToEdit) {
      editIdInput.value = dataToEdit.id;
      editNameInput.value = dataToEdit.name;
      editAgeInput.value = dataToEdit.age;
      editEmailInput.value = dataToEdit.email;
      editPhoneInput.value = dataToEdit.phone;
      editModal.style.display = 'flex'; // flexにして中央寄せを有効に
    }
  }

  // 編集モーダルを閉じる関数
  function closeEditModal() {
    editModal.style.display = 'none';
  }

  // --- イベントリスナー ---

  // ページロード時にローカルストレージからテーブルデータを読み込み、テーブルを表示
  loadTableDataFromLocalStorage();

  // フォームの入力値を自動保存する機能をセットアップ
  setupAutoSaveForm(userForm, LOCAL_STORAGE_KEY_FORM_DRAFT);
  // ページロード時にローカルストレージからフォームの値をロードして入力欄に設定
  loadAndFillForm(userForm, LOCAL_STORAGE_KEY_FORM_DRAFT);

  // 「追加」ボタンのクリックイベント
  addButton.addEventListener('click', () => {
    // ボタンの色を青に変更 (今回はクリック後に緑に変える例)
    addButton.classList.add('clicked-button'); 

    const formData = getFormData();
    clearErrors();

    if (!validateForm(formData)) return;

    createData(formData); // データ追加関数を呼び出す
  });

  // モーダルを閉じるボタン
  closeButton.addEventListener('click', closeEditModal);
  cancelEditButton.addEventListener('click', closeEditModal);

  // モーダル内の保存ボタン
  saveEditButton.addEventListener('click', () => {
    const id = parseInt(editIdInput.value);
    const updatedData = {
      name: editNameInput.value.trim(),
      age: parseInt(editAgeInput.value.trim()),
      email: editEmailInput.value.trim(),
      phone: editPhoneInput.value.trim()
    };

    if (!validateForm(updatedData)) return; // 編集フォームのバリデーションも既存関数を利用

    updateData(id, updatedData); // データ更新関数を呼び出す
  });
});
</script>

</body>
</html>
```