JavaScriptに関する質問を何でもしていい掲示板です。匿名での投稿も可能です。どんなことでもどしどし書き込んでください。回答もどなたでも自由にできます。僕も回答できる内容であれば必ず回答します!
質問は下記に沿って書いていただくと回答しやすくなります。
お世話になっております。前回はありがとうございました。
2回目の質問になります。
今回の質問はコードが長いのでためらいましたが、どうしても自力では理解できず質問させていただきました。
現在TodoMVCというTodoアプリのサンプルサイトを参考に勉強しています。
https://todomvc.com/examples/vanillajs/
https://github.com/tastejs/todomvc/tree/gh-pages/examples/vanilla-es6/src
質問の内容はエラーになる理由なのですが、エラー自体は解決できています。
しかし、そのコードでなぜエラーが解決できるのかがわからず質問させていただきました。
エラーの内容はこちらです。
Uncaught TypeError: Cannot read property 'dataset' of null
at _itemId (view.js:7)
at HTMLInputElement.<anonymous> (view.js:247)
at HTMLUListElement.dispatch(helpers.js: 44)
問題の箇所はview.js
にあるbindEditItemSave
とbindEditItemCancel
の★の部分です。
これらが参照しているview.js
の_itemId
関数ではelement
の親要素(またはさらにその親要素)のdataset
を取得しているのに
その子要素であるelement
(編集中のinput
)にdataset
を指定しないと上記のエラーになってしまいます。
この理由が理解できずに苦戦しています。
このエラーの理由をご教授いただけますでしょうか。
バブリングとキャプチャリングが関係しているのかと推察して
stopPropagation
を使ってみましたがダメでした。
Todoアイテムを追加し編集中の状態のHTMLです
<section style="display: block;" class="main">
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li data-id="1613277309944" class="editing"> /* このdatasetを取得しているはず */
<div class="view">
<input class="toggle" type="checkbox">
<label>テスト</label>
<button class="destroy"></button>
</div>
<input class="edit"> /* 編集中のinput */
</li>
</ul>
<footer class="footer">
<span class="todo-count">1 item left</span>
<ul class="filters">
<li>
<a href="#/" class="selected">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button class="clear-completed" style="display: none;">Clear completed</button>
</footer>
</section>
helpers.js
export function $on(target, type, callback, capture) {
target.addEventListener(type, callback, !!capture);
}
/**
* セレクタに一致するすべての要素のイベントにハンドラを付けます。
*/
export function $delegate(target, selector, type, handler, capture) {
const dispatch = (event) => {
const targetElement = event.target;
const potentialElements = target.querySelectorAll(selector);
let i = potentialElements.length;
while (i--) {
if (potentialElements[i] == targetElement) {
handler.call(targetElement, event);
}
}
}
$on(target, type, dispatch, !!capture);
}
view.js
// TodoアイテムのIDを10進数の整数値にパースする関数
// ★親要素のdatasetを取得しているはずなのに子要素のdatasetを作らないとエラーになる
const _itemId = element => {
return parseInt(element.parentNode.dataset.id || element.parentNode.parentNode.dataset.id, 10);
};
export default class View {
/**
* Todoリストの編集中アイテムがフォーカスを失った時にhandlerを実行
*/
bindEditItemSave(handler) {
$delegate(this.$todoList, 'li .edit', 'blur', ({ target }) => {
if (!target.dataset.iscanceled) { // ② ★これが無いとエラーになってしまう
handler(_itemId(target), target.value.trim());
}
}, true);
$delegate(this.$todoList, 'li .edit', 'keypress', ({ target, keyCode }) => {
if (keyCode === ENTER_KEY) {
target.blur();
}
});
}
/**
* Todoリストの編集中アイテムでESCキーが押された時にフォーカスを外しhandlerを実行
*/
bindEditItemCancel(handler) {
$delegate(this.$todoList, 'li .edit', 'keyup', ({ target, keyCode }) => {
if (keyCode == ESCAPE_KEY) {
target.dataset.iscanceled = true; // ①★これが無いとエラーになってしまう
target.blur();
handler(_itemId(target));
}
});
}
}
controller.js
export default class Controller {
constructor(store, view) {
this.store = store;
this.view = view;
view.bindEditItemSave(this.editItemSave.bind(this));
view.bindEditItemCancel(this.editItemCancel.bind(this));
}
/**
* エディットでアイテムを保存します。
*/
editItemSave(id, title) {
if (title.length) {
this.store.update({ id, title }, () => {
this.view.editItemDone(id, title);
});
} else {
this.removeItem(id);
}
}
/**
* アイテム編集モードをキャンセルします。
*/
editItemCancel(id) {
this.store.find({ id }, data => {
const title = data[0].title;
this.view.editItemDone(id, title);
});
}
}