おはようございます、こんにちは、こんばんわ。
今回はJavaScriptのクラスについて書きます。
クラス使ってみたいけど、機会もないし、そもそもどういう場合に適しているのかいまいち分からない・・・
なので百聞は一コーディングにしかずの精神でTODOリストを書いてみました。
前置き
新しいもの触るならまずはTODOリスト作ってみるっしょ!って何処かで見聞きした記憶があるのでTODOリストを作ります。
また、クラスに関しては調べれば詳しいことは沢山出てくるので、仕様等については詳しく解説しません。
今回作ったTODOリストはこちら
https://codepen.io/nokonokojr/pen/oNzeWvr
TODOリストの仕様
- TODOの追加ができること
- TODOの削除ができること
- チェックボックスでまとめて削除ができること
(本当はTODOの編集も実装したかったが時間と気力の都合上カット)
まずは追加機能を作る
とりあえずTODOリストを生成する箱と入力用のinputとbuttonを作ります。
<div class="to-do">
<h1>Class構文でToDoリスト</h1>
<ul class="to-do-list"></ul>
<input type="text" class="to-do__text-box" placeholder="TODOを入力">
<button type="button" class="to-do__add-button">追加</button>
</div>
次はJavaScriptでクラスを宣言します。
constructorでTODOリストを入れておく配列と使うであろうDOMを用意しておきます。
追加したTODOを保存しておく機能は作りませんが、それ用に引数も設定。
頭から使う要素などコードに書き出せたのでスッキリ。
class toDoList {
constructor(toDoArray = []) {
this.toDoArray = toDoArray
this.element = {
toDoList: document.querySelector('.to-do-list')
}
}
}
初期化処理の部分は一旦書けたので、追加機能用にメソッドを書きます。
constructorで用意したTODOリストを入れておく配列に新しくデータを追加します。
引数のvalueには入力フォームの内容が渡される想定。
class toDoList {
constructor(toDoArray = []) {...}
add(value) {
this.toDoArray = [...this.toDoArray, value]
}
}
ここまで書いて一旦htmlと機能を繋げて、実際に配列にvalueを入れてみる
宣言したクラスの下に記述してきます。
// オブジェクトを作成
const toDoClass = new toDoList()
document.querySelector('.to-do__add-button').addEventListener('click', () =>{
const value = document.querySelector('.to-do__text-box').value
toDoClass.add(value)
// 入力欄を空に
document.querySelector('.to-do__text-box').value = ''
})
特に問題もなく配列にvalueを追加できたので、TODOリストをDOM生成する機能を作ります。
TODOリストの配列をforEachで回して一個ずつDOMを生成する感じ・・・
addメソッドでcreateメソッドを呼び出すのも忘れずに。
class toDoList {
constructor(toDoArray = []) {...}
create() {
// ToDoリストを生成
this.toDoArray.forEach(toDo => {
const element = document.createElement('li')
const text = document.createTextNode(`・${toDo}`)
element.appendChild(text)
this.element.toDoList.appendChild(element)
})
}
add(value) {
this.toDoArray = [...this.toDoArray, value]
this.create()
}
}
DOMの生成はできたけれども、配列全てが生成されるので前回生成したDOMと合わせてDOTOが雪だるま式に増えていってしまった・・・
前回生成したDOMを全て削除しないと...!!
子要素を削除するのって大変だな・・・(jQueryに感謝)
class toDoList {
constructor(toDoArray = []) {...}
create() {
// 表示しているToDoリストを消す
while (this.element.toDoList.firstChild) {
this.element.toDoList.removeChild(this.element.toDoList.firstChild);
}
// ToDoリストを生成
this.toDoArray.forEach(toDo => {
const element = document.createElement('li')
const text = document.createTextNode(`・${toDo}`)
element.appendChild(text)
this.element.toDoList.appendChild(element)
})
}
add(value) {...}
}
削除機能の実装
チェックボックスでまとめて消せるようにするので、チェックボックスを追加しないと・・・
TODOリストを生成する処理にチェックボックスの生成を追加します。
class toDoList {
constructor(toDoArray = []) {...}
create() {
// 表示しているToDoリストを消す
while (this.element.toDoList.firstChild) {
this.element.toDoList.removeChild(this.element.toDoList.firstChild);
}
// ToDoリストを生成
this.toDoArray.forEach(toDo => {
const element = document.createElement('li')
const checkbox = document.createElement('input')
const text = document.createTextNode(`・${toDo}`)
checkbox.setAttribute("type","checkbox");
element.appendChild(checkbox)
element.appendChild(text)
this.element.toDoList.appendChild(element)
})
}
add(value) {...}
}
削除ボタンもhtmlに追加します。
タイトル下で良い気がする・・・
<div class="to-do">
<h1>Class構文でToDoリスト</h1>
<button type="button" class="to-do__remove-button">削除</button>
<ul class="to-do-list"></ul>
<input type="text" class="to-do__text-box" placeholder="TODOを入力">
<button type="button" class="to-do__add-button">追加</button>
</div>
モノは揃った気がするので、削除用のメソッドを追加します。
何番目のTODOがチェックされているかを配列でもらって、TODOリスト配列から該当するデータを削除します。
['TODO1','TODO2','TODO3']は
- TODO1
- TODO2
- TODO3
とTODOリスト配列の順番通りにDOM生成されているので、辻褄が会うはず!
class toDoList {
constructor(toDoArray = []) {...}
create() {...}
add(value) {...}
remove(removeToDo) {
descendRemoveToDo.forEach(toDoNumber => {
this.toDoArray.splice(toDoNumber, 1)
})
this.create()
}
}
削除機能をhtmlと繋ぎます。
querySelectorAllで取得したNodeListはそのままTODOリストの順番になるはずなので、forEachでNodeListを回してcheckedの場合はindexを配列に追加していきます。
作った配列はremoveメソッドに渡します。
// オブジェクトを作成
const toDoClass = new toDoList()
document.querySelector('.to-do__add-button').addEventListener('click', () =>{...})
document.querySelector('.to-do__remove-button').addEventListener('click', () =>{
const removeToDo = []
const checkList = document.querySelectorAll('.to-do-list input[type="checkbox"]')
checkList.forEach((checkbox, index) => {
if(checkbox.checked) {
removeToDo.push(index)
}
})
toDoClass.remove(removeToDo)
})
削除はできているけど、2つ以上になるとうまく削除されない・・・
原因はTODOの削除処理は1つずつ削除しているので、削除用の配列とTODOリストの配列の辻褄が合わなくなったためでした。
-
削除用の配列:
[1,2] -
TODOリストの配列:
['TODO1','TODO2','TODO3']
で削除処理を通すと2回目の削除を行うときにTODOリストの配列は
['TODO1','TODO3']
になってしまうので3番目の配列は存在しないことになり、削除したかったTODO3は削除されなくなっていました。
そして、悩んで出した答えは削除用配列を降順に並び替えることでした。
後ろの方から削除していけば辻褄が合わなくなることがなくなった!(コードを書いていて気持ちいい瞬間)
降順に並び替えるのは.sort() 数値並び替えと調べると出てきます。
class toDoList {
constructor(toDoArray = []) {...}
create() {...}
add(value) {...}
remove(removeToDo) {
// removeToDoの中身を降順に
const descendRemoveToDo = removeToDo.sort((a,b) => {
return b - a
})
descendRemoveToDo.forEach(toDoNumber => {
this.toDoArray.splice(toDoNumber, 1)
})
this.create()
}
}
大体実装が終わったので、動作確認をしたら実装漏れがあったので対応。
引数でTODOリストの配列を渡された時の対応ができていなかった・・・
初期化時に走るconstructorにcreateメソッドを記述します。
class toDoList {
constructor(toDoArray = []) {
this.toDoArray = toDoArray
this.element = {
toDoList: document.querySelector('.to-do-list')
}
// 初期化処理時、toDoArrayに中身があるとき
if(this.toDoArray.length) {
this.create()
}
}
create() {...}
add(value) {...}
remove(removeToDo) {...}
}
一旦TODOリストが完成しました。
入力フォーム空っぽのままでもTODO追加できちゃうとか色々やることまだありますが、学習目的ということでご容赦を!
感想
ビュアなJavaScriptで書くと大変だなと第一に思いました。
クラスについては整理して書かざるを得ない感じなのでいいなと思いました。
WEB制作で使うとなるとmainみたいなクラスを作ってmainをオーバーライドする形で機能を実装していく感じすると良いのかな...??
前置きにも記述しましたが、今回作ったTODOリストはこちら
https://codepen.io/nokonokojr/pen/oNzeWvr