Reactフロントエンド開発テンプレート（アルファ版）

#目的
Reactでのサンプルアプリケーションの実装
* 各種ライブラリを選択、組み合わせてアプリケーションを作成する。

#経緯
フロントエンドを使ったアプリケーションを構築する場合に
各種ライブラリやフレームワークの選択を行う必要があります。<br>
投稿時点（2015年11月）において、新規でこれらの仕組みを考える場合に
個人的にはAngular1.x系の採用は非推奨と考えました。
（既にリリース済みのシステムやサブシステムがある場合は除く）<br>

理由としては、以下の点が懸念されます。
* Angularチームは次のフレームワークとしてAngular2.x系を発表しており<br>
Angular1.x系をそのまま踏襲するのではなく大幅に変更する方針。
* Angular1.x系からAngular2.x系への移植性について不透明な点。
* Angularの学習コストが非常に高い。（Angular2.x系では、がらっと変わる点もマイナス）<br>
* ECMAScript2015(以下ES6)での実装は開発環境も含めて、現状Reactの方がシームレスに実装できる。<br>

では、今は何を選べば良いんだとの問いに対して、
Reactもしくは正式版のリリースまで待てるのならAngular2.x系を試してから選択するのが現実的だと考えます。<br>
しかし、Angular2.x系の正式版のリリースまでは待てないので、
とりあえず実績が多いAngular1.x系かなという安易な決定は避けたいと考え、
Reactではまだまだ実装サンプルも少なく、具体的なイメージも湧きづらいと思い、サンプルアプリケーションを実装しました。<br>

作成したソースに無駄な内容や誤っている点等あると思いますが、ご了承ください。
モジュールの選択について、こっちのモジュールの方が良いよとのご意見がありましたらお知らせ頂ければと思います。<br>
<br>

#作成サンプルURL
http://github.com/andoaki/ReactTemplate_Alpha
<br>

#サンプルの起動方法
※ reduxのサンプルとほぼ同じです。
###１．Gitよりサンプルをダウンロード
###２．カレントディレクトリで「npm install」
（C++が必要等のエラーメッセージが出た場合には、インストールなどを行って対応して下さい。）
###３．カレントディレクトリで「npm start」
###４．ブラウザのURLに「http://localhost:3000/」 を入力してログイン画面表示。
（ログインIDとパスワードには同じものを入力しないと認証エラーになるので同じものを入力）<br>

#画面構成
|   画面   | メニュー | 内容 |
|---------------|------------|------------|
| ログイン画面 | | ログインID、パスワードの入力チェック、次画面遷移 |
| メイン画面 | トップ画面 | トップ画面表示 |
| メイン画面 | 登録画面 | テキストボックスでの登録処理 |
| メイン画面 | サーバアクセス画面 | サーバデータアクセス処理、テーブル表示 |
| メイン画面 | お絵かき画面 | HTML5のCanvasを使用 |
| メイン画面 | 結果報告画面 | テキストボックス、ドロップダウン、オプションボタン、ダイアログ表示 |
| メイン画面 | Google | Google画面表示 |
| メイン画面 | ログアウト | ログイン画面へ戻る |
<br>

#主な利用ライブラリ
| ライブラリ名 | バージョン |
|---------------|------------|
| react | 0.14.0 |
| redux | 3.0.4 |
| react-router | 1.0.0-rc1 |
| react-bootstrap | 0.25.100-react-pre.1 |
| react-bootstrap-table | 0.9.17 |
| es6-react-mixins | 0.2.1 |
| superagent | 1.4.0 |
<br>

#ポイント解説
ソースファイルの数が多い為、ポイントを絞って解説します。

###１．react-reduxのProviderとreact-routerでルーティングを記述しています。
```js:containers/Sample1App.js
  render() {
    return (
      <div>
        <Provider store={store}>
          <Router history={this.props.history}>
            <Route path='/' component={Login}/>
            <Route component={Template}>
              <Route path='/top' component={Top}/>
              <Route path='/gamen1' component={Gamen1}/>
              <Route path='/gamen2' component={Gamen2}/>
              <Route path='/gamen3' component={Gamen3}/>
              <Route path='/gamen4' component={Gamen4}/>
            </Route>
          </Router>
        </Provider>
      </div>
    );
  }
```

###２．ログイン画面です。認証ＯＫの字の画面遷移に「history.pushState」を使うのでes6-react-mixinsでmixinしています。
```js:containers/Login.js
  class Login extends mixin(History) {
  ・・・・・・・・
  handleLogin() {
    this.state.actions.loginOnClick();
    if (this.props.login.confirm == '1') {
      this.history.pushState(null, '/top', '');
    }
  }
  ・・・・・・・・
```
###３．メイン画面のテンプレート画面です。
```js:containers/Template.js
  render() {
    const { isMenuActive } = this.state
    const activeClass = isMenuActive ? 'active' : ''

    return (
      <div id="layout" className={activeClass}>
        <div id="my_header"><h2>ヘッダー</h2></div>
        <div id="my_navigation">
          <a href="#menu" id="menuLink"
            className={classnames('menu-link', activeClass)}
            onClick={this.handleMenuClick.bind(this)}>
            <span></span>
          </a>

          <Menu activeClass={activeClass} />
        </div>

        <div id="my_contents">
          <div id="main">
            {this.props.children}
          </div>
        </div>

        <div id="my_footer">
          <Footer />
        </div>

      </div>
    )
  }
```
###４．サーバーアクセス画面です。Gamen2Logic1.getDataでサーバーからデータを取得しています。サーバーからデータを取得出来たら、取得したデータでstateを変更します。

```js:containers/Gamen2.js
  constructor(props, context) {
    super(props, context);
    const actions = bindActionCreators(Gamen2Actions,this.props.dispatch);
    var logic1 = new Gamen2Logic1();
    this.state = {
      actions: actions,
      logic1: logic1
    };
  }
  ・・・・・・・・
  handleSelect(file) {
    const _this = this;
    this.state.logic1.getData(file)
    .then((obj) => {
      console.debug(obj);
      _this.state.actions.selectOnChange(obj);
    }).catch((err) => {
      console.error(err);
    });
  }
```
###５．サーバーアクセスロジッククラスです。ES6のPromiseを使ってハンドリングしています。
```js:app/Gamen2Logic1.js
  constructor(props, context) {
    super(props, context);
    const xhr = new XhrApi();
    this.state = {
      xhr: xhr
    };
  }

  getData(file) {
    return new Promise((resolve, reject) => {
      var url = 'http://localhost:3000/resources/json/' + file;
      this.state.xhr.getXhr(url)
      .then((obj) => {
        console.debug(obj);
        resolve(obj);
      }).catch((error) => {
        reject(error);
      });
    });
  }
```
###６．superagentを使ってサーバアクセスを行います。
```js:middleware/XhrApi.js
  getXhr(url) {
    return new Promise((resolve, reject) => {
        superagent
            .get(url)
            .end((error, res) => {
            	error ? reject(error) : resolve(res);
            });
    });
  }
```
###７．HTML5のキャンバスを使ってお絵かきアプリを実装しています。
```js:containers/Gamen3.js
  componentDidMount() {
    var canvas = document.getElementById('my_canvas');
    var ctx = canvas.getContext('2d');
    this.state.ctx = ctx;
    canvas.addEventListener('mousemove', this.handleMousemove.bind(this));
    canvas.addEventListener('mousedown', this.handleMousedown.bind(this));
    canvas.addEventListener('mouseup', this.handleMouseup.bind(this));
  }

  componentWillUnmount() {
    var canvas = document.getElementById('my_canvas');
    canvas.removeEventListener('mousemove', this.handleMousemove);
    canvas.removeEventListener('mousedown', this.handleMousedown);
    canvas.removeEventListener('mouseup', this.handleMouseup);
  }

  handleMousemove(event) {
    this.state.mouseX = event.pageX;
    this.state.mouseY = event.pageY;
    if (this.state.blush) {
        this.state.ctx.beginPath();
        this.state.ctx.moveTo(this.state.x + this.state.offsetX, this.state.y + this.state.offsetY);
        this.state.ctx.lineTo(this.state.mouseX + this.state.offsetX, this.state.mouseY + this.state.offsetY);
        this.state.ctx.stroke();
    }
    this.state.x = this.state.mouseX;
    this.state.y = this.state.mouseY;
  }

  handleMousedown(event) {
    this.state.blush = true;
  }

  handleMouseup(event) {
    this.state.blush = false;
  }

  render() {
    return (
      <div>
          <h3>お絵かき画面</h3>
          <canvas id="my_canvas" width="300" height="200"></canvas>
      </div>
    );
  }
```
###８．各種画面コンポーネントを実装しています。
```js:containers/Gamen4.js
テキストコンポーネント
<FwInputText onSave={this.handleSaveInputText1.bind(this)}
 text={this.props.gamen4.inputText1} className='alert-success col-sm-3' ref='inputText1'/>

ドロップダウンコンポーネント
<FwDropDown id='dropDown1' value={this.props.gamen4.dropDown1} className='col-sm-1'
onSelect={this.handleSelectDropDown1.bind(this)} items={hojin_sybt_items} ref='dropDown1'/>

ラジオボタン
<FwRadioButton items={customer_keitai_radios} value={this.props.gamen4.customer_keitai_radios}
className='col-sm-10' onClick={this.handleSelectCustomerKeitaiRadios.bind(this)} ref='customer_keitai_radios'/>

ダイアログ
<Modal show={this.props.gamen4.showModal} onHide={this.close.bind(this)}>
  <Modal.Header closeButton>
    <Modal.Title>モーダルダイアログのサンプル</Modal.Title>
  </Modal.Header>
  <Modal.Body>
    <h4>サンプル実装内容</h4>
    <p>引数の受け渡しテスト</p>
    <hr />
    <h4>
    <FwLabel className='col-sm-2' text='親画面からの引数' for=''/>
    </h4>
    <p>
    <FwLabel className='col-sm-2' text={this.props.gamen4.dialogArgs1} for='' ref='dialogArgs1'/>
    </p>
    <h4>
    <FwLabel className='col-sm-2' text='親画面への戻り値' for=''/>
    </h4>
    <p>
    <FwInputText onSave={this.handleSelectDialogReturn1.bind(this)}
        text={this.props.gamen4.dialogReturn1} className='alert-success col-sm-3' ref='dialogReturn1'/>
    </p>
  </Modal.Body>
  <Modal.Footer>
    <FwButton text={'Close'} placeholder={'ダイアログを閉じます'} onClick={this.close.bind(this)} />
  </Modal.Footer>
</Modal>
```
#次回展望
React自身のバージョンアップや各種ライブラリの入れ替え等がある程度発生し、
作成できる暇があれば次のバージョンを作成したいですね。