43
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Riot.jsによる再利用しやすいコンポーネントの作り方

Last updated at Posted at 2016-09-16

Riot.jsは単純な仕様のため、複合したHTMLタグとJavaScript機能を持ち合わせた、いわゆるコンポーネントが非常に作りやすい。
今回実際作成したコンポーネントの解説を通して作りやすさを感じてもらい、Riotの普及活動に繋げたい。

#再利用しやすいコンポーネントとは
これには諸説あると思うが、自分が思うに「利用したくない」と思われるコンポーネントは再利用されない。
自分が利用したくないコンポーネントは

  • 使い方が複雑
  • 依存関係が複雑
  • カスタマイズできない。もしくはしづらい
  • 見た目がかっこわるい

なので、これらの逆をつけば再利用しやすくなるのではと考える。

#作ったもの
手書き入力した内容を自動認識し、変換された文字列を返せるもの
<input type="text">の代替として利用可能なもの
image

仕様としては

  • タッチもしくはマウス操作で手書きすると右下に認識した文字列を表示し、JavaScriptから値取得ができる
  • 右上の×を押すとクリアする

こんなものを作った。

#使い方を単純に

組み込み方

アプリケーションへの組み込み方は楽であればあるほどいい。
Riotでは

index.html
<html>
<head>
  <title></title>
</head>
<body>
    <my-handwrite></my-handwrite>
    <script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
    <script src="https://rawgit.com/riot/riot/master/riot%2Bcompiler.min.js"></script>
    <script src="my-handwrite.tag" type="riot/tag"></script>
    <script>
      riot.mount('my-handwrite');
    </script>
</body>
</html>

配置だけならこれだけでOK。
操作を組み込みたい場合はコンポーネント側で気を使う必要がある。
基本的には「普通のHTMLタグ」と使い方を合わせる。
そうすることで違和感なくHTMLが読めるようになり、利用者側が余計な時間を使わなくてすむようになる。

  • イベントハンドラーはonclick,onchangeoのようなGlobalEventHandlersと名前を合わせる
  • その他アトリビュートも同様

Riotではカスタムタグのアトリビュートに書いたものがoptsという変数のメンバーとして自動的に渡るため、以下のように簡単に定義できる。

my-handwrite.tag(抜粋)
this.height = opts.height || 100 // 高さは指定可能
this.oninput = opts.oninput  || function(e){} // 認識完了後、もしくはクリア後に呼ぶコールバック。

このように記述するだけで、パラメータを受け取ることができるようになり、
利用者側は

index.html(抜粋)
<my-handwrite height="80" oninput={inputHandler}></my-handwrite>
<script>
  var inputHandler = function(e) {
    alert(e.value)
  }
</script>

このようにパラメータ、及びイベントハンドラーを渡せる。

#依存関係をなくす
カスタムタグ単品だけもってきたら動くようにしたい。
このタグを使うためには、xx.cssとyy.cssとhogehoge.jsとfugafuga.jsが必要とか言われたらそれだけで使う気をなくすし、副作用も気になる。
なので極力vanillaで組んだ方がいい。
今回は時間との兼ね合いで素のJQueryのみ利用した。本当は外したい。
CSSについてはRiotが便利な仕組みを持っている。

my-handwrite.tag(抜粋)
<my-handwrite>
  <div name="container">
    <canvas name="canvas" width={ width } height={ height }
            ontouchstart={ writeStart(touchSwitch) }
            onmousedown={ writeStart(mouseSwitch) }></canvas>
    <label>{ value }</label>
    <button onclick={ clear }>
      <i>x</i>
    </button>
  </div>

<style>
my-handwrite div {
  position: relative;
  box-sizing: content-box;
  width: 100%;
}
...
</style>
...

<style>の位置に注目して欲しい。
HTMLタグ記述の後に記述している。
styleは<head>内に書くかインラインで<div style="..."と書かないと適用されないが、Riotではカスタムタグ中に<style>があると、利用者のHTMLのheadの最後に自動的に挿入してくれる。
実行時のDOM
image

なので、スタイルとHTMLを同梱したコンポーネントが作成できる上にインラインスタイルも書かなくていい。
便利。

#カスタマイズしやすく
Riotでは構造(HTML)と見た目(style)と動作(JavaScript)全てを1ソース内でコンポーネント化でき、しかも特殊ルールが極めて少ないためカスタマイズしやすいコンポーネントが作りやすい。
ただ、機能を1コンポーネントに詰め込みすぎたり汚いコードだと当たり前だがキツくなるため、再利用が必要なコンポーネントは読みやすさを特に意識した方がよい。
RiotではES6でもCoffeeScriptでもTypeScriptでもロジック記述できるので素敵。

#見た目をかっこよく
センスをみがくかデザイナーさんを探そう。
というのは半分冗談で、いままで述べてきたとおり、Riotでは既存のWeb技術がほぼそのまま利用可能なため、参考になるサイトの気に入った部分のHTMLとCSSをそのままもってきてコンポーネント化してもそれなりに動く。
実はRiotのメリットとしてこれが一番大きい気がする。

#今回作ったコンポーネントの動作仕様
image

  1. コンポーネント内のcanvasの入力内容をフック
  2. 入力内容をGoogle手書き入力APIにAjax送信
  3. 返されてきた認識結果を取得
  4. コンポーネント内のlabelに認識結果を表示
  5. コンポーネント利用者にonchangeを通知

通信付きコンポーネントなのでオフライン利用はできない。

##Google手書き入力APIについて
Google 入力ツールというツールをGoogleが提供している。
膨大な手入力のデータを深層学習により解析しているらしく、手書きのストロークパターンからかなりの精度で近しい文字列に変換してくれる。
提供形態としては、AndroidアプリChromeの拡張機能があり、どうも同じサービスを利用しているっぽかった。
今回文字認識にあたりやり方を参考にしようとしたところ、APIが特に認証もなく利用できてしまったのでそのまま使わせてもらった。
GoogleMapのように正式にAPIサービスとして提供はされてないようなので、今回のコンポーネントは結構グレーな代物ではある。
商用利用はたぶんやめた方がいい。

#アプリ仕様
以前作った足し算掛け算アプリの入力部分を今回の手書き入力コンポーネントに置き換えた。

##変更差分

<my-input>
  <div class="row">
    <div class="col-xs-offset-1 col-xs-10">
      <form class="form-horizontal">
        <div class="row">
-          <div class="form-group label-floating" >
-            <label class="control-label">a</label>
-            <input name="a" type="text" class="form-control" oninput="{notify}">
+          <div>
+            <label>a</label>
+            <my-handwrite name="a" oninput="{notify}"/>
          </div>
        </div>
        <div class="row">
-          <div class="form-group label-floating" >
-            <label class="control-label">b</label>
-            <input name="b" type="text" class="form-control" oninput="{notify}">
+          <div>
+            <label>b</label>
+            <my-handwrite name="b" oninput="{notify}"/>
          </div>
        </div>
      </form>
    </div>
  </div>
+  var self = this
  this.notify = function(e) {
    obseriot.notify(action.input, {
-      a: Number(this.a.value),
-      b: Number(this.b.value)
+      a: Number(self.tags.a.value),
+      b: Number(self.tags.b.value)
    })
  };
  this.on('mount', function() {
    $.material.init();
  })
</my-input>     

大体同じ感じで利用できてるのが分かると思う。

#できあがり
image

powerdbyは怖いから付けた。

#デモおよびソースコード

デモ:
http://run.plnkr.co/plunks/LNgklu/

ソースコード:
https://embed.plnkr.co/LNgklu/

#所感

Riot流行ってかっこいいコンポーネントがたくさん作られるようになって欲しい。
あとGoogleは偉大。

43
40
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
40

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?