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

タグ: DOM

イベントに関係する要素は教えてもらえるよ。(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() 系で頑張るしかないね。 (´・ω・`)

参考

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

参考

ツリーを歩き回るならTreeWalkerはどうかな。(DOMおれおれAdvent Calendar 2015 – 14日目)

カテゴリー: JavaScript

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

2015-12-14

TreeWalkerというオブジェクトがあります。その名の通り、(DOMの)ツリーを歩き回るオブジェクトです。

例えばこんな構成。(インデント等によるテキストノードはないものとします。)

div#root
+ div#el-1
  + div#el-1-1
    + div#el-1-1-1

あるノード(#root)を起点に、長子の子孫を巡り(#el-1→#el-1-1→#el-1-1-1)、また戻ってくる(#el-1-1→#el-1→#root)という処理が必要だとします。

普通に関連ノードを参照して移動していくコードはこんな感じです。

var origin = document.querySelector('#root');
var current = origin;

// 子孫を巡る
while (current.firstChild) {
  current = current.firstChild;
  console.log(current.id);
}

// 祖先を巡る
while (current.parent && current !== origin) {
  current = current.parent;
  console.log(current.id);
}

何も問題ないように見えますか? 実はこれ、はい、何も問題ありません。まあ問題はないんですが、せっかくなのでこれをTreeWalkerを使っておきかえるとこんな感じです。

var origin = document.querySelector('#root');
var walker = document.createTreeWalker(origin);

// 子孫を巡る
while (walker.firstChild()) {
  console.log(walker.currentNode.id);
}

// 祖先を巡る
while (walker.parentNode()) {
  console.log(walker.currentNode.id);
}

TreeWalkerが指し示すノードは currentNode に格納されており、この内容がくるくる変わります。 parentNode() はプロパティではなくメソッドで、 walker の状態を変更する副作用を持ちます。

ちなみに戻り値に次の currentNode に相当するものを返します。対象が存在しない場合は null を返し、 currentNode の値を変更しません。

全部めぐる

せっかくなので長子だけじゃなくて全部歩き回るコードも載せておきます。

var walker = document.createTreeWalker(document.body);
walkThrough(walker);

function walkThrough(walker) {
  if (walker.firstChild()) {
    do {
      var node = walker.currentNode;
      // 要素ノードなら要素名を出力し、子の子を出力
      if (node.nodeType === node.ELEMENT_NODE) {
        console.log(node, node.tagName);
        walkThrough(walker);
      }
      // 文字列ノードなら文字列を出力
      else if (node.nodeType === node.TEXT_NODE) {
        var text = String.trim(node.nodeValue);
        if (text) {
          console.log('#', text.slice(0, 127));
        }
      }
    } while(walker.nextSibling());
    walker.parentNode();
  }
}

その他

  • document.createTreeWalker() で作ります。ルートになるノードの指定が必須です。
  • 第二引数でフィルターを指定する事ができます。
    • 例: document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT)
    • フィルターは | で区切って複数指定できます。
  • ルートとされたノードは walker.root に格納されます。
  • ルートとされたノードより上位へはたどれません。 walker.parentNode() が null を返します。

使い道

んー特に思い付きませんでした。

同じインスタンスを使い続けられるので、参照の管理が楽なのが利点?

環境

IE 9+。その他問題ないようです。

参考