subtitle

RaiseTechの各種コースをはじめとしたイロイロな学習の記録

JavaScriptでToDoアプリを作る③

CodeSandboxを使ってToDoアプリを作成中。

前回は、ちょっと挙動がおかしいところを修正して、今回は、ローカルストレージにToDoリストを保存して、画面がリロードされてもリストを保持できるように改良。

過去記事:



ローカルストレージにデータを記録するには、キーと値を対にして記録する方法と、配列やオブジェクトをJSON形式にして記録する方法がある。


Web Storage APIでは?の記事では、キーと値を対にして記録する方法をまとめたので、今回は、オブジェクトの配列をローカルストレージに記録する方法をまとめてみる。



なぜオブジェクトの配列型にするのか?

今回自分の作るToDoアプリは、やることリストに完了・差し戻しボタンがついている。

完了のタスクは、文字をグレーにして取り消し線表示にし、ボタンは完了の代わりに差し戻しボタンに変更する。

つまり、ページリロード時に、やることの一覧だけでなく、各項目の状態(完了・未完了)も保持しておかなければならないからである。


オブジェクトは、item(やることリストの項目)と、comp(完了状態:1、未完了状態:0)のキーを持つものにする。

※完了・未完了のステータスなどがなく、単純にToDoのリストの内容だけ保持するのであれば、ただの配列でOK。



ローカルストレージに保存するデータの作成

JavaScript

// ローカルストレージに保存
  let lStorage = [];
  if (getLocalStorage() !== null) {
    lStorage = getLocalStorage();
  }
  lStorage.push({ item: task, comp: 0 });
  setLocalStorage(lStorage);


入力されたタスクを、未完了状態で、lStorageに追加。

オブジェクトにpushは使えないが、配列の中身がオブジェクトの場合は、pushが使える。



ローカルストレージにオブジェクトの配列を保存

JavaScript

// ローカルストレージにデータを保存
const setLocalStorage = (list) => {
  localStorage.setItem('todo', JSON.stringify(list));
};


配列やオブジェクトの場合は、JSON形式でローカルストレージに保存されるので、変換してやる必要がある。

todoというキーに対して、JSON形式に変換した配列やオブジェクトを引数に指定して、localStorage.setItemを呼び出す。



ローカルストレージのデータを取り出す

JavaScript

// ローカルストレージのデータを取得
const getLocalStorage = () => {
  return JSON.parse(localStorage.getItem('todo'));
};


今度は逆に、保存したキーを指定して、JSON形式のデータを配列・オブジェクト型に変換して取り出す。



取り出したデータをToDoリストとして整形しなおす

JavaScript

  const lStorage = getLocalStorage();

  if (lStorage === null) {
    taskList.textContent = '';
  } else {
    // ローカルストレージに保存してあるデータを戻す
    for (let i = 0; i < lStorage.length; i++) {
      const listItem = document.createElement('li');
      listItem.id = lStorage[i].item;
      if (lStorage[i].comp === 1) {
        listItem.setAttribute('class', 'complete');
      }
      listItem.textContent = lStorage[i].item;
      taskList.appendChild(listItem);
      addButtons(listItem);
    }
  }


取り出したデータは、ただのオブジェクトの配列になっているので、1つずつデータを取り出して、リストになるように整形していく。



オブジェクトの配列をローカルストレージに保存する例

今回のJavaScript側のコードをまるっと。

const taskInput = document.getElementsByClassName('task_input')[0];
const taskSubmit = document.getElementsByClassName('task_submit')[0];
const taskDelete = document.getElementsByClassName('task_delete')[0];
const taskList = document.getElementsByClassName('task_list')[0];

// ローカルストレージのデータを取得
const getLocalStorage = () => {
  return JSON.parse(localStorage.getItem('todo'));
};

// ローカルストレージにデータを保存
const setLocalStorage = (list) => {
  localStorage.setItem('todo', JSON.stringify(list));
};

// Deleteボタン押下時の処理
const deleteTasks = (deleteButton) => {
  const chosenTask = deleteButton.closest('li');
  taskList.removeChild(chosenTask);

  // ローカルストレージに保存
  const lStorage = getLocalStorage();
  for (let i = 0; i < lStorage.length; i++) {
    if (lStorage[i].item === chosenTask.id) {
      lStorage.splice(i, 1);
      setLocalStorage(lStorage);
      break;
    }
  }
};

// Completeボタン押下時の処理(完了⇔未完了のトグル)
const completeTasks = (completeButton) => {
  const chosenTask = completeButton.closest('li');

  if (chosenTask.className === 'complete') {
    // 完了タスクを未完了に戻す
    chosenTask.className = 'notComplete';
    completeButton.innerHTML = 'Complete';
  } else {
    // 未完了タスクを完了にする
    chosenTask.className = 'complete';
    completeButton.innerHTML = 'Restore';
  }

  // 完了・未完了のデータをローカルストレージに保存
  const lStorage = getLocalStorage();
  for (let i = 0; i < lStorage.length; i++) {
    if (lStorage[i].item === chosenTask.id) {
      if (chosenTask.className === 'complete') {
        lStorage[i].comp = 1;
      } else {
        lStorage[i].comp = 0;
      }
      setLocalStorage(lStorage);
      break;
    }
  }
};

// やること追加ボタン押下時③ ⇒ ボタンの作成
const addButtons = (item) => {
  const deleteButton = document.createElement('button');
  deleteButton.className = 'task_button';
  deleteButton.innerHTML = 'Delete';
  item.appendChild(deleteButton);

  const completeButton = document.createElement('button');
  completeButton.className = 'task_button';
  item.appendChild(completeButton);
  if (item.className === 'complete') {
    completeButton.innerHTML = 'Restore';
  } else {
    completeButton.innerHTML = 'Complete';
  }

  deleteButton.addEventListener('click', (evt) => {
    evt.preventDefault();
    deleteTasks(deleteButton);
  });

  completeButton.addEventListener('click', (evt) => {
    evt.preventDefault();
    completeTasks(completeButton);
  });
};

// やること追加ボタン押下時② ⇒ タスクの要素を追加
const addTasks = (task) => {
  const listItem = document.createElement('li');
  const showItem = taskList.appendChild(listItem);
  listItem.id = task;

  const listItem2 = document.createElement('p');
  const showItem2 = showItem.appendChild(listItem2);
  showItem2.innerHTML = task;

  addButtons(listItem);

  // ローカルストレージに保存
  let lStorage = [];
  if (getLocalStorage() !== null) {
    lStorage = getLocalStorage();
  }
  lStorage.push({ item: task, comp: 0 });

  setLocalStorage(lStorage);
};

// やること追加ボタン押下時① ⇒ エラー処理
taskSubmit.addEventListener('click', (evt) => {
  evt.preventDefault();
  const task = taskInput.value;
  if (task === '') {
    // タスク未入力は認めない
    window.alert('タスクを入力してください');
    return;
  }

  const lStorage = getLocalStorage();
  if (lStorage !== null) {
    for (let i = 0; i < lStorage.length; i++) {
      if (lStorage[i].item === task) {
        // タスクの重複は認めない
        window.alert('タスクが重複しています');
        return;
      }
    }
  }

  addTasks(task);
  taskInput.value = '';
});

// 全削除ボタン押下時 ⇒ リストの全削除(LoaclStorageも全削除)
taskDelete.addEventListener('click', (evt) => {
  evt.preventDefault();
  localStorage.clear();
  taskList.textContent = '';
});

// Reload時の処理
document.addEventListener('DOMContentLoaded', () => {
  const lStorage = getLocalStorage();

  if (lStorage === null) {
    taskList.textContent = '';
  } else {
    // ローカルストレージに保存してあるデータを戻す
    for (let i = 0; i < lStorage.length; i++) {
      const listItem = document.createElement('li');
      listItem.id = lStorage[i].item;
      if (lStorage[i].comp === 1) {
        listItem.setAttribute('class', 'complete');
      }
      listItem.textContent = lStorage[i].item;
      taskList.appendChild(listItem);
      addButtons(listItem);
    }
  }
});


実際に動くものはここで確認できる。

https://vpdk4.csb.app/