プログラマーへの道 #39 の補足説明
こんにちは、プログラマーVTuberの衣亥栖ティオです。
今回は以下の動画の補足説明をします。
動画内で実装したソースコードも載せています。
今回の動画で実装したソースコード
以下が今回実装したソースコードです。
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous"> <title>メモ帳アプリ</title> </head> <body> <div> <h3>メモの登録</h3> <button id="id-add-button">メモを登録する</button> </div> <div> <h3>メモの一覧</h3> <button id="id-delete-button">削除する</button> <table class="table"> <thead> <tr> <th scope="col"><input type="checkbox" id="id-delete-all-memos"></th> <th scope="col">タイトル</th> <th scope="col">作成日</th> <th scope="col">更新日</th> <th scope="col">編集</th> </tr> </thead> <tbody id="id-memo-list"> </tbody> </table> <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">編集</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <div id="id-validation-error-in-edit-modal" class="alert alert-danger" style="display:none;" role="alert"> </div> <div class="input-group mb-3"> <span class="input-group-text" id="inputGroup-sizing-default">タイトル</span> <input type="text" id="id-modal-title" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default"> <input type="hidden" id="id-memo-id"> </div> <div class="form-floating"> <textarea class="form-control" style="height: 150px;" placeholder="Leave a comment here" id="id-modal-body"></textarea> <label for="id-modal-body">本文</label> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" id="id-update-button" class="btn btn-primary">更新</button> </div> </div> </div> </div> <div class="modal fade" id="id-add-modal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="id-add-modal-label">登録</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <div id="id-validation-error-in-add-modal" class="alert alert-danger" style="display:none;" role="alert"> </div> <div class="input-group mb-3"> <span class="input-group-text" id="inputGroup-sizing-default">タイトル</span> <input type="text" id="id-add-modal-title" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default"> </div> <div class="form-floating"> <textarea class="form-control" style="height: 150px;" placeholder="Leave a comment here" id="id-add-modal-body"></textarea> <label for="id-add-modal-body">本文</label> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" id="id-add-modal-button" class="btn btn-primary">登録</button> </div> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script> </body> <script> const myModal = new bootstrap.Modal(document.getElementById('exampleModal')) const addModal = new bootstrap.Modal(document.getElementById('id-add-modal')) const addButtonInModal = document.getElementById("id-add-modal-button") addButtonInModal.addEventListener("click", (event) => { const title = document.getElementById("id-add-modal-title").value const body = document.getElementById("id-add-modal-body").value const errorMessage = document.getElementById("id-validation-error-in-add-modal") errorMessage.innerHTML = "" if (title.length < 1 || title.length > 30) { errorMessage.innerHTML = "タイトルの文字数は1文字以上30文字以下にしてください。<br>" } if (body.length < 1 || body.length > 100) { errorMessage.innerHTML = errorMessage.innerHTML + "本文の文字数は1文字以上100文字以下にしてください。<br>" } const tmp = document.getElementById("id-memo-list") if (tmp.children.length > 0) { Array.from(tmp.children).forEach((tr) => { const t = document.getElementById("id-title-in-list-" + tr.id).innerText if (title == t) { errorMessage.innerHTML = errorMessage.innerHTML + "すでに登録済みのタイトルです。<br>" } }) } if (errorMessage.innerHTML.length > 0) { errorMessage.style.display = "" return } const now = new Date() //あとでライブラリで実装する var min = now.getMinutes() if (min < 10) { min = "0" + min } const createdAt = now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate() + " " + now.getHours() + ":" + min + ":" + now.getSeconds() const updatedAt = "なし" const memoId = now.getTime() const tr = document.createElement("tr") tr.setAttribute("id", memoId) const editButtonId = "id-edit-button-" + memoId const idTitleInList = "id-title-in-list-" + memoId const updatedAtId = "id-updated-at-" + memoId tr.innerHTML = '<td><input class="class-checkbox" data-memo-id="' + memoId + '" type="checkbox"></td><td id="' + idTitleInList + '">' + title + '</td><td>' + createdAt + '</td><td id="' + updatedAtId + '">' + updatedAt + '</td><td><button type="button" data-memo-id="' + memoId + '" id="' + editButtonId + '" class="btn btn-primary">編集</button></td>' const memoList = document.getElementById("id-memo-list") memoList.appendChild(tr) const inputForBody = document.createElement("input") inputForBody.setAttribute("id", "id-body-in-list-" + memoId) inputForBody.setAttribute("type", "hidden") inputForBody.value = body tr.appendChild(inputForBody) const editButton = document.getElementById(editButtonId) editButton.addEventListener("click", (event) => { const errorMessage = document.getElementById("id-validation-error-in-edit-modal") errorMessage.style.display = "none" errorMessage.innerHTML = "" const memoId = event.currentTarget.dataset.memoId document.getElementById("id-memo-id").value = memoId const title = document.getElementById("id-title-in-list-" + memoId).innerText const titleElement = document.getElementById("id-modal-title") titleElement.value = title const body = document.getElementById("id-body-in-list-" + memoId).value const bodyElement = document.getElementById("id-modal-body") bodyElement.value = body document.getElementById("exampleModalLabel").innerText = "編集" myModal.show() }) addModal.hide() }) const addButton = document.getElementById("id-add-button") addButton.addEventListener("click", (event) => { document.getElementById("id-add-modal-title").value = "" document.getElementById("id-add-modal-body").value = "" addModal.show() const errorMessage = document.getElementById("id-validation-error-in-add-modal") errorMessage.style.display = "none" errorMessage.innerHTML = "" }) const deleteButton = document.getElementById("id-delete-button") deleteButton.addEventListener("click", (event) => { const checkboxes = document.getElementsByClassName("class-checkbox") Array.from(checkboxes).forEach((checkbox) => { if (checkbox.checked == false) { return false } const memoId = checkbox.dataset.memoId document.getElementById(memoId).remove() }) }) const deleteAllMemos = document.getElementById("id-delete-all-memos") deleteAllMemos.addEventListener("change", (event) => { const checkboxes = document.getElementsByClassName("class-checkbox") if (checkboxes.length == 0) { return } Array.from(checkboxes).forEach((checkbox) => { if (deleteAllMemos.checked == true) { checkbox.checked = true } else { checkbox.checked = false } }) }) const updateButton = document.getElementById("id-update-button") updateButton.addEventListener("click", (event) => { const memoId = document.getElementById("id-memo-id").value const title = document.getElementById("id-modal-title").value const body = document.getElementById("id-modal-body").value const errorMessage = document.getElementById("id-validation-error-in-edit-modal") errorMessage.innerHTML = "" if (title.length < 1 || title.length > 30) { errorMessage.innerHTML = "タイトルの文字数は1文字以上30文字以下にしてください。<br>" } if (body.length < 1 || body.length > 100) { errorMessage.innerHTML = errorMessage.innerHTML + "本文の文字数は1文字以上100文字以下にしてください。<br>" } const tmp = document.getElementById("id-memo-list") if (tmp.children.length > 0) { Array.from(tmp.children).forEach((tr) => { if (tr.id == memoId) { return } const t = document.getElementById("id-title-in-list-" + tr.id).innerText if (title == t) { errorMessage.innerHTML = errorMessage.innerHTML + "すでに登録済みのタイトルです。<br>" } }) } if (errorMessage.innerHTML.length > 0) { errorMessage.style.display = "" return } //メモリストのタイトル document.getElementById("id-title-in-list-" + memoId).value = title //本文 document.getElementById("id-body-in-list-" + memoId).value = body const now = new Date() const updatedAt = now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate() document.getElementById("id-updated-at-" + memoId).innerText = updatedAt myModal.hide() }) </script> </html>
JavaScriptで時間を扱う
JavaScript には時間を扱うためのオブジェクトとして Date が存在します。
以下のあるようなメソッドを利用することで時刻を取得したりすることができます。
Date.prototype.getDate() - JavaScript | MDN
しかし、Date は特定のフォーマットで時刻を取得することができません。 例えば、動画中でもあったように分は "05" のような表現はできず、"5" になってしまいます。 動画中でやったように自分でフォーマットを整えることもできますが、少し面倒に感じてしまいます。
通常は以下にあるような "ライブラリ" という仕組みを利用するのですが、 ライブラリを説明すると動画が長くなってしまうので、動画中では一旦実装を諦めました。
The best JavaScript date libraries in 2021 - Skypack Blog
動画中でも言及していますが、一通り実装が完了したあとにライブラリを導入して日付周りの実装を終わらせようと思っています。