Riot.jsで作る期間選択UI
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-period
はfrom
とto
という値をそれぞれ持っていて期間の値を簡単に取り出す事ができるように作ってみました。
update
のタイミングで最新の値が取り出せるので、それを動的に使用するか、若しくは送信ボタンとかを隣につけて送信のタイミングで値を取得してフォームに投げて、グラフを再描画する時なんかに使います。
riot-period-input
1番小さいコンポーネント、riot-period-input
はinput
タグだけでできています。
カスタムの日付入力のinput
の要素になります。
日付の入力の煩わしさを感じさせないようにする
日付の選択UIは、PCから画面を操作する場合にカレンダーのUIを使いたいので、Pikaday プラグインを入れました。
しかしカレンダーのUIはスマホから操作している場合には逆に使いにくいですよね。
スマホではinput
タグを[type=date]
に設定しておくとOS搭載の日付選択UIが表示されますが、やはりデフォルトのものが一番使いやすいと思うのでスマホの時はそちらを表示させるように切り替えます。
mount
時のタイミングにontouchstart
の有無を確認して振り分けます。
min
、max
の値を設定して想定外の数値が入らないようにする
値が変更された際、想定外の数値(例えば期間のfrom
よりto
の方が小さい値になってしまわないように)が入らないように制御します。
スマホの場合は$(input).on('blur', function(e) { … })
で、PCの場合には Pikaday の初期設定でonSelect: function(date) { … }
のタイミングで、input
タグに設定したmin
、max
の値と新しい値とを比較して必要に応じて訂正する処理を入れています。Moment.js のisBetween
を使うと便利です。
また、Pikaday はinput
タグが[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では、years
、months
などの期間のタイプとその期間の長さの2つの値を元に必要な日付を割り出すようにしています。
各項目はdata-period
、data-period-type
の2つの値を持っていて、onclick
のタイミングで受け取れる引数e
のe.target
から対象のa
タグが取得できますので、そこからデータ属性を介して必要な値を取得して計算します。
項目選択のUIで、現在選択されているリンクはアクティブの状態に切り替える
Riot.jsではDOMにクラスを設定する場合は、普通にhtmlを書く時と同様にclass=“className"
と入力する事ができますが、動的にクラスを付け替えたい場合にはclass={className: true}
等で指定する事ができます。
true
の箇所にはBooleanではなく変数を使う事が可能ですが、状況に応じて変化する場合などに関数をそのまま入れる事も可能です。
今回はボタンが持っている値をisActive
という関数に渡してボタンのクラスを付け替え、アクティブ状態を切り替えるように作ってみました。(デザイン上では太字になっているだけですが…)
isActive
関数は渡した値と現在のコンポーネントが保持している値を比較してtrue
、false
を返すという処理を行なっているだけです。
a
タグが持っているdata-period
、data-period-type
の値を比較するのでthis
とかでa
タグ自体を渡せたら良いのですが、どうやら出来なさそうだったので引数でデータ属性の値を直接渡しています。
カスタムの日付入力項目のコンポーネントを設置する
カスタムの日付を入れるUIはriot-period-input
要素を2つと決定ボタンを並べて作成しています。
期間の最大、最小の値を設定するのは本来ならばriot-period-input
の中に入れたい関数ですが、期間の始まりの日付は期間の終わりの日付の1日前にしたいのでmin
、max
の値を更新するような関数はこっちに書いています。
this.tags.tagName
でコンポーネントの中のタグが取得できるのでそこから中のinput
を取得して値を書き換えています。
まとめ
以上のような感じで作成しています。 ざっくりと何をやっているかの説明だけを書いたので、詳細なコードは こちら をご覧ください。
Riot.jsは1つ1つの機能毎に気軽にコンポーネントを作る事ができ、後からコードを見返した時にも見通しも良くなるしシンプルなので非常に使い勝手が良くて好きです。 当初結構面倒な機能だと思ったのですが、200行に満たないコードでjsとhtmlを書く事が出来ました。
なんでこんなのを作ったかというと今人知れず個人的に作っているサービスで、( ログブック のアプリなのですが統計のグラフを表示したくて)簡単に期間を選択できるUIが欲しかった、という理由がありました。
23日の Riot.js Advent Calendar は clown0082 さんの記事 Riot.js + webpack + ES6(Babel, buble)での開発環境構築例 ※追記webpack2 です。
Comment
comments powered by Disqus