Master PG

プログラムし続ける...

【Polymer 1.0】イベント


目次


イベントリスナの設定

ホストエレメント(自身のエレメント)のイベントリスナを設定するにはlistenersオブジェクトを使用し、イベントとイベントハンドラを関連付けます。

listenersには、イベント名と関連付けるイベントハンドラの関数名を指定します:

⇒ ソースコード

Polymer({
  is: 'x-custom',
  listeners: {
    'tap': '_onTap'
  },
  _onTap: function (e, detail) {
    // イベント発生時の処理を記述
  }
});

またthis.$ハッシュに格納されるid付きのノードも指定できます:

⇒ ソースコード

<dom-module id="x-custom">
  <template>
    <span id="playground">Tap me!</span>
  </template>
  <script>
    Polymer({
      is: 'x-custom',
      listeners: {
        // this.$ハッシュに格納されるid付きノードを指定できる
        'playground.tap': '_playgroundOnTap'
      },
      _playgroundOnTap: function (e, detail) {
        // id="playground"のspanがタップされた際の処理を記述
      }
    });
  </script>
</dom-module>


アノテーションによるイベントリスナの設定

<template>内にあるエレメントのイベントリスナの設定方法としてon-eventアノテーションがあり、eventの部分に実際のイベント名を指定します。この方法によりlistenersイベントリスナ設定で必要だったエレメントのid付与を行わなくてすみます。

⇒ ソースコード

<dom-module id="x-custom">
  <template>
    <button on-click="_buttonOnClick">Click me!</button>
  </template>
  
  <script>
    Polymer({
      is: 'x-custom',
      _buttonOnClick: function (e) {
        alert('_buttonOnClick');
      }
    });
  </script>
</dom-module>

イベントアノテーションはHTML属性で指定するので、イベント名は常に小文字化されます。これはHTMLが属性名の大文字と小文字を区別しないためです。例えば、on-myEventというイベントアノテーションは、myeventというイベントリスナの設定を行います。ただしイベントハンドラ名(上記コードでは_buttonOnClick)は大文字と小文字を区別します。

Note: ここで説明したように、イベントアノテーションは小文字に変換されます(on-myEventというイベントアノテーションがmyeventに変換されるように)。混乱をさけるため、イベント名は常に小文字を使用してください。


カスタムイベント

ホストエレメントからカスタムイベントを発火するにはfire()メソッドを使用します。またイベントハンドラに固有のデータを渡したい場合は、fire()メソッドの引数にデータを渡してください。イベントハンドラに渡されたデータは、イベントオブジェクトのdetailというプロパティに設定されています。

⇒ ソースコード

<!DOCTYPE html>
<html>
<head>
  <script src="../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
  <link rel="import" href="../bower_components/polymer/polymer.html">
</head>
<body>

<x-custom></x-custom>

<dom-module id="x-custom">
  <template>
    <button on-click="_fireKickOnClick">Kick Me</button>
  </template>

  <script>
    document.addEventListener('WebComponentsReady', function () {
      Polymer({
        is: 'x-custom',
        _fireKickOnClick: function (e, detail) {
          // "kick"というカスタムイベントに"{kicked: true}"
          // というデータを付加して発火する。
          this.fire('kick', {kicked: true});
        }
      });
    });
  </script>
</dom-module>

<script>
  // "kick"カスタムイベントのハンドラです
  document.querySelector('x-custom').addEventListener('kick', function (e) {
    // "kick"カスタムイベントに付加されたデータ"kicked"を表示(trueが表示される)
    alert('kicked: ' + e.detail.kicked);
  })
</script>

</body>
</html>


ジェスチャーイベント

Polymerでは、ユーザーインタラクションのイベントに対してカスタム"ジェスチャー"イベントを自動で発火します。これらのイベントはタッチまたはマウス環境で一貫して発火されるので、mouse〜touch〜の代わりとなる同等のイベントを使用することが推奨されます。例えば、clickよりtapを使用することでクロスプラットフォームを実現できます。

以下はサポートされるジェスチャーイベントの簡単な説明と、イベントハンドラが受け取るイベントオブジェクトのevent.detailで利用可能なプロパティを記載してます:

  • down - finger/button のdownイベント
    • x - clientXイベント座標
    • y - clientYイベント座標
    • sourceEvent - downアクションの起因となるオリジナルDOMイベント
  • up - finger/button のupイベント
    • x - clientXイベント座標
    • y - clientYイベント座標
    • sourceEvent - upアクションの起因となるオリジナルDOMイベント
  • tap - downupが起因となるイベント
    • x - clientXイベント座標
    • y - clientYイベント座標
    • sourceEvent - tapアクションの起因となるオリジナルDOMイベント
  • track - down状態の間の finger/button のmoveイベント
    • state - トラッキング状態を示す文字列:
      • start - トラッキングが最初に見つけられ発火した状態(finger/buttondownされ、一定間隔moveした状態)
      • track - トラッキングしている状態
      • end - トラッキングが終了している状態
    • x - clientXイベント座標
    • y - clientYイベント座標
    • dx - 最初にtrackイベントがあった場所からの横ピクセルの移動量
    • dy - 最初にtrackイベントがあった場所からの縦ピクセルの移動量
    • ddx - 最後にtrackイベントがあった場所からの横ピクセルの移動量
    • ddy - 最後にtrackイベントがあった場所からの縦ピクセルの移動量
    • hover() - 現在ホバーされているエレメントを決定するために呼び出される関数


次にジェスチャーイベントの例を示します。灰色の領域でドラッグを行うと、その動作ログが「Drag me!」の領域に表示されます:

⇒ ソースコード

f:id:masterpg:20160923104859p:plain

<dom-module id="drag-me">
  <style>
    #playground {
      width: 350px;
      height: 350px;
      background: gray;
    }
  </style>
  
  <template>
    <div>{{message}}</div>
    <div id="playground" on-track="_playgroundOnTrack"></div>
  </template>
  
  <script>
    Polymer({
      is: 'drag-me',
      properties: {
        message: {type: String, value: 'Drag me!'}
      },
      _playgroundOnTrack: function (e) {
        switch (e.detail.state) {
          case 'start':
            this.message = 'Tracking started!';
            break;
          case 'track':
            this.message = 'Tracking in progress... ' +
              e.detail.x + ', ' + e.detail.y;
            break;
          case 'end':
            this.message = 'Tracking ended!';
            break;
        }
      }
    });
  </script>
</dom-module>


ジェスチャーイベントをハンドリングすることでタッチ環境でのスクロール制御を行うことができます。例えば、タッチ環境でtrackイベントをハンドリングしているエレメントをスワイプしても、デフォルトでは画面がスクロールしないようになっています。スクロール動作はthis.setScrollDirection(direction, node)メソッドで制御することができます。directionには「'x', 'y', 'none', 'all'」のいずれかを指定し、nodeには対象のエレメントを指定します。

次のサンプルはタッチ環境の端末(スマートフォンなど)で実行してみてください。スクロール"なし"を選択して灰色の領域をスワイプしても青色の領域はスクロールしません。スクロール"あり"を選択して灰色の領域をスワイプすると青色の領域がスクロールします:

⇒ ソースコード

f:id:masterpg:20160923104944p:plain

<dom-module id="drag-me">
  <template>
    <style>
      :host {
        display: block;
        width: 10000px;
        height: 10000px;
        color: white;
        background-color: blue;;
      }
      .container {
        position: relative;
        left: 50px;
        top: 50px;
      }
      #playground {
        width: 350px;
        height: 350px;
        background: gray;
      }
    </style>
    
    <div class="container">
      <!-- メッセージ領域 -->
      <div>{{message}}</div>
      <!-- スクロールあり/なしのラジオボタン -->
      <div>
        <span>スクロール: </span>
        <input type="radio" id="directionOff" name="direction" value="none"
               on-change="_changeDirection" checked>なし
        <input type="radio" id="directionOn" name="direction" value="all"
               on-change="_changeDirection">あり
        <span>(タッチ環境用の設定)</span>
      </div>
      <!-- ドラッグ領域 -->
      <div id="playground" on-track="_handleTrack"></div>
    </div>
  </template>
  
  <script>
    Polymer({
      is: 'drag-me',
      properties: {
        message: {type: String, value: 'Drag me!'}
      },
      ready: function () {
        this._changeDirection();
      },
      // ドラッグ領域でトラックが発生した際のハンドラ
      _handleTrack: function (e) {
        switch (e.detail.state) {
          case 'start':
            this.message = 'Tracking started!';
            break;
          case 'track':
            this.message = 'Tracking in progress... ' +
              e.detail.x + ', ' + e.detail.y;
            break;
          case 'end':
            this.message = 'Tracking ended!';
            break;
        }
      },
      // スクロールのあり/なしを変更するメソッド
      _changeDirection: function () {
        if (this.$.directionOff.checked) {
          this.setScrollDirection('none', this.$.playground);
        } else if (this.$.directionOn.checked) {
          this.setScrollDirection('all', this.$.playground);
        }
      }
    });
  </script>
</dom-module>


Eventの再標的化(Event retargeting)

Shadow DOMにはEventの再標的化(Event retargeting)と呼ばれる機能があります。この機能によって、イベント発生元のエレメントが置き換えられ、カスタムエレメント自身でイベントが発生したかのような動作になります。Shady DOMは再標的化が行われないので、環境に依存した振る舞いをすることになります。

Polymer.dom()メソッドの引数にイベントハンドラのイベントオブジェクトを指定することで、Polymerによって標準化されたイベントオブジェクトを取得できます。このオブジェクトを使用することによって、Shadow DOMとShady DOM両方の環境で同じイベントターゲットを取得することができます。標準化されたイベントオブジェクトは次のプロパティを持ちます:

  • Polymer.dom(event).rootTarget: Shadow DOMが再標的化される前の、オリジナルまたはルートのターゲットです。
  • Polymer.dom(event).localTarget: 再標的化されたターゲット、つまりカスタムエレメント自身です。
  • Polymer.dom(event).path: イベントがどのノードを通過してきたかを示すパスです。


まずは次のサンプルをShady DOM環境で実行し、「Click me!」をクリックした際のイベントのターゲットを確認してみましょう:

Shady DOMで実行するにはクエリパラメータ「dom=shady」を付加します。

例: http://localhost:3000/src/events/retargeting.html?dom=shady

⇒ ソースコード

f:id:masterpg:20160923105108p:plain

<dom-module id="x-custom">
  <template>
    <span id="clickMe" style="border: solid blue">Click me!</span>
  </template>
  <script>
    Polymer({is: 'x-custom'});
  </script>
</dom-module>
<!DOCTYPE html>
<html>
...
<body>
<x-custom></x-custom>
<script>
  document.querySelector('x-custom').addEventListener('click', function (event) {
    console.log('■■■ rootTarget ■■■');
    console.log(Polymer.dom(event).rootTarget);
    console.log('■■■ localTarget ■■■ ');
    console.log(Polymer.dom(event).localTarget);
    console.log('■■■    path     ■■■ ');
    console.log(Polymer.dom(event).path);
    console.log('■■■   target   ■■■');
    console.log(event.target);
  });
</script>
</body>
</html>

Shady DOM環境で取得したイベントのターゲットは次のようになります:

  • Polymer.dom(event).rootTarget: クリックしたエレメント(id="clickMe"のspan)。
  • Polymer.dom(event).localTarget: カスタムエレメント(x-custum)自身。
  • event.target: クリックしたエレメント(id="clickMe"のspan)。

f:id:masterpg:20160923105208p:plain


次はShadow DOM環境で実行した際のイベントのターゲットを確認してみましょう。

Shadow DOMで実行するにはクエリパラメータ「dom=shadow」を付加します。

例: http://localhost:3000/src/events/retargeting.html?dom=shadow

Shadow DOM環境で取得したイベントのターゲットは次のようになります:

  • Polymer.dom(event).rootTarget: クリックしたエレメント(id="clickMe"のspan)。
  • Polymer.dom(event).localTarget: カスタムエレメント(x-custum)自身。
  • event.target: カスタムエレメント(x-custum)自身。

f:id:masterpg:20160923105301p:plain


結果として、Polymer.dom(event)で取得したイベントのターゲットは、Shady DOMでもShadow DOMでも同じでした。ただしevent.targetはShady DOMとShadow DOMでは取得されるものが異なるため、カスタムエレメントのイベントのターゲットを取得する場合はPolymer.dom(event)を使用するようにしてください