Riot.jsの使い方について

もとは社内のRiot.jsを知らない人、名前は知ってるけど使ったこと無い人向けに
Riot.jsの使い方を知って貰うために作成していた資料です。

# Riot.jsとは
React.jsのようなJavascriptの軽量UIライブラリです。
カスタムタグにHTML、JS、CSSなどを記述して、それらを組み合わせてページを作成する事が出来ます。

[公式サイト](http://riotjs.com/ja/)


# 前提
node.jsはインストール済み
tagファイルはプリコンパイルして使用

# ディレクトリ構成
下記のような構成になるようにファイルとディレクトリを作成

```
sample
  ┣ js
  ┣ tags
  ┗ index.html ← 中身は空っぽ
```

## プリコンパイルするための環境作成
まずは、riotをインストールします。

```
npm install riot -g
```

### プリコンパイルしたくない！って場合
インブラウザ・コンパイルを利用する事で、htmlが呼び出されたときにブラウザでコンパイルして実行する事が出来ます。
上記のriotのインストールも不要になります。
ブラウザでコンパイルを行っても、パフォーマンスへの影響はほとんど無いようです。

プリコンパイルのメリットについては、公式サイトに記載されています。

公式：[コンパイラ](http://riotjs.com/ja/guide/compiler/)

※以下は、プリコンパイルする事を前提に記載しています。

## app.tag作成
tagsディレクトリの直下にcoffee.tagファイルを作成。
coffeeという名前のカスタムタグの構成を作成します。
※タグ名とファイル名は異なっていても構いません。

```html:coffee.tag
<coffee>
  <h1>{name}</h1>
  <script>
    this.name = 'コーヒー牛乳';
  </script>
</coffee>
```

## riotコマンド実行
```
riot ./tags ./js
```
tagsディレクトリ直下の`*.tagファイル`が、jsディレクトリに`*.jsファイル`として出力されます。
指定できるオプションについては、`riot --help`で確認できます。

この時点では、下記のようなディレクトリ構成になっています。

```
sample
  ┣ js
  ┃  ┗ coffee.js
  ┣ tags
  ┃  ┗ coffee.tag
  ┗ index.html ← 中身は空っぽ
```

## index.html修正
空のカスタムタグ(coffeeタグ)をhtmlに記載し、下記のように修正します。

```html:index.html
<html>
  <head>
    <title>sample</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/riot/3.7.3/riot.min.js'></script>
  </head>
  <body>
    <coffee></coffee>

    <script src="./js/coffee.js"></script>

    <script>riot.mount('coffee')</script>
  </body>
</html>
```

index.htmlにアクセスすると、下記のように表示されます。
![gyunyu.jpg](https://qiita-image-store.s3.amazonaws.com/0/206289/03f8dc2e-c3ad-02e1-2139-131385496f5a.jpeg)

### カスタムタグを使いたくない場合
htmlに空のカスタムタグを書きたくない場合、idやclass名を指定してマウントする事もできます。

```html:ichigo.tag
<ichigo>
  <h1>{name}</h1>
  <script>
    this.name = 'いちご牛乳';
  </script>
</ichigo>
```

```html:lemon.tag
<lemon>
  <h1>{name}</h1>
  <script>
    this.name = 'れもん牛乳';
  </script>
</lemon>
```

```html:index.html
<html>
  <head>
    <title>sample</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/riot/3.7.3/riot.min.js'></script>
  </head>
  <body>
    <coffee></coffee>
    <div class="lemon"></div>
    <div id="ichigo"></div>

    <script src="./js/coffee.js"></script>
    <script src="./js/ichigo.js"></script>
    <script src="./js/lemon.js"></script>

    <script>riot.mount('coffee')</script>
    <script>riot.mount('#ichigo', 'ichigo')</script>
    <script>riot.mount('.lemon', 'lemon')</script>
  </body>
</html>
```

- riot.mount('#ichigo', 'ichigo')
  idが`ichigo`の要素にichigoタグを展開

- riot.mount('.lemon', 'lemon') 
 class名が`lemon`の要素にlemonタグを展開

## ロジックを共有する
`mixin`を用いることで別ファイルに記載したロジックを実行する事ができます。

```html:app.tag
riot.mixin('Gyunyu', {
    output : function(v) {
        console.log(v);
    }
});
```

```html:coffee.tag
<coffee>
<h1>{name}</h1>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");
</script>
</coffee>
```

```html:lemon.tag
<lemon>
<h1>{name}</h1>
<script>
this.name = 'れもん牛乳';
this.mixin("Gyunyu");
this.output("これはれもん牛乳です。");
</script>
</lemon>
```

```html:ichigo.tag
<ichigo>
<h1>{name}</h1>
<script>
this.name = 'いちご牛乳';
this.mixin("Gyunyu");
this.output("これはいちご牛乳です。");
</script>
</ichigo>
```

```html:index.html
<html>
  <head>
    <title>sample</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/riot/3.7.3/riot.min.js'></script>
  </head>
  <body>
    <coffee></coffee>
    <div class="lemon"></div>
    <div id="ichigo"></div>

    <script src="./js/app.js"></script>
    <script src="./js/coffee.js"></script>
    <script src="./js/ichigo.js"></script>
    <script src="./js/lemon.js"></script>

    <script>riot.mount('coffee')</script>
    <script>riot.mount('.lemon', 'lemon')</script>
    <script>riot.mount('#ichigo', 'ichigo')</script>
  </body>
</html>
```

tagファイルをプリコンパイルして、index.htmlにアクセスすると、
下記のようなログが出力されます。
![Clipboard02.jpg](https://qiita-image-store.s3.amazonaws.com/0/206289/20550688-a836-4ef7-b87f-0c05833fda77.jpeg)

## イベント処理
ボタンがクリックされたときに何らかの処理を行いたい場合は、`onclick`属性に実行したい関数を設定します。

```html:coffee.tag
<coffee>
<h1>{name}</h1>
<button type="button" name="button1" onclick={print}>ボタン１</button>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");

this.print = function () {
    console.log("ボタンをクリックしました");
}
</script>
</coffee>
```

ボタンが押された時に、イベントオブジェクトを取得したい場合は、
下記のように、呼び出される関数に引数を記述すると、イベントオブジェクトを取得する事ができます。

```html:coffee.tag
<coffee>
<h1>{name}</h1>
<button type="button" name="button1" onclick={print}>ボタン１</button>
<button type="button" name="button2" onclick={print}>ボタン２</button>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");

this.print = function (event) {
    console.log("クリックしたボタン：" + event.target.name);
}
</script>
</coffee>
```
「ボタン１」をクリックすると「クリックしたボタン：button1」、「ボタン２」をクリックすると「クリックしたボタン：button2」がコンソールに出力されます。

イベントオブジェクト以外の引数を受け取りたい場合は、`bind`関数を用いる事で値を渡すことが出来るようになります。

```html:coffee.tag
<coffee>
<h1>{name}</h1>
<button type="button" name="button1" onclick={print.bind(this, "これはボタン１です")}>ボタン１</button>
<button type="button" name="button2" onclick={print.bind(this, "これはボタン２です")}>ボタン２</button>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");

this.print = function (message, event) {
    console.log("クリックしたボタン：" + event.target.name + "：" + message);
}
</script>
</coffee>
```

## 入力された値を取得する
要素に`ref`属性を設定することで、JavaScriptからアクセスする事ができるようになります。
`this.refs.ref属性に指定した名前.value`で値を取得する事ができます。

```html:coffee.tag
<coffee>
<h1>{name}</h1>
<input type="text" name="text1" value="" ref="message">
<button type="button" name="button1" onclick={print}>表示</button>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");

this.print = function () {
    this.name = this.refs.message.value
}
</script>
</coffee>
```

「text1」を入力して「表示」ボタンをクリックすると、h1タグのテキストに入力した値が反映されます。

## データバインディング
Riot.jsにはデータバインディングの機能がありません。
しかし、`oninput`属性を用いることでデータバインディングみたいな機能を作る事が出来ます。

```html:coffee.tag
<coffee>
<h1>{name}</h1>
<input type="text" name="text1" value="" ref="message" oninput={print}>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");

this.print = function () {
    this.name = this.refs.message.value
}

</script>
</coffee>
```

`oninput`イベントが発火するとprint関数を実行してnameを書き換える仕組みになってます。

他にも、`update`関数を使うことでデータバインディングのような機能を作ることができるようです。
参考：[Riot.jsでデータバインディング](http://kito0039.hatenablog.com/entry/2016/11/01/125325)

```html:coffee.tag
<coffee>
<h1>{this.refs.message.value}</h1>
<input type="text" name="text" value="" ref="message" oninput={update}>
<script>
</script>
</coffee>
```

追記：上記の`update`関数を使った方法、アクセス時に`this.refs.message is undefined`ってエラーが出てました。
`<h1>{this.refs.message.value}</h1>`をinputタグより下に書くか、下記のような書き方にしないとダメなのかもしれない。

```html:coffee.tag
<coffee>
<h1>{name}</h1>
<input type="text" name="text" value="" ref="message" oninput={update}>
<script>
this.on("update", function() {
    this.name = this.refs.message.value;
});
</script>
</coffee>
```

## タグベースルーティングの設定
`route+tag.js`を読み込むと利用できるようになります。

```html:index.html
<html>
  <head>
    <title>sample</title>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/riot/3.7.3/riot.min.js'></script>
    <script src='https://cdn.jsdelivr.net/npm/riot-route@3.1.2/dist/route+tag.min.js'></script>
  </head>
  <body>
    <item></item>
    <script>
      riot.mount('*');
    </script>
  </body>
</html>
```

マウントするタグを切り替えるための設定を記述したファイルを作成します。
下記は、「/index.html」にアクセスした場合は、`top`タグをマウントし、
「/gyunyu/coffee」「/gyunyu/lemon」「/gyunyu/ichigo」などにアクセスした場合は、`gyunyu`タグをマウントする
ルートを記載しています。

```html:gyunyu-router.tag
<item>
    <router>
        <route path="/index.html"><top /></route>
        <route path="/gyunyu/*"><gyunyu /></route>
    </router>
</item>
```

カスタムタグを作成します。

```html:app.tag
<top>
<h1>牛乳一覧</h1>
<a href="/sample/gyunyu/coffee">コーヒー牛乳</a><br>
<a href="/sample/gyunyu/lemon">レモン牛乳</a><br>
<a href="/sample/gyunyu/ichigo">いちご牛乳</a><br>
</top>

<gyunyu>
<h1>値段</h1>
</gyunyu>
```

index.htmlを下記のように修正。

```html:index.html
<html>
  <head>
    <title>sample</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/riot/3.7.3/riot.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/riot-route@3.1.2/dist/route+tag.min.js"></script>
  </head>
  <body>
    <item></item>
    <script src="/sample/js/app.js"></script>
    <script src="/sample/js/gyunyu-router.js"></script>
    <script>
      route.base("/sample");
      riot.mount("*");
    </script>
  </body>
</html>
```

`http://localhost/sample/index.html`にアクセスすると`top`タグをマウントし、
`http://localhost/sample/gyunyu.html`にアクセスすると`gyunyu`タグがマウントされます。

gyunyu-router.tagには、「/index.html」と「/gyunyu/\*」と記載しています。
「/sample/index.html」「/sample/gyunyu/\*」のように記載していない理由は、
index.htmlに`route.base("/sample")`を記述しているためです。
ベースパスを`/sample`に設定しているため、ルートには`/sample`の後ろに続く文字列だけを記載しています。

`route`イベントをフックすることで、URLを取得する事ができます。
下記の場合だと、「/gyunyu/\*」の「*」に該当する値を取得する事が出来ます。

```html:app.tag
<gyunyu>
<h1>値段</h1>
<div>{price}</div>
<script>
this.on("route", id => {
    if (id == "coffee") {
        this.price = "100円";
    } else if (id == "lemon") {
        this.price = "110円";
    } else if (id == "ichigo") {
        this.price = "120円";
    } else {
        this.price = "0円";
    }
})
</script>
</gyunyu>
```

# その他
## 繰り返し
`each`属性を用いることで、要素の数だけループを行うことが出来ます。

```html:app.tag
<top>
<h1>牛乳一覧</h1>
<a href="/sample/gyunyu/coffee">コーヒー牛乳</a><br>
<a href="/sample/gyunyu/lemon">レモン牛乳</a><br>
<a href="/sample/gyunyu/ichigo">いちご牛乳</a><br>
<ul>
<li each={milk}>{name}は{price}です。</li>
</ul>
<script>
this.milk = [
    {name : "コーヒーミルク", price : "100円"},
    {name : "レモンミルク", price  : "110円"},
    {name : "いちごミルク", price  : "120円"}
];
</script>
</top>
```

## 条件分岐
`if`属性を用いることで、条件に応じて要素を追加／削除する事が出来ます。

```html:app.tag
<top>
<h1>牛乳一覧</h1>
<a href="/sample/gyunyu/coffee">コーヒー牛乳</a><br>
<a href="/sample/gyunyu/lemon">レモン牛乳</a><br>
<a href="/sample/gyunyu/ichigo">いちご牛乳</a><br>
<ul>
<li each={milk}>{name}は{price}です。<div if={name === "レモンミルク"}>(レモン果肉入り)</div></li>
</ul>
</div>
<script>
this.milk = [
    {name : "コーヒーミルク", price : "100円"},
    {name : "レモンミルク", price  : "110円"},
    {name : "いちごミルク", price  : "120円"}
];
</script>
</top>
```

`show`／`hide`属性を用いることで、条件に応じて要素を表示／非表示する事ができます。

```html:app.tag
<top>
<h1>牛乳一覧</h1>
<a href="/sample/gyunyu/coffee">コーヒー牛乳</a><br>
<a href="/sample/gyunyu/lemon">レモン牛乳</a><br>
<a href="/sample/gyunyu/ichigo">いちご牛乳</a><br>
<ul>
<li each={milk}>{name}は{price}です。<div show={name === "コーヒーミルク"}>(税込み)</div><div hide={name === "コーヒーミルク"}>(地域限定)</div><div if={name === "レモンミルク"}>(レモン果肉入り)</div></li>
</ul>
</div>
<script>
this.milk = [
    {name : "コーヒーミルク", price : "100円"},
    {name : "レモンミルク", price  : "110円"},
    {name : "いちごミルク", price  : "120円"}
];
</script>
</top>
```

`show` ： 条件が真のときに、style="display: ''"として要素を表示します。
`hide` ： 条件が真のときに、style="display: none"として要素を非表示にします。

# 最後に
Riot.jsは学習途中で分からない事が多々あるのですが、Riot.jsの学習は一旦置いといて、
今更な感じもしますが、PWAの学習を始めようかなと最近思ってます。