もとは社内のRiot.jsを知らない人、名前は知ってるけど使ったこと無い人向けに
Riot.jsの使い方を知って貰うために作成していた資料です。
Riot.jsとは
React.jsのようなJavascriptの軽量UIライブラリです。
カスタムタグにHTML、JS、CSSなどを記述して、それらを組み合わせてページを作成する事が出来ます。
前提
node.jsはインストール済み
tagファイルはプリコンパイルして使用
ディレクトリ構成
下記のような構成になるようにファイルとディレクトリを作成
sample
┣ js
┣ tags
┗ index.html ← 中身は空っぽ
プリコンパイルするための環境作成
まずは、riotをインストールします。
npm install riot -g
プリコンパイルしたくない!って場合
インブラウザ・コンパイルを利用する事で、htmlが呼び出されたときにブラウザでコンパイルして実行する事が出来ます。
上記のriotのインストールも不要になります。
ブラウザでコンパイルを行っても、パフォーマンスへの影響はほとんど無いようです。
プリコンパイルのメリットについては、公式サイトに記載されています。
公式:コンパイラ
※以下は、プリコンパイルする事を前提に記載しています。
app.tag作成
tagsディレクトリの直下にcoffee.tagファイルを作成。
coffeeという名前のカスタムタグの構成を作成します。
※タグ名とファイル名は異なっていても構いません。
<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>
<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にアクセスすると、下記のように表示されます。
カスタムタグを使いたくない場合
htmlに空のカスタムタグを書きたくない場合、idやclass名を指定してマウントする事もできます。
<ichigo>
<h1>{name}</h1>
<script>
this.name = 'いちご牛乳';
</script>
</ichigo>
<lemon>
<h1>{name}</h1>
<script>
this.name = 'れもん牛乳';
</script>
</lemon>
<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
を用いることで別ファイルに記載したロジックを実行する事ができます。
riot.mixin('Gyunyu', {
output : function(v) {
console.log(v);
}
});
<coffee>
<h1>{name}</h1>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");
</script>
</coffee>
<lemon>
<h1>{name}</h1>
<script>
this.name = 'れもん牛乳';
this.mixin("Gyunyu");
this.output("これはれもん牛乳です。");
</script>
</lemon>
<ichigo>
<h1>{name}</h1>
<script>
this.name = 'いちご牛乳';
this.mixin("Gyunyu");
this.output("これはいちご牛乳です。");
</script>
</ichigo>
<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にアクセスすると、
下記のようなログが出力されます。
イベント処理
ボタンがクリックされたときに何らかの処理を行いたい場合は、onclick
属性に実行したい関数を設定します。
<coffee>
<h1>{name}</h1>
<button type="button" name="button1" onclick={print}>ボタン1</button>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");
this.print = function () {
console.log("ボタンをクリックしました");
}
</script>
</coffee>
ボタンが押された時に、イベントオブジェクトを取得したい場合は、
下記のように、呼び出される関数に引数を記述すると、イベントオブジェクトを取得する事ができます。
<coffee>
<h1>{name}</h1>
<button type="button" name="button1" onclick={print}>ボタン1</button>
<button type="button" name="button2" onclick={print}>ボタン2</button>
<script>
this.name = 'コーヒー牛乳';
this.mixin("Gyunyu");
this.output("これはコーヒー牛乳です。");
this.print = function (event) {
console.log("クリックしたボタン:" + event.target.name);
}
</script>
</coffee>
「ボタン1」をクリックすると「クリックしたボタン:button1」、「ボタン2」をクリックすると「クリックしたボタン:button2」がコンソールに出力されます。
イベントオブジェクト以外の引数を受け取りたい場合は、bind
関数を用いる事で値を渡すことが出来るようになります。
<coffee>
<h1>{name}</h1>
<button type="button" name="button1" onclick={print.bind(this, "これはボタン1です")}>ボタン1</button>
<button type="button" name="button2" onclick={print.bind(this, "これはボタン2です")}>ボタン2</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
で値を取得する事ができます。
<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
属性を用いることでデータバインディングみたいな機能を作る事が出来ます。
<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でデータバインディング
<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タグより下に書くか、下記のような書き方にしないとダメなのかもしれない。
<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>
<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
タグをマウントする
ルートを記載しています。
<item>
<router>
<route path="/index.html"><top /></route>
<route path="/gyunyu/*"><gyunyu /></route>
</router>
</item>
カスタムタグを作成します。
<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>
<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/*」の「*」に該当する値を取得する事が出来ます。
<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
属性を用いることで、要素の数だけループを行うことが出来ます。
<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
属性を用いることで、条件に応じて要素を追加/削除する事が出来ます。
<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
属性を用いることで、条件に応じて要素を表示/非表示する事ができます。
<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の学習を始めようかなと最近思ってます。