Polymer サンプルコード (2) FizzBuzz

# FizzBuzz

<img src="https://www.polymer-project.org/images/logos/p-logo.png" height="26"> [Polymer](https://www.polymer-project.org/)で[FizzBuzz](https://ja.wikipedia.org/wiki/Fizz_Buzz)を表示するタグ(コンポーネント、カスタム要素)を作成し、その定義の仕方や [ループ(繰り返し)](https://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%BC%E3%83%97_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0))、[条件分岐(if文)](https://ja.wikipedia.org/wiki/If%E6%96%87) の方法を確認します。

※ [Polymer サンプルコード (1) Hello, World !](http://qiita.com/howking/items/d23c850265c0965ef55c) の続きです。

<dl>
  <dt>動作確認</dt>
  <dd>Windows 10 : ○ Chrome60, Firefox54, Edge40 × IE11</dd>
  <dd>Mac 10.3 : ○ Chrome60, Safari10.1</dd>
  <dd>Android 7.1 : ○ Chrome60</dd>
</dl>

## 画面イメージ

![qiita-plnkr-fizzbuzz](https://qiita-image-store.s3.amazonaws.com/0/90678/9bb0f653-52a1-1a79-0001-6b3063a20210.png)

[:globe_with_meridians: デモサイト (plunker)](https://plnkr.co/edit/aAgX9SsQ8hdjVgTelPyi)

## サンプルコード

- `index.html` FizzBuzzタグの呼出し元

```html:index.html
<script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>

<link rel="import" href="fizz-buzz.html">

<fizz-buzz max="20"></fizz-buzz>
```

- `fizz-buzz.html` FizzBuzzを表示するHTML

```html:fizz-buzz.html
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="https://polygit.org/components/polymer/lib/elements/dom-repeat.html">
<link rel="import" href="https://polygit.org/components/polymer/lib/elements/dom-if.html">

<dom-module id="fizz-buzz">
  <template strip-whitespace>
    <template is="dom-repeat" items="[[_nums]]">
      <div>
        <template is="dom-if" if="[[_isFizz(item)]]">Fizz</template>
        <template is="dom-if" if="[[_isBuzz(item)]]">Buzz</template>
        <template is="dom-if" if="[[_isNum(item)]]">[[item]]</template>
      </div>
    </template>
  </template>
  <script>
    class FizzBuzz extends Polymer.Element {
      static get is() { return 'fizz-buzz' }
      static get properties() {
        return {
          max: {
            type: Number,
            value: 10
          },
          _nums: {
            type: Array,
            computed: "_initNums(max)"
          }
        }
      }
      _initNums(max) {
        // generate numbers array [1...max]
        return Array.from(new Array(max)).map((v, i) => return i + 1)
      }
      _isFizz(num) {
        return num % 3 == 0
      }
      _isBuzz(num) {
        return num % 5 == 0
      }
      _isNum(num) {
        return num % 3 != 0 && num % 5 != 0
      }
    }
    customElements.define(FizzBuzz.is, FizzBuzz)
  </script>
</dom-module>
```

## 各行の説明

### index.html

```html:index.html
<script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
```
[Web Components](https://www.webcomponents.org/)のポリフィルを読み込みます。

```html:index.html
<link rel="import" href="fizz-buzz.html">
```
`<fizz-buzz>`タグを読み込みます。

```html:index.html
<fizz-buzz max="20"></fizz-buzz>
```
`<fizz-buzz>`タグを表示し、`max`属性で数えあげる最大値を指定します。

### fizz-buzz.html

```html:fizz-buzz.html
<link rel="import" href="https://polygit.org/components/polymer/polymer-element.html">
<link rel="import" href="https://polygit.org/components/polymer/lib/elements/dom-repeat.html">
<link rel="import" href="https://polygit.org/components/polymer/lib/elements/dom-if.html">
```
Polymerで[タグ(コンポーネント、カスタム要素)を定義する](http://qiita.com/jtakiguchi/items/8bcc2ef508709839985f)には`polymer/polymer-element.html`を読み込みます。ここで`<dom-module>`という定義用のタグが使えるようになります。
バージョン1.xとの互換モードでは`polymer/polymer.html`(`-element`なし)を指定し、こちらは一括で`<dom-*>`系のタグを読み込んでくれましたが、2.x系では繰り返し処理の`dom-repat`、条件分岐の`dom-if`はそれぞれ[別個で読み込む必要](http://qiita.com/jtakiguchi/items/105556142cfd6be278a7)があります。

```html:fizz-buzz.html
<dom-module id="fizz-buzz">
  <template strip-whitespace>
    ...
  </template>
  <script>
    class FizzBuzz extends Polymer.Element {
      static get is() { return 'fizz-buzz' }
      ...
    }
    customElements.define(FizzBuzz.is, FizzBuzz)
  </script>
</dom-module>
```
`<fizz-buzz>`タグを定義します。`<dom-module id="foo-bar">`と`customElements.define()`に渡す名前(`foo-bar`)を一致させる必要があります。作成できるタグには間に`-(ハイフン)`を入れなければいけない等の[ルール](http://qiita.com/jtakgiuchi/items/392ab42292b172830ff7)があり、**`<fizzbuzz>`とか`<-FIZZ-BUZZ->`とかは作れません**。(`<fizz-buzz/>`のような終了タグがないものも作れません)
`<template>`に`strip-whitespace`を指定すると生成するHTMLから余計な空白を除去してくれるそうです(参考: [Polymer templateのstrip-whitespaceについて](http://qiita.com/jtakgiuchi/items/dad1836abbee382f62b9))。

> 古いブラウザ向けにはES5に変換するか、1.x系の互換モードでファクトリメソッドを使用して定義します(まだ主要なライブラリでもこちらの書き方をしているのが多いです)。
> 
> ```html
> <link rel="import" href="https://polygit.org/components/polymer/polymer.html">
> 
> <dom-module id="fizz-buzz">
>   <template>
>    ...
>   </template>
>   <script>
>     Polymer({
>       is: 'fizz-buzz',
>       ...
>     })
>   </script>
> </dom-module>
> ```

```html:fizz-buzz.html
<template is="dom-repeat" items="[[_nums]]">
  <div>
    ...
  </div>
</template>
```

繰り返し処理には[`<dom-repeat>`](https://www.polymer-project.org/2.0/docs/api/elements/Polymer.DomRepeat)タグを使用しますが、`<dom-module>`のテンプレート内であれば[省略記法](http://qiita.com/jtakgiuchi/items/105556142cfd6be278a7#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%83%AA%E3%83%94%E3%83%BC%E3%82%BF%E3%83%BCdom-repeat)の`<template is="dom-repeat">`を使います。`items`属性にArray等の配列を指定すると繰り返し処理がされ、デフォルトで`item`という名前の変数に値がセットされます。
突然`item`という変数が出現するのに違和感があれば、`as="num"`を指定すると`num`変数に値がセットされ、`_isFizz(num)`と書くことができます)。
他にfor文とかwhile文とかありません。**繰り返しは`dom-repeat`一択です。**
直接`i=0; i<=10 ; i++`のような値でループ回数の指定はできず`Array`を渡す必要がありますが、`filter="..."`、`sort="..."`などで制御でき、`initial-count="10"`で最初にレンダリングする個数を10に指定することなどができます。

```html:fizz-buzz.html
<template is="dom-if" if="[[_isFizz(item)]]">Fizz</template>
<template is="dom-if" if="[[_isBuzz(item)]]">Buzz</template>
<template is="dom-if" if="[[_isNum(item)]]">[[item]]</template>
```
```js:fizz-buzz.html
_isFizz(num) {
  return num % 3 == 0
}
_isBuzz(num) {
  return num % 5 == 0
}
_isNum(num) {
  return num % 3 != 0 && num % 5 != 0
}
```
こちらも条件分岐の[`<dom-if>`](https://www.polymer-project.org/2.0/docs/api/elements/Polymer.DomIf)タグは[省略記法](http://qiita.com/jtakgiuchi/items/105556142cfd6be278a7#%E6%9D%A1%E4%BB%B6%E4%BB%98%E3%81%8D%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88dom-if)で`<template if="dom-if">`と書きます。
他にif-else文とかswitch文はありません。**条件分岐も`dom-if`一択です。**
githubの[リポジトリ](https://github.com/Polymer/polymer/tree/master/lib/elements)には基本のタグが置かれているので、Pull Requestを送ると増やしてくれるのかもしれません。
`[[_isFizz(item)]]`は`class FizzBuzz`で宣言しているメソッド`_isFizz(num)`を呼び出し、表示の為の真偽判定をしています。

```js:fizz-buzz.html
static get properties() {
  return {
    max: {
      type: Number,
      value: 10
    },
    _nums: {
      type: Array,
      computed: "_initNums(max)"
    }
  }
}

_initNums(max) {
 ...
}
```

`<fizz-buzz>`タグにおいて属性(`attribute`)を定義するには、`properties()`メソッドで[プロパティを定義](http://qiita.com/jtakgiuchi/items/85d4e636bc490eac699c)します。

> `max`: 数値型の初期値で10の属性を定義します。
> `_nums`: `computed`で指定している関数`_initNums(max)`は`max`プロバティの値が変更される度に呼び出され、その結果の配列が`_nums`にセットされます。

ちなみにメソッド定義部分の `_initNums(max) { ... }`の`max`は関数の引数名で、`computed: "_initNums(max)"`が指すのは`FizzBuzz`クラスの`max`プロパティです。メソッド内から`FizzBuzz`クラスの`max`プロパティを参照するには`this.max`を使用します。

## 最後に
**:bow: コメント、編集リクエスト歓迎**
[Polymer サンプルコード (3) SPA](http://qiita.com/howking/items/3fda07e706dfbd815b0e) へ続きます。

以上