GeekFactory

int128.hatenablog.com

入れ子になっている要素のイベント処理を制御する

Knockout.js を使う場合に、入れ子になっている要素のイベントを上手く制御する方法について書きます。公式ドキュメントに記載されている典型的なパターンですが、混乱しやすいのでまとめてみました。

やりたいこと

以下のHTMLを考えます。

<div class="task">
 <input type="checkbox" />
 <span class="title">温泉を予約する</span>
 <div class="notes"></div>
</div>

イメージはこんな感じ。

以下を実現したいとします。

  • 付箋(div.task 要素)がクリックされたらダイアログを表示したい。
  • チェックボックス(input 要素)がクリックされたらサーバサイドに保存したい。

click binding を使ってみましょう。

<div data-bind="click: showDialog" class="task">
 <input data-bind="click: saveStatus" type="checkbox" />
 <span data-bind="text: title" class="title"></span>
 <div data-bind="text: notes" class="notes"></div>
</div>
/**
 * @class the task
 */
function Task () {
  this.initialize.apply(this, arguments);
}
/**
 * Constructor.
 */
Task.prototype.initialize = function () {
  var self = this;
  this.showDialog = function () {/* ... */};
  this.saveStatus = function () {/* ... */};
};

以下のように期待とは違う結果になります。

  • 付箋(div.task 要素)がクリックされたら showDialog() が実行される。
  • チェックボックス(input 要素)がクリックされたら saveStatus() が実行されるが、チェックボックスの状態は変わらない。そして showDialog() が実行される。

問題を解いていきましょう。

  1. チェックボックスをクリックした時に状態が変わるようにする。
  2. チェックボックスをクリックした時はダイアログを表示しない。

クリック時にチェックボックスのネイティブ処理を実行する

一つ目の問題です。

チェックボックスをクリックした時に状態が変わらないのは、チェックボックスのネイティブ処理が実行されないためです。

By default, Knockout will prevent the click event from taking any default action. This means that if you use the click binding on an a tag (a link), for example, the browser will only call your handler function and will not navigate to the link’s href. This is a useful default because when you use the click binding, it’s normally because you’re using the link as part of a UI that manipulates your view model, not as a regular hyperlink to another web page.

However, if you do want to let the default click action proceed, just return true from your click handler function.

http://knockoutjs.com/documentation/click-binding.html#note_3_allowing_the_default_click_action

ネイティブ処理を実行するには、click バインドするメソッドで true を返します。

/**
 * @class the task
 */
function Task () {
  this.initialize.apply(this, arguments);
}
/**
 * Constructor.
 */
Task.prototype.initialize = function () {
  var self = this;
  this.showDialog = function () {/* ... */};
  this.saveStatus = function () {
    /* Allowing the default action */
    return true;
  };
};

チェックボックスをクリックした時はダイアログを表示しない

二つ目の問題です。

チェックボックスをクリックした時にダイアログが表示されるのは、付箋のclick bindingがクリックイベントを処理しているためです。

By default, Knockout will allow the click event to continue to bubble up to any higher level event handlers. For example, if your element and a parent of that element are both handling the click event, then the click handler for both elements will be triggered. If necessary, you can prevent the event from bubbling by including an additional binding that is named clickBubble and passing false to it, as in this example: (以下略)

http://knockoutjs.com/documentation/click-binding.html#note_4_preventing_the_event_from_bubbling

data-bind属性でclickBubbleを指定すると、上位の要素にイベントを伝播しなくなります。

<div data-bind="click: showDialog" class="task">
 <input data-bind="click: saveStatus, clickBubble: false" type="checkbox" />
 <span data-bind="text: title" class="title"></span>
 <div data-bind="text: notes" class="notes"></div>
</div>