Polymerを使えば、宣言的記述によりWeb Componentsを簡単に作成することができます。

New web developers can simply add custom HTML elements on a web page with markdown. It’s just like using the HTML tags you’re already familiar with:

<h1>A heading!</h1>
<fancy-thing>A fancy thing!</fancy-thing>

カスタム要素は、Polymerの提供する以下のような機能を利用でき、シンプルな記述によって複雑でインタラクティブな性質を持ったカスタム要素を容易に構築することができます。:

  • カスタム要素の登録
  • ライフサイクルコールバック
  • プロパティの監視
  • Shadow DOMテンプレート
  • データバインディング

このチュートリアルでは、何もインストールすることなくPolymerライブラリのクイックツアーを体験できるようにしています。チュートリアル内のPLUNKERのサンプルコードというリンクをクリックすれば、サンドボックスの上でサンプルを試すことができます。

また、ボタンをクリックすれば、各セクションで紹介した機能に関連するドキュメントの該当ページへ移動できます。

新しいカスタム要素を登録するには、ES6で導入されたclass構文を使いPolymer.Elementクラスを拡張した上で、customElements.defineメソッドを呼び出しカスタム要素をブラウザに_登録_します。この登録によってカスタム要素名とclass名が関連付けられます。なお、カスタム要素の名前はASCII文字で始まりダッシュ(-)を含める必要があります

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<script>
  // Define the class for a new element called custom-element
  class CustomElement extends Polymer.Element {
    static get is() { return "custom-element"; }
    constructor() {
        super();
        this.textContent = "I'm a custom-element.";
      }
  }
  // Register the new element with the browser
  customElements.define(CustomElement.is, CustomElement);
</script>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="custom-element.html">
  </head>
  <body>
    <custom-element></custom-element>
  </body>
</html>

Try it out in Plunker:

  • Try modifying the contents of this.textContent.
  • If you’re familiar with your browser’s developer tools, try printing the custom element’s tagName property to the console. Hint: add console.log(this.tagName); to the constructor method!

このサンプルでは、ライフサイクルコールバックをして<custom-element>の初期化時にコンテンツを追加しています。初期化が完了すると、readyというコールバックが呼び出されます。readyコールバックは、カスタム要素の生成後にワンタイムの初期化を行いたい場合に利用できます。

Learn more: カスタム要素の登録

Learn more: ライフサイクルコールバック

多くの要素は、独自のUIや動作を実装するために内部にDOMノードを持っています。PolymerのDOMテンプレートを使うことで、カスタム要素にShadow DOMというDOMのサブツリーを作成できます。

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<dom-module id="dom-element">

  <template>
    <p>I'm a DOM element. This is my shadow DOM!</p>
  </template>

  <script>
    class DomElement extends Polymer.Element {
      static get is() { return "dom-element"; }
    }
    customElements.define(DomElement.is, DomElement);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="dom-element.html">
  </head>
  <body>
    <dom-element></dom-element>
  </body>
</html>

Try it out in Plunker:

  • Try adding some other html elements inside the block. For example, add <h1>A heading!</h1> or <a href=”stuff.html”>A link!</a>

Shadow DOMはカスタム要素の内部にカプセル化されています。

Learn more: DOM templating

Shadow DOMを使うことでカスタム要素を柔軟に構築できます。カスタム要素の子は割り当てられるので、Shadow DOMツリーに挿入されたかのようにレンダリングされます。

このサンプルでは、ロゴイメージをCSSでスタイリングされた<div>タグで囲うことで、シンプルなカスタムタグを作成しています。

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<dom-module id="picture-frame">

  <template>
    <!-- scoped CSS for this element -->
    <style>
      div {
        display: inline-block;
        background-color: #ccc;
        border-radius: 8px;
        padding: 4px;
      }
    </style>
    <div>
      <!-- any children are rendered here -->
      <slot></slot>
    </div>
  </template>

  <script>
    class PictureFrame extends Polymer.Element {
      static get is() { return "picture-frame"; }
    }
    customElements.define(PictureFrame.is, PictureFrame);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="picture-frame.html">
  </head>
  <body>
    <picture-frame>
      <img src="https://www.polymer-project.org/images/logos/p-logo-32.png">
    </picture-frame>
  </body>
</html>

Try it out in Plunker:

  • Try adding a <div> to index.html; is it affected by the styles in <picture-frame>'s shadow DOM?
  • Try adding other HTML elements to the DOM template to see how they are positioned relative to the distributed child nodes.

ヒント: <dom-module>の内部で定義されたCSSのスタイル情報は、カスタム要素のShadow DOM内にスコープされます。そのため、上記サンプルにおいて<picture-frame>内部のdivに対して適用したスタイルルールは、内部の<div>タグに対してのみ適用されます。

Learn more: Composition & distribution

もちろん予め静的にマークアップしたShadow DOMだけは十分でないと考えるでしょう。多くの場面において、Shadow DOMを動的にアップデートしたいと考えるはずです。そのような場合には、データバインディングというシステムを使いします。

データバインディングを使えば、簡潔なコードでカスタム要素内部で発生した変化をShadow DOMへ反映させることができます。{{}}という記号を使い、コンポーネント内のプロパティとバインドします。例えば{{}}は、カスタム要素内のプロパティfooの値によって置き換えられます。

<!-- import polymer-element -->
<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<dom-module id="name-tag">
  <template>
    <!-- bind to the "owner" property -->
    This is <b>{{owner}}</b>'s name-tag element.
  </template>
  
  <script>
    class NameTag extends Polymer.Element {
      static get is() { return "name-tag"; }
      
      // set this element's owner property
      constructor() {
        super();
        this.owner = "Daniel";
      }
    }
    
    customElements.define(NameTag.is, NameTag);
  </script>
</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="name-tag.html">
  </head>
  <body>
    <name-tag></name-tag>
  </body>
</html>

Try it out in Plunker:

  • Try editing the value of the owner property.
  • Try adding another property and binding it in your component. Hint: Add this.propertyName = "Property contents"; to the constructor and add {{propertyName}} to the element’s shadow DOM.

Learn more: data binding

プロパティは、カスタム要素が外部へ公開するAPIの重要な一部です。Polymerは、宣言的なプロパティをサポートしており、以下のような一般的な機能を提供します。:

  • プロパティにデフォルト値を設定
  • マークアップからプロパティを設定
  • プロパティの変更を監視して事前に指定した処理(observer)を実行

下記サンプルでは、一つ前に紹介したサンプルにownerプロパティの宣言を加えています。index.html内のマークアップからownerプロパティを設定しているのが分かると思います。

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<dom-module id="configurable-name-tag">

  <template>
    <!-- bind to the "owner" property -->
    This is <b>[[owner]]</b>'s name-tag element.
  </template>
  
  <script>
    class ConfigurableNameTag extends Polymer.Element {
      static get is() { return "configurable-name-tag"; }
      // configure owner property
      static get properties() {
        return {
          owner: {
            type: String,
            value: "Daniel",
          }
        };
      }
    }
    customElements.define(ConfigurableNameTag.is, ConfigurableNameTag);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="configurable-name-tag.html">
  </head>
  <body>
    <!-- configure a property from markup by setting
         the corresponding attribute                 -->
    <configurable-name-tag owner="Scott"></configurable-name-tag>
  </body>
</html>

Try it out in Plunker:

  • Try editing the initial value of owner in index.html. Observe how this sets the property directly from your HTML.

Learn more: プロパティの宣言

Shadow DOMのテキストコンテンツだけでなく、カスタム要素のプロパティに対してもバインドすることができます。(property-name="[[binding]]"という記法を使います。)Polymerのpropertiesは、任意のオプションとして双方向バインディングもサポートしています。(property-name="{{binding}}"のように{{}}を使います。)

このサンプルでは、双方向バインディング(two-way binding)を使用しています。カスタムinput要素(iron-input)のvalueownerプロパティがバインドされており、ユーザーがタイプするとカスタム要素のコンテンツもアップデートされます。

<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">
<!-- import the iron-input element -->
<link rel="import"  href="https://polygit.org/components/iron-input/iron-input.html">

<dom-module id="editable-name-tag">

  <template>
    <!-- bind to the "owner" property -->
    <p>This is <b>[[owner]]</b>'s name-tag element.</p>
    
    <!-- iron-input exposes a two-way bindable input value -->
    <iron-input bind-value="{{owner}}">
      <input is="iron-input" placeholder="Your name here...">
    </iron-input>
  </template>

  <script>
    class EditableNameTag extends Polymer.Element {
      static get is() { return "editable-name-tag"; }
      
      // configure the owner property
      static get properties() {
        return {
          owner: {
            type: String,
            value: 'Daniel'
          }
        };
      }
      
    }
    customElements.define(EditableNameTag.is, EditableNameTag);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="editable-name-tag.html">
  </head>
  <body>
    <editable-name-tag></editable-name-tag>
  </body>
</html>

Try it out in Plunker:

  • Edit the placeholder text to see two-way data binding at work.

ヒント: <iron-input>要素は、ネイティブの<input>要素のラッパーとして双方向のデータバインディングや入力値のバリデーション機能を提供します。

テンプレートリピーター(dom-repeat)は、配列とのバインドに特化したテンプレートです。配列内の各アイテムにつき一つずつテンプレート内のコンテンツをインスタンス化します。

<!-- import polymer-element -->
<link rel="import"  href="https://polygit.org/components/polymer/polymer-element.html">

<!-- import template repeater --> 
<link rel="import"  href="https://polygit.org/components/polymer/lib/elements/dom-repeat.html">

<dom-module id="employee-list">
  <template>
    <div> Employee list: </div>
    <p></p>
    <template is="dom-repeat" items="{{employees}}">
        <div>First name: <span>{{item.first}}</span></div>
        <div>Last name: <span>{{item.last}}</span></div>
        <p></p>
    </template>
  </template>
  <script>
    class EmployeeList extends Polymer.Element {
      static get is() { return "employee-list"; }
      
      // set this element's employees property
      constructor() {
        super();
        this.employees = [
          {first: 'Bob', last: 'Li'},
          {first: 'Ayesha', last: 'Johnson'},
          {first: 'Fatma', last: 'Kumari'},
          {first: 'Tony', last: 'Morelli'}
        ]; 
      }
    }
  customElements.define(EmployeeList.is, EmployeeList);
  </script>

</dom-module>
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
    <link rel="import" href="employee-list.html">
  </head>
  <body>
    <employee-list></employee-list>
  </body>
</html>

Try it out in Plunker:

  • Change the first and last names inside this.employees
  • Add another employee by inserting the following text into the array definition after Tony Morelli:
     ,
       {first: 'Shawna', last: 'Williams'} 
    

Learn more: テンプレートリピーター(dom-repeat)

これでPolymerの基本的なコンセプトは理解できたはずです。あとは、build an app with App ToolboxというCLIツールを使って実際にアプリケーションを作成したり、feature overview of the Polymer libraryを参照してPolymerライブラリの機能の概要を理解していってください。