※スマホ対応はしてません。

タグ: Advent Calendar 2015

イベントに関係する要素は教えてもらえるよ。(DOMおれおれAdvent Calendar 2015 – 19日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 19日目分

2015-12-19

イベントのコールバック実行時、 this が監視対象の要素になるんですが、引数で与えられるEventオブジェクトからも得る事ができます。

<div id="wrapper">
  <button id="button"></button>
</div>
var el = document.querySelector('#wrapper');
el.addEventListener('click', function(event) {
  console.log(this.id, event.target.id, event.currentTarget.id);
});

event.currentTarget

event.currentTarget が this と同じになります。(なので、よく bind() で this は違うものにしたりします。)

event.target

event.target は最初にイベントが起こった要素です。上記の例だと監視しているのは <div> です。その要素内の <button> をクリックした場合、 event.target はクリックした <button> 、 event.currentTarget は監視している <div> になります。

event.relatedTarget

他に関連する要素を格納しています。マウス操作のイベントにのみ設定されます。 focus や mouseenter のイベントで、「どこから移動してきたか」「どこへ移動したか」を示したります。

<input id="text-1" type="text" />
<input id="text-2" type="text" />
var el = document.querySelector('#text-1');
el.addEventListener('focus', function(event) {
    console.log('from:', event.relatedTarget);
});
el.addEventListener('focusout', function(event) {
    console.log('to:', event.relatedTarget);
});

使った事ないです。古いIEでも使えますが、Firefoxが頑なに実装してません。(?)

参考

jQueryのon()をDOMでやるとaddEventListener()。ちょいと面倒だな。(DOMおれおれAdvent Calendar 2015 – 18日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 18日目分

2015-12-18

jQueryだと on() で楽ちんなんだけど、DOMでは addEventListener() になります。な、長い!

var el = document.querySelector('#the-button');
el.addEventListener('click', function(event) {
  alert('Hello world!');
});

第三引数

ふつうは addEventListener(type, listener) という形で使うけれど、実は addEventListener(type, listener, useCapture) のように三つめの引数 useCapture を取る事ができます。

昔は必須だったけど今は省略可能で、省略時は false となります。

で、MDNを見るとこんな註釈が挿入されてます。(英語版にも同様の註釈あり。)

注記: useCapture は、主要なブラウザの最近のバージョンでのみ省略可能です。例えば、Firefox 6 より前では省略できません。幅広い互換性を保つために、引数を省略すべきではありません。

Firefox 6のリリースは2011年。IE と違って何年も前のものを使い続ける事はないだろうし、省略しちゃっても全然構わないと思います。

用途の説明は省きます。使わんし。

bind() の併用

addEventListener() で登録した関数は、実行時は対象の要素オブジェクトを this として呼ばれます。MVxのViewとかに分けて作ってるとそういうの困るので、 bind() して使う事が多いかと思います。

var view = {
  el: null,

  initialize: function(options) {
    this.el = options.el;
    this.name = options.name;

    this.el.addEventListener('click', this.onclick.bind(this));
  },

  onclick: function(event) {
    alert(this.name);  // => "Orange"
  }
};

view.initialize({
  el: document.querySelector('button'),
  name: 'Orange'
});

環境

IE 9+。他は問題なし。

IE 8までは attachEvent() という別のメソッドで同様の結果を得られます。ほとんど一緒だけど、イベント名の前に "on" を追加する必要があります。(例: "click" → "onclick" ) あとはコールバック関数に与えられる引数の内容がちょっと違うとか。

参考

data属性を扱うならdatasetで楽ちん。(DOMおれおれAdvent Calendar 2015 – 17日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 17日目分

2015-12-17

HTML5になって data-xxx という形の任意の属性を、あらゆるタグに書けるようになりました。属性なので getAttribute() とかで扱えるんだけど、それとは別に専用の dataset というものもあります。

var el;  // => <div data-foo="123" data-sushi-type="roll" />

// 取得
console.log(el.getAttribute('data-foo'));  // => "123"
console.log(el.dataset.foo);               // => "123"

// 設定
el.dataset.foo = "Woof!";
console.log(el.getAttribute('data-foo'));  // => "Woof!"

// ハイフン繋がりの名前
console.log(el.dataset.sushiType);  // => "roll"

というわけで、こんな感じです。

  • data-xxx について el.dataset.xxx の書式でアクセスできる
  • 名前がハイフン繋がり data-xxx-yyy の場合はキャメルケース xxxYyy になる
  • 設定値は文字列のみ
    • 非文字列は文字列へ変換される(例: null → "null" )
    • ちなみに setAttribute() の値に null を指定すると、 removeAttribute() の動作になる
  • 属性がなければ null が返る
  • 属性値がなければから文字列 "" が返るっぽい(場合によるかも)
  • delete el.dataset.xxx で data-xxx 属性を削除

環境

IE 11+。他は問題なし。

古いIEに対応するなら getAttribute() 系で頑張るしかないね。 (´・ω・`)

参考

compareDocumentPosition()で要素の位置を比較できるよ。(DOMおれおれAdvent Calendar 2015 – 16日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 16日目分

2015-12-16

HTMLが以下のような構成だとして、 #origin を起点に各要素との位置を比較してみます。

compareTargetTree
入れ子になった要素群。

var origin = document.querySelector('#origin');
var parent = document.querySelector('#parent');

var result = origin.compareDocumentPosition(parent);
console.log(!!(result & origin.DOCUMENT_POSITION_PRECEDING));     // => true
console.log(!!(result & origin.DOCUMENT_POSITION_FOLLOWING));     // => false
console.log(!!(result & origin.DOCUMENT_POSITION_CONTAINS));      // => true
console.log(!!(result & origin.DOCUMENT_POSITION_CONTAINED_BY));  // => false
console.log(!!(result & origin.DOCUMENT_POSITION_DISCONNECTED));  // => false

状態

node.DOCUMENT_POSITION_CONTAINS みたいな感じで定義されています。

状態 定数
#origin から見て先 DOCUMENT_POSITION_PRECEDING
#origin から見て後 DOCUMENT_POSITION_FOLLOWING
#origin から見て上 DOCUMENT_POSITION_CONTAINS
#origin から見て下 DOCUMENT_POSITION_CONTAINED_BY
#origin とは無関係 DOCUMENT_POSITION_DISCONNECTED

前後関係はツリーを順々に見て行った場合のものになるので、兄弟でなくてもPRECEDINGないしFOLLOWINGになります。

無関係と判断されるのは所属するドキュメントツリー自体が別の場合ですかね。ノード生成の後、まだツリーに追加していない場合とか。

上記の他に、ブラウザーが独自に拡張できる値も用意されてるみたいです。

比較は && ではなく &

ビット演算子です。演算子の説明はしませんが、この仕組みのおかげで複数の状態を同時に持つ事ができます。

var result = origin.compareDocumentPosition(el);
if (result & el.DOCUMENT_POSITION_FOLLOWING) {
  console.log('elが先に出てきます。');
}
if (result & el.DOCUMENT_POSITION_CONTAINS) {  // <-else ifじゃない
  console.log('elに含まれてます。');
}

諸々を確認した結果

compareTargetTree
入れ子になった要素群。(再掲)

vs ancestor
 - PRECEDING
 - CONTAINS
vs parent
 - PRECEDING
 - CONTAINS
vs elder
 - PRECEDING
vs elder-child
 - PRECEDING
vs younger
 - FOLLOWING
vs child
 - FOLLOWING
 - CONTAINED_BY
vs descendant
 - FOLLOWING
 - CONTAINED_BY
vs younger-anscestor
 - FOLLOWING

contains()

要素を含むかどうかだけなら origin.contains(target) というAPIもあります。

環境

IE 9+。他は大丈夫そうです。

参考

isEqualNode()で構成が同じか確認できるよ。(DOMおれおれAdvent Calendar 2015 – 15日目)

カテゴリー: JavaScript

DOMおれおれAdvent Calendar 2015 – 15日目分

2015-12-15

ノードが同じかどうか確認するには、普通に == ないし === で確認できます。

var el1 = document.body;
var el2 = document.querySelector('body');
console.log(el1 === el2);  // => true

当たり前ですね。普通ですね。

そして複製した場合は同じにはなりません。

var el1 = document.createElement('div');
var el2 = el1.cloneNode();
console.log(el1 === el2);  // => false

中身は一緒でもインスタンスが違うっていう、JavaScript初心者さんがつまづくやつです。

DOMノードの場合、その「中身は一緒」かどうかを確認する isEqualNode() というAPIが用意されています。

var el1 = document.createElement('input');
el1.id = '123';
el1.setAttribute('data-foo', 'bar');
el1.type = 'checkbox';
// <input id="123" data-foo="bar" type="checkbox" />

var el2 = el1.cloneNode();
console.log(el1 === el2);  // => false
console.log(el1.isEqualNode(el2));  // => true

確認する条件は以下の通りです。

  • 二つのノードの種類が同じである事
  • 要素名や文字列内容等が同じである事
  • 要素の属性が全て同じである事
  • 子ノード childNodes も同じである事(ちなみに cloneNode(true) だと子孫ノードもまとめて複製されます。)

属性は確認するけどプロパティは確認しないので、「同じようなチェックボックスで片方はチェックあり、片方はチェックなし」の場合にも「同じ」と判断されるみたいです。

var el1 = document.createElement('input');
el1.type = 'checkbox';
var el2 = el1.cloneNode();

el1.checked = true;
el2.checked = false;

console.log(el1.isEqualNode(el2));  // => true

参考