2017.12.25

React.jsを使ってセグメントカレンダーを実装してみた


こんにちは。次世代システム研究室のT.D.Qです。

最近ではSPA (Single Page Application) Webサイトを目にすることも多くなってきました。
現場でReact.jsのSPAの開発を対応しながら、セグメントカレンダーを実装してみましたので、今回はこのセグメントカレンダーについて紹介したいと思います。

セグメントカレンダー

今回実装したセグメントカレンダーは以下の画像の通りで、いくつかの特徴があります。
まず”セグメントカレンダー”とは、今回私がカスタマイズして実装したカレンダーを示す、造語となります。
以下、今回のカスタムカレンダーをセグメントカレンダーと記述します。


セグメント

・「期間」、「日」、「週」、「月」という4セグメントあります
・セグメント間画面遷移スクロールアニメーション
・Popupサイズ変更(日単位、週単位、月単位を表示するため)

期間セグメント

決まった期間選択できるセグメントです。
 ・過去7日
 ・過去30日
 ・過去90日
 ・先週
 ・先月
 ・指定期間(指定したい期間を選択可能)

日セグメント

  1ヶ月間単位のカレンダーが表示され、日単位で選択できるセグメントです。

週セグメント

  1ヶ月間単位のカレンダーが表示され、週単位で選択できるセグメントです。

月セグメント

  1年間単位のカレンダーが表示され、月単位で選択できるセグメントです。

React.jsのライブラリ調査

各要素でReact.jsのオープンソースライブラリがあるかの調査を行いました。ほしいコンポーネントが意外と多いので、使えそうなものだけピックアップしました。

ポップアップ

ポップアップライブラリを調査したところ、Tooltip形式のコンポーネントがよさそうですので、
採用することにしました。ポップアップモーダルのサイズはCSSで指定できるのでかなり便利です。
サンプルはこのページで確認できます。
今回、簡単にポップアップできるコンポーネントを使いたいので、React.jsのSimple Popoverを採用します。

セグメント表示

次はセグメント表示です。React.jsのタブという使い方もあるかと思いますが、Segment Controlという便利なコンポーネントもあるので今回採用してみました。コンポーネント自体のデモページでいろいろなサンプルがあるので、興味のある方は是非試してください。React.js版があるのでそのままインポートして使います。

アニメーション

ページ遷移時のアニメーションについて、以下のライブラリがあるので、必要な場合は対応できます。今回、この処理はスキップします。
react-slick
react-responsive-carousel

カレンダー

人気のあるReact.jsのカレンダーコンポーネント(react-datepicker、react-date-picker、rc-calendarなど)がいくつかありますが、今回、以下の機能を対応するrc-calendarを選択することにしました。
・日付選択
・週選択
・月選択
・範囲選択
rc-calendarが使いやすいコンポーネントでサンプルも複数あるため初心者でも参考すれば実装できそうです。

開発環境の準備

MacBook Pro(macOS High Sierra)でAtom 1.2.3.1を使って開発しました。Node.jsのインストールについては公式ページをごらんください。

create-react-appを使ってサンプルSPA作成

npm install -g create-react-app
create-react-app calendar-demo
cd calendar-demo

各ライブラリをpackage.jsonに追加

package.jsonファイルに必要なライブラリを追加してインストールします。
{
  "name": "calendar-demo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "~15.1.0",
    "react-dom": "~15.1.0",
    "react-scripts": "1.0.17",
    "rc-calendar": "^9.1.8",
    "moment": "^2.13.0",
    "moment-range": "^3.0.3", 
    "react-segmented-control": "^0.2.2", 
    "react-simple-popover": "^0.2.0",
    "react-maskedinput": "^3.2.0" 
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

ライブラリのインストール

npm install
npm start

localhost:3000にアクセスし、SPAアプリのデフォルトページが表示されたら一旦開発環境の準備が完了です。

実装

今回JavaScriptを1ファイルに実装しましたが、各コンポーネントをPRJ構成にあわせたほうが良いと思います。

セグメントカレンダーを実装する

以下で、カレンダーのメインコンポーネントを配置します。
・選択した日付(FROM~TO)を表示するためのテキストボックス(表示専用)
・ポップアップ:テキストボックスにクリックすると表示するモーダル。
 このモーダルの中身は、セグメントコンポーネントです。
    <dl className={"segment_calendar"}>
        <input className="calendar_input" type="text" readOnly
            ref="segment_calendar_target"
            value={this.state.dateSelected}
            size={size ? size : 30}
            onClick={e => this.onClick()}
       />
       <Popover
           placement="bottom"
           container={this.parent}
           target={this.refs.segment_calendar_target}
           show={this.state.isShowPicker}
           onHide={this.onHide.bind(this)}
           rootClose
           style={{
               width: 280,
               height: this.state.curSegment == 'seg_period' ? 535 : this.state.curSegment == 'seg_month' ? 365 : 345
           }}>

       //セグメントコンポーネント
         {this.renderCalendarSegments()}
     </Popover>
   </dl>

セグメントコンポーネントを使って各セグメントの設置

    renderCalendarSegments() {
        //カレンダーが表示しない状態の場合は処理スキップする
        if (!this.state.isShowPicker) {
            return null;
        }
        return (
            <div>
                <SegmentControl
                    className="calendar_segments"
                    onChange={this.onChangeSegment.bind(this)}
                    value={this.state.curSegment}
                    name="period_picker_segment">
                        <span value={"seg_period"}>期間</span>
                        <span value={"seg_day"}>日</span>
                        <span value={"seg_week"}>週</span>
                        <span value={"seg_month"}>月</span>
                </SegmentControl>
                {this.renderCalendarSegment()}
            </div>
        );
    }

カレンダーコンポーネントを使って各セグメントを実装する

SegmentControlの選択が変更されるときに、onChangeイベントで選択されているセグメントをstateに保存し、Renderするときアクティブするセグメントを判定します。
renderCalendarSegment() {
    let currentSegment = this.state.curSegment;
    switch (currentSegment) {
        case 'seg_period':
            return (
                <PeriodCalendarSegment />
            );
        case 'seg_day':
            return (
                <DayCalendarSegment />
            );
        case 'seg_week':
            return (
                <WeekCalendarSegment />
            );
        case 'seg_month':
            return (
                <MonthCalendarSegment />
            );
    }
}

上記でカレンダーの形が大分実現することがきると思います。コードで説明する結構長くなるので、各セグメントのProps設定とonClickイベントを省略し、下記で簡単に各コンポーネントを説明します

・PeriodCalendarSegmentがFROM~TO日付を選択できるコンポーネントです。このコンポーネントにはいつかの期間が事前に設定されます。選択した日付をstateに保存することで、カレンダーの日付テキストボックスに表示されます。
・DayCalendarSegment:1日単位で日付を選択。カレンダー(DatePicker)1個使う。日付を選択するとstateに保存します。
・WeekCalendarSegment:1週間単位で選択。選択するとカレンダーの行をハイライトする。選択した週の日付(FROMとTO)をstateに保存します。
・MonthCalendarSegment: 1ヶ月間単位で選択。選択した月の日付(FROMとTO)をstateに保存。選択している年の1月~12月まで表示する。スタイルがrc-calendarのデフォルトスタイルがありますが、CSSだけではなくJacascriptでもカスタマイズは可能です。

動作確認


上記でカレンダーの実装が完了したので、動作確認しましょう。以下の画像の通り各セグメントが綺麗に表示されて、動作もスムーズで良い感じです。


所感

今回、React.jsを使ってセグメントカレンダーを実装してみました。オープンソースライブラリを使って効率よく各セグメントを実装できましたが、違和感なく各セグメントを切り替えることができるようにpropsの管理が少し面倒でした。また、今後はセグメント切り替え時のアニメーションやカレンダーのスタイルを変更しやすくなり、日付From~Toに直接日付入力できるようになる機能が改善していきたいところです。このセグメントカレンダーは日付の期間選択にとても便利なWidgetだと思いますので、興味のある方は是非実装してみてください。ではまた。

次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。