GitHub 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)
サンプルプログラム
なお当プログラムは、HerokuでDemoが見られます。http://simplereactjs.herokuapp.com/
また下記リンクからダウンロードして、解凍すればローカルで動かせます。
index.htmlやmain.jsxを編集して、自分で試す事が簡単に出来ます。
なお今回は、JSXTransforerをブラウザで動かすことで、jsxファイルの事前コンパイルを不要にしています。
このため最新のバージョンでなく、0.13.3を使用しています。(0.14以降はサポートされていません。)
サンプルプログラムの動かし方
- データ読み込みボタン
サーバーからデータを読み込んで、テーブルに表示します。下のフォームはクリアされます。
テーブル内をクリックすると、その行のデータが選択されて下のフォームに表示されます。
<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の場合は、新規追加処理、入っている場合は、データ更新処理を行います。
<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)
- 新規ボタン
フォームをクリアします。
<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)
},
- 削除ボタン
フォームのデータをテーブルから削除します。
本来はサーバーにデータを送り、戻ってきたデータを反映するのですが、今回は省略して、直接反映しています。
<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)
},
- ダイアログ ボタン
モーダルダイアログを表示します。
<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ファイルにあります。
<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の設定をしている。
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 (例)
<b.Input type="text" name="form#name"
value={$w.app.state.form.name} onChange={$c.onChange}
style={{width:"120",marginLeft:20,color:"#000000"}}/>
チェックボックス (例)
<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.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
$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?"X":""}</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
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
"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)
}