EagleLand

2014.08.25

HTML ImportsするHTMLのコンテキストになるdocument

Polymerに慣れたあとに、いざピュアなWeb Componentsでやろうとするとハマるかもしれないポイント。 document.registerElement()<template>やらを使ってWeb Componentsを構成するときに、ライフサイクルコールバック中にそのテンプレートタグを当然参照すると思いますが、インポートをしているとコンテキストになるdocumentがずれる話。

コードで見たほうが早いと思われるので早速例を。

Polymerを使ってx-elementを作る場合

x-element.htmlの例。

<link rel="import" href="lib/polymer/polymer.html">
<polymer-element name="x-element" attributes="foo bar">
  <template id="tmpl">
    <div>This is x-element!</div>
  </template>
  <script>
    Polymer('x-element', {
      ready: function () {
        // <template id="tmpl">を難なく取得出来る。
        console.log(this.$.tmpl);
      }
    });
  </script>
</polymer>

コメントに書いてあるけど、x-element.html内の要素を簡単に参照できる。なぜならばPolymerが上手いこと https://twitter.com/hokaccha/statuses/479063274881683457 みたいな仕組みを作っているからなんだけど、いざこれをピュアなWeb Componentsでやってみようとすると、どうなるか。

Polymerを使わずx-elementを作る場合

x-element.htmlの例。

<template id="tmpl">
  <div>This is x-element!</div>
</template>
<script>
  var XElementPrototype = Object.create(HTMLElement.prototype);
  XElementPrototype.createdCallback = function () {
    console.log(document.querySelector('#tmpl'));
  };
  window.XElement = document.registerElement('x-element', {
    prototype: XElementPrototype
  });
</script>

実は、いざこれをインポートしようとするとエラーが起きる。具体的にはdocument.querySelector('#tmpl')している箇所でエラーが起きるのだけど、なぜならばこのスクリプトを評価しているコンテキストが x-element.htmlをインポートしているHTML だからである。(逆に言えば、先程の<link rel='import'>ではなくHTMLにベタ書きすればコンテキストはそのままなのでOKではある。)

このquerySelector()の実行ドキュメントはx-element.htmlでないと、<template>はパースされておらず参照不可能なので、ちょっとやり方を変える必要がある。それにはdocument.currentScript.ownerDocumentを使う。

<template id="tmpl">
  <div>This is x-element!</div>
</template>
<script>
  var thisDocument = document.currentScript.ownerDocument;
  var XElementPrototype = Object.create(HTMLElement.prototype);
  XElementPrototype.createdCallback = function () {
    // #tmplはthisDocument内にある
    console.log(thisDocument.querySelector('#tmpl'));
  };
  // registerElementするdocument はインポート元のdocumentのままでOK
  window.XElement = document.registerElement('x-element', {
    prototype: XElementPrototype
  });
</script>

実行コンテキストは各コンポーネントとして独立させているHTML(ここでいうx-element.html)にしつつ、変数とかはインポートした先でそのまま展開されるということが頭にあれば混乱しないと思う。つまり、ここでdocument.currentScript.ownerDocumentのキャッシュでvar thisDocumentとしているけど、他のy-element.htmlでもthisDocumentとかにキャッシュさせて両方共インポートしていると、変数が上書きれて上手くいかないかと思われる。

タイトルと URL をコピーしました