Riot.jsで作る期間選択UI

2016-12-22 #Development

Riot.js Advent Calendarの22日目の記事です。

石原と申します。フリーランスでWebやアプリのデザインと、フロントエンド周りの実装をしております。

この記事ではRiot.js を作って期間選択のUIを実装する流れを書いてみようと思います。


作るもの

コードはこのページに全部書くと見難そうなので下記のリポジトリに上げています。

demo: https://is8r.github.io/riot-period/

github: https://github.com/is8r/riot-period

Riot.js以外の使ったライブラリ

Riot.jsの良さを活用してさらっと簡単なコンポーネントを作ってみようと思ったので、.tagファイル以外のコードは極力省きたくていくつか他のライブラリも使っています

上記の3つもそうですが、Riot.jsもCDNを使用しています

riot-period

コンポーネントの構成としては

  • カスタムで詳細な日付を指定する時に使用するriot-period-input
  • プルダウンを表示して「今月」とかで決まった期間を選択するriot-period-select
  • それらをまとめた親のコンポーネントであるriot-period

の3つで動いています。 実際に使用する時には下記のような感じでriot-periodを呼び出すだけになっております。

riot-periodfromtoという値をそれぞれ持っていて期間の値を簡単に取り出す事ができるように作ってみました。 updateのタイミングで最新の値が取り出せるので、それを動的に使用するか、若しくは送信ボタンとかを隣につけて送信のタイミングで値を取得してフォームに投げて、グラフを再描画する時なんかに使います。

riot-period-input

1番小さいコンポーネント、riot-period-inputinputタグだけでできています。 カスタムの日付入力のinputの要素になります。

日付の入力の煩わしさを感じさせないようにする

日付の選択UIは、PCから画面を操作する場合にカレンダーのUIを使いたいので、Pikaday プラグインを入れました。

しかしカレンダーのUIはスマホから操作している場合には逆に使いにくいですよね。 スマホではinputタグを[type=date]に設定しておくとOS搭載の日付選択UIが表示されますが、やはりデフォルトのものが一番使いやすいと思うのでスマホの時はそちらを表示させるように切り替えます。 mount時のタイミングにontouchstartの有無を確認して振り分けます。

minmaxの値を設定して想定外の数値が入らないようにする

値が変更された際、想定外の数値(例えば期間のfromよりtoの方が小さい値になってしまわないように)が入らないように制御します。

スマホの場合は$(input).on('blur', function(e) { … })で、PCの場合には Pikaday の初期設定でonSelect: function(date) { … }のタイミングで、inputタグに設定したminmaxの値と新しい値とを比較して必要に応じて訂正する処理を入れています。Moment.jsisBetweenを使うと便利です。

また、Pikadayinputタグが[type=text]になっており、保持している値もYYYY/MM/DDとなっています。

Moment.jsの初期化で使用できる書式はYYYY-MM-DDで、input[type=date]valueの値もYYYY-MM-DDなので、Pikadayを使用している場合にはアップデートの際にはそちらに合わせて書式を変更する手間がかかりました。といってもinput.value.replace(/\//g, "-“)で置換するだけですが。

riot-period-select

日付選択の項目でドロップダウンのメニューが勝手に閉じないようにする

期間選択のUIは「今月」とか「過去半年」とかの予め決められた期間を選択するボタンと、カスタムで期間の始まりと終わりをそれぞれ指定するUIの2つが入ったドロップダウンメニューで構成されています。

ドロップダウンの部分はBootstrapの機能なのですが、ドロップダウンメニューはデフォルトでは項目を触ったり領域外をクリックすると勝手に閉じるようになっています。

項目を何個か順にクリックして切り替えて考える事もあるでしょうし、カレンダーのUIから日付を選択する度にドロップダウンが閉じてしまうと困るので、勝手に閉じないようにBootstrapのイベントをe.stopPropagation()でキャンセルするようにしておきます。

項目選択のUIで、クリックしたボタンからコンポーネントにデータ属性を介して値を渡す

項目選択のUIでは、yearsmonthsなどの期間のタイプとその期間の長さの2つの値を元に必要な日付を割り出すようにしています。 各項目はdata-perioddata-period-typeの2つの値を持っていて、onclickのタイミングで受け取れる引数ee.targetから対象のaタグが取得できますので、そこからデータ属性を介して必要な値を取得して計算します。

項目選択のUIで、現在選択されているリンクはアクティブの状態に切り替える

Riot.jsではDOMにクラスを設定する場合は、普通にhtmlを書く時と同様にclass=“className"と入力する事ができますが、動的にクラスを付け替えたい場合にはclass={className: true}等で指定する事ができます。

trueの箇所にはBooleanではなく変数を使う事が可能ですが、状況に応じて変化する場合などに関数をそのまま入れる事も可能です。

今回はボタンが持っている値をisActiveという関数に渡してボタンのクラスを付け替え、アクティブ状態を切り替えるように作ってみました。(デザイン上では太字になっているだけですが…)

isActive関数は渡した値と現在のコンポーネントが保持している値を比較してtruefalseを返すという処理を行なっているだけです。

aタグが持っているdata-perioddata-period-typeの値を比較するのでthisとかでaタグ自体を渡せたら良いのですが、どうやら出来なさそうだったので引数でデータ属性の値を直接渡しています。

カスタムの日付入力項目のコンポーネントを設置する

カスタムの日付を入れるUIはriot-period-input要素を2つと決定ボタンを並べて作成しています。

期間の最大、最小の値を設定するのは本来ならばriot-period-inputの中に入れたい関数ですが、期間の始まりの日付は期間の終わりの日付の1日前にしたいのでminmaxの値を更新するような関数はこっちに書いています。

this.tags.tagNameでコンポーネントの中のタグが取得できるのでそこから中のinputを取得して値を書き換えています。

まとめ

以上のような感じで作成しています。 ざっくりと何をやっているかの説明だけを書いたので、詳細なコードはこちらをご覧ください。

Riot.jsは1つ1つの機能毎に気軽にコンポーネントを作る事ができ、後からコードを見返した時にも見通しも良くなるしシンプルなので非常に使い勝手が良くて好きです。 当初結構面倒な機能だと思ったのですが、200行に満たないコードでjsとhtmlを書く事が出来ました。

なんでこんなのを作ったかというと今人知れず個人的に作っているサービスで、(ログブックのアプリなのですが統計のグラフを表示したくて)簡単に期間を選択できるUIが欲しかった、という理由がありました。


明日のRiot.js Advent Calendarbourbonizableさんです。よろしくお願いいたします!