超簡単 Reactjs もうflux、store、reduxで悩まなくても良い。onChangeのhandlerも記述不要。

GitHub [simplereactjs https://github.com/mikeshimura/simplereactjs](https://github.com/mikeshimura/simplereactjs)

Reactjsは、本来 Viewのみの部分で、それを補完するために、fluxやreduxが必要と考えられています。

しかし、下記の原則にそって記述すれば、簡単に少ないコードで作成できます。

- 状態を管理する stateは、一番 TOPのコンポーネントにのみ定義する。
- 一番 TOPのコンポーネントの参照が容易になるよう、Global変数（今回の例では　$w.app)に設定する。
- 上記のGlobal変数から、どこからでも stateが参照できる。（今回の例では　$w.app.state)
- stateの変更は、必ず SetState()関数を使用する。（今回の例では　$w.app.SetState(XXXXX))
- Inputコンポーネントの　nameは、stateの階層構造を#で区切って設定する。  
  例えば、form.nameは form#name。  
  これにより、onChange eventは、共通処理 プログラムで、
  例えば \$c.onChangeを呼び出すだけで、自動的に設定される。（CheckBoxの場合は、$c.onChecked)

## サンプルプログラム
![Simple1](https://bytebucket.org/mikeshimura/goreport/wiki/image/simplereactjs.jpg "simplereactjs")  
なお当プログラムは、HerokuでDemoが見られます。http://simplereactjs.herokuapp.com/  
また下記リンクからダウンロードして、解凍すればローカルで動かせます。  
index.htmlやmain.jsxを編集して、自分で試す事が簡単に出来ます。 


なお今回は、JSXTransforerをブラウザで動かすことで、jsxファイルの事前コンパイルを不要にしています。  
このため最新のバージョンでなく、0.13.3を使用しています。（0.14以降はサポートされていません。）  

 
[windows](https://bytebucket.org/mikeshimura/goreport/wiki/excel-by-text/excel-by-ext_windows_amd64.exe)   
[mac amd64](https://bytebucket.org/mikeshimura/goreport/wiki/excel-by-text/excel-by-text_darwin_386) 

## サンプルプログラムの動かし方  

- データ読み込みボタン  
  サーバーからデータを読み込んで、テーブルに表示します。下のフォームはクリアされます。  
  テーブル内をクリックすると、その行のデータが選択されて下のフォームに表示されます。 

```js
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.dataRead} name="btnSearch"
                style={{width:$w.W.b1,marginLeft:20}}>データ読込 </b.Button>
    ///////////////////////////////////////////////////////////////////////////
    callback: function(res,status) { 
        var para =
            {
                rcds:res,
                selRow:-1,
                form:$c.deepCopy($w.app.state.blank)
            }
         $w.app.setState(para)
    },    
    dataRead: function() {    
        $c.ajaxPostJson("dataget",{},this.callback)
    },
``` 
- 更新ボタン  
  フォームで修正した内容を上記テーブルに反映します。 
  本来はサーバーにデータを送り、戻ってきたデータを反映するのですが、今回は省略して、直接反映しています。  
  IDがnullの場合は、新規追加処理、入っている場合は、データ更新処理を行います。  

```js
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.updateClick} name="btnUpdate"
                style={{width:$w.W.b2,marginLeft:20}}>更新</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    updateClick:function(e){
        var para=$w.app.state
        if ($w.app.state.form.id==null){
            var len=$w.app.state.rcds.length
            para.rcds[len]=$c.deepCopy($w.app.state.form)
            para.rcds[len]["id"]=this.getId()
            para.selRow=-1
            para.form=$c.deepCopy($w.app.state.blank)
        } else {
            para.rcds[$w.app.state.selRow]=$c.deepCopy($w.app.state.form)
        }
        $w.app.setState(para)
```
- 新規ボタン  
  フォームをクリアします。 

```js
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.newClick} name="btnNew"
                style={{width:$w.W.b2,marginLeft:20}}>新規</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    newClick:function(e){
        var para={
            selRow:-1,
            form:$c.deepCopy($w.app.state.blank)
        }
        $w.app.setState(para)
    },
``` 
- 削除ボタン  
  フォームのデータをテーブルから削除します。  
本来はサーバーにデータを送り、戻ってきたデータを反映するのですが、今回は省略して、直接反映しています。  

```js
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.deleteClick} name="btnDelete"
                style={{width:$w.W.b2,marginLeft:20}}>削除</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    deleteClick:function(e){
        var para=$w.app.state
        para.rcds.splice(para.selRow,1)
        para.selRow=-1
        para.form=$c.deepCopy($w.app.state.blank)
        $w.app.setState(para)
    },
```  
- ダイアログ　ボタン  
  モーダルダイアログを表示します。  

```js
    <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.dialogTest} name="btnSearch2"
                style={{width:$w.W.b1,marginLeft:20}}>ダイアログ</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    dialogTest:function(e){
        para=$w.app.state.alert
        para["isShow"]=true
        para["message"]="これはダイアログの\nテストです。"
        $w.app.setState(para)
    }
```      
- ダイアログ　の了解 ボタン  
  モーダルダイアログをクローズします。　共通のjsxファイルにあります。  

```js
    <b.Button bsStyle="primary" onClick={this.onClick} 
                name="alert#CloseBtn">了解</b.Button>
    ///////////////////////////////////////////////////////////////////////////
    onClick:function(){
        para=$w.app.state.alert
        para["isShow"]=false
        para["message"]=""
        $w.app.setState(para)
    }
```  
## Application定義の最初の部分（例） 
ここで、stateの設定をしている。


```js
 var Application = React.createClass({
    getInitialState: function() {
        $w.app = this; //ここでGlobal変数に設定。なお$wは予め定義してある。
        return {
                    blank:{
                        action:false,
                        id:null,
                        mail:"",
                        name:"",
                        cat:"",
                    },
                    rcds:[],
                    form:{
                        action:false,
                        id:null,
                        mail:"",
                        name:"",
                        cat:"",
                    },
                    selRow:-1,
                    alert:{
                        isShow:false,
                        message:""
                    }
                  };
    },
```

## テキスト Input （例） 

```js
<b.Input type="text" name="form#name" 
                value={$w.app.state.form.name} onChange={$c.onChange} 
                style={{width:"120",marginLeft:20,color:"#000000"}}/>
```

## チェックボックス （例） 

```js
<b.Input type="checkbox" name="form#action" 
            checked={$w.app.state.form.action?"checked":""}　
            onChange={$c.onChecked} 
            style={{width:"20",height:"20",marginTop:-10
            ,marginLeft:0,color:"#000000"}}/>
```

## セレクトボックス （例） 

```js
<b.Input type="select" label='' name={"form#cat"} 
              onChange={$c.onChange}  
              style={{height:30, width:88,color:"#000000"}}>
                {$c.Option($w.app.state.form.cat,$w.catMap)}
            </b.Input>
```

## main.jsx 

```js
  $c.checkAndCreate("$w");
 $w.tableColW={c1:120,c2:200,c3:40,c4:40,c5:40}
 $w.W={b1:100,b2:50,l1:60}
 $w.catMap={
     "":"",
     "1":"CAT1",
     "2":"CAT2",
     "3":"CAT3",
 }
 var b = ReactBootstrap;
 var Item = React.createClass({ 
  render: function() {
    var c = this.props.rcd;
    return (
      <tr key={this.props.no*10} id={"row"+this.props.no} 
        onClick={$w.app.rowClick}  style={{backgroundColor: 
            (this.props.no==$w.app.state.selRow)?"#FFD0D0":"#FFFFFF"}} >
        <td key={this.props.no*10+1}
            style={{width:$w.tableColW.c1,border:1,borderStyle:"solid"}}>
                {c.name}</td>
        <td key={this.props.no*10+2}
            style={{width:$w.tableColW.c2,border:1,borderStyle:"solid"}}>
                {c.mail}</td>
        <td key={this.props.no*10+3}
            style={{width:$w.tableColW.c3,border:1,borderStyle:"solid"}}>
                {c.action?"Ｘ":""}</td>
        <td key={this.props.no*10+4}
            style={{width:$w.tableColW.c4,border:1,borderStyle:"solid"}}>
                {$w.catMap[c.cat]}</td>
        <td key={this.props.no*10+5}
            style={{width:$w.tableColW.c5,border:1,borderStyle:"solid"}}>
                {c.id}</td>
      </tr>
    );
  }
 });
  var Application = React.createClass({
    getInitialState: function() {
        $w.app = this;
        return {
                    blank:{
                        action:false,
                        id:null,
                        mail:"",
                        name:"",
                        cat:"",
                    },
                    rcds:[],
                    form:{
                        action:false,
                        id:null,
                        mail:"",
                        name:"",
                        cat:"",
                    },
                    selRow:-1,
                    alert:{
                        isShow:false,
                        message:""
                    }
                  };
    },
    render: function() {
        var rcds = $w.app.state.rcds;
        var object_list = [];
        for (var i = 0; i < rcds.length; i++) {
            object_list.push(
                <Item rcd={rcds[i]} no={i}/>
            );
        }
         return (  
            <div style={{width:480}}>
        <b.Row style={{margin:20}}>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.dataRead} name="btnSearch"
                style={{width:$w.W.b1,marginLeft:20}}>データ読込 </b.Button>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.dialogTest} name="btnSearch2"
                style={{width:$w.W.b1,marginLeft:20}}>ダイアログ</b.Button>
        </b.Row>
        <table cellPadding={10} cellSpacing={10} 
            style={{border:1,borderStyle:"solid",
             width:500,backgroundColor: "#F0F0F0"}}>
            <thead>
            <tr>
              <th width={$w.tableColW.c1} style={{border:1,borderStyle:"solid"}}>
                名前</th>
              <th width={$w.tableColW.c2} style={{border:1,borderStyle:"solid"}}>
                メール</th>
              <th width={$w.tableColW.c3} style={{border:1,borderStyle:"solid"}}>
                Action</th>
              <th width={$w.tableColW.c4} style={{border:1,borderStyle:"solid"}}>
                Cat</th>
              <th width={$w.tableColW.c5} style={{border:1,borderStyle:"solid"}}>
                id</th>
            </tr>
            </thead>
            <tbody>

            {object_list}
            </tbody>
        </table>        
        <b.Row style={{margin:20}}>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.updateClick} name="btnUpdate"
                style={{width:$w.W.b2,marginLeft:20}}>更新</b.Button>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.newClick} name="btnNew"
                style={{width:$w.W.b2,marginLeft:20}}>新規</b.Button>
            <b.Button bsSize="small" bsStyle="primary" 
                onClick={this.deleteClick} name="btnDelete"
                style={{width:$w.W.b2,marginLeft:20}}>削除</b.Button>
        </b.Row>  
        <b.Row style={{marginLeft:20}}>
            <span style={{width:$w.W.l1,marginLeft:20,float:"left",
                color:"#000000"}}>名前</span>
            <b.Input type="text" name="form#name" 
                value={$w.app.state.form.name} onChange={$c.onChange} 
                style={{width:"120",marginLeft:20,color:"#000000"}}/>
          </b.Row> 
        <b.Row style={{marginLeft:20}}>
            <span style={{width:$w.W.l1,marginLeft:20,float:"left",
                color:"#000000"}}>メール</span>
             <b.Input type="text" name="form#mail" 
                value={$w.app.state.form.mail} onChange={$c.onChange}  
                style={{width:"300",marginLeft:20,color:"#000000"}}/>
        </b.Row> 
        <b.Row style={{marginLeft:20,height:40}}>
           <span style={{width:$w.W.l1,marginLeft:20,float:"left",
            color:"#000000"}}>Action</span>
            <b.Input type="checkbox" name="form#action" 
            checked={$w.app.state.form.action?"checked":""}　
            onChange={$c.onChecked} 
            style={{width:"20",height:"20",marginTop:-10
            ,marginLeft:0,color:"#000000"}}/>
        </b.Row> 
          <b.Row style={{marginLeft:20}}>
            <span style={{width:$w.W.l1,marginLeft:20,float:"left",
                color:"#000000"}}>CAT</span>
            <b.Input type="select" label='' name={"form#cat"} 
              onChange={$c.onChange}  
              style={{height:30, width:88,color:"#000000"}}>
                {$c.Option($w.app.state.form.cat,$w.catMap)}
            </b.Input>
        </b.Row> 
        <b.Row style={{marginLeft:20}}>
            <span style={{width:$w.W.l1,float:"left",
                marginLeft:20,color:"#000000"}}>ID</span>
            <b.Input type="text" value={$w.app.state.form.id}
                disabled  style={{width:"60",color:"#000000"}}/>
        </b.Row> 
        <span style={{clear:"both"}}></span>
        <$c.Alert/>          
        </div>
        )
    } ,
    callback: function(res,status) { 
        var para =
            {
                rcds:res,
                selRow:-1,
                form:$c.deepCopy($w.app.state.blank)
            }
         $w.app.setState(para)
    },    
    dataRead: function() {    
        $c.ajaxPostJson("dataget",{},this.callback)
    },
    rowClick:function(e){
        row=e.target.parentNode.id.substr(3)
        var para={
            selRow:parseInt(row,10),
            form:$c.deepCopy($w.app.state.rcds[row])
        }
        $w.app.setState(para)
    },
    newClick:function(e){
        var para={
            selRow:-1,
            form:$c.deepCopy($w.app.state.blank)
        }
        $w.app.setState(para)
    },
    getId:function(){
        var maxid=-1
        for (i=0;i<$w.app.state.rcds.length;i++){
            if ($w.app.state.rcds[i]["id"] > maxid){
                maxid = $w.app.state.rcds[i]["id"]
            }
        }
        return maxid+1
    },
    deleteClick:function(e){
        var para=$w.app.state
        para.rcds.splice(para.selRow,1)
        para.selRow=-1
        para.form=$c.deepCopy($w.app.state.blank)
        $w.app.setState(para)
    },    
    updateClick:function(e){
        var para=$w.app.state
        if ($w.app.state.form.id==null){
            var len=$w.app.state.rcds.length
            para.rcds[len]=$c.deepCopy($w.app.state.form)
            para.rcds[len]["id"]=this.getId()
            para.selRow=-1
            para.form=$c.deepCopy($w.app.state.blank)
        } else {
            para.rcds[$w.app.state.selRow]=$c.deepCopy($w.app.state.form)
        }
        $w.app.setState(para)
    },
    dialogTest:function(e){
        para=$w.app.state.alert
        para["isShow"]=true
        para["message"]="これはダイアログの\nテストです。"
        $w.app.setState(para)
    }
  });
React.render(React.createElement(Application, null),
     document.getElementById('content'));
```

## commonjsx.jsx 

```js
var b = ReactBootstrap;
$c.MulitLine = React.createClass({
  render: function () {
    var sarray = this.props.value.split("\n");
    var lines = sarray.map(function(line,i){
        if (i===0){
          return <span key={i}>{line}</span>;
        } else {
          return <span  key={i}><br/>{line}</span>;
        }
      },this);
    return (
        <div>
        {lines}
        </div>
      );
  }
});
$c.Alert = React.createClass({
    mixins: [b.OverlayMixin],
    render: function () {
        return (
            <span/>
        );
    },
    renderOverlay: function () {
        if ($w.app.state.alert.isShow==false){
            return (
            <span/>
        );
    }
    return (
        <b.Modal onRequestHide={function(){}} className="alert" >
            <div className="modal-body">
            <$c.MulitLine value={$w.app.state.alert.message} />
                </div>
            <div className="modal-footer">
            <b.Button bsStyle="primary" onClick={this.onClick} 
                name="alert#CloseBtn">了解</b.Button>
            </div>
            </b.Modal>
        );
    },
    onClick:function(){
        para=$w.app.state.alert
        para["isShow"]=false
        para["message"]=""
        $w.app.setState(para)
    }
});  
$c.Option= function(value,map){
var options = []
for (key in map){
        options.push( 
        <option  
        value={key} label={map[key]} 
        selected={(key==value)?"selected":""}
        >{map[key]}</option> )
    }  
    return options  
}
```

## common.js 

```js
"use strict";
var checkAndCreate;
checkAndCreate = function (v) {
    if (window[v] == null) {
        return window[v] = {};
    }
};
checkAndCreate("$c");
$c.checkAndCreate = checkAndCreate;
$c.contextpath = "/";
$c.onChange = function (e) {
    var state = $w.app.state;
    var names = e.target.name.split("#")
    switch (names.length) {
        case 1: state[names[0]] = e.target.value
            break;
        case 2: state[names[0]][names[1]] = e.target.value
            break;
        case 3: state[names[0]][names[1]][names[2]] = e.target.value
            break;
        case 4: state[names[0]][names[1]][names[2]][names[3]]
             = e.target.value
            break;
        default:
            console.error("name length over")
    }
    $w.app.setState(state)

};
$c.onChecked = function (e) {
    var state = $w.app.state;
    var names = e.target.name.split("#")
    switch (names.length) {
        case 1: state[names[0]] = e.target.checked ? true : false
            break;
        case 2: state[names[0]][names[1]] = e.target.checked ?
             true : false
            break;
        case 3: state[names[0]][names[1]][names[2]] =
            e.target.checked ? true : false
            break;
        case 4: state[names[0]][names[1]][names[2]][names[3]] = 
            e.target.checked ? true : false
            break;
        default:
            console.error("name length over")
    }
    $w.app.setState(state)
};
$c.ajaxPost = function (url, data, contenttype, callback) {
    $.ajax({
        type: "POST",
        url: $c.contextpath + url,
        data: data,
        contentType: contenttype
    }).fail(function (jqXHR, textStatus) {
        console.log("Internet or Server Error")
        callback(jqXHR, textStatus)
    }).done(callback);
}
$c.ajaxPostJson = function (url, param, callback) {
    var data = JSON.stringify(param);
    $c.ajaxPost(url, data, "text/json", callback);
}
$c.deepCopy = function (obj) {
    return $.extend(true, {}, obj)
}

```