JavaScriptの質問をしたり答えたりする掲示板

2019-01-29に作成

JavaScriptに関する質問を何でもしていい掲示板です。匿名での投稿も可能です。どんなことでもどしどし書き込んでください。回答もどなたでも自由にできます。僕も回答できる内容であれば必ず回答します!

質問は下記に沿って書いていただくと回答しやすくなります。

  • 実際のソースコードがある場合、可能な範囲で書いていただくと問題点を見つけやすくなります。
  • 実際に発生したエラーメッセージがある場合、コピペしていただくと問題解決につながる可能性が高いです。
  • 見た目上の問題の場合、スクリーンショットを貼ると状況が把握しやすくなります。

Uncaught TypeError: Cannot read property 'dataset' of null になる理由

お世話になっております。前回はありがとうございました。
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にあるbindEditItemSavebindEditItemCancelの★の部分です。
これらが参照している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);
        });
    }
}

タグ

投稿月

最近コメントされた投稿