はじめに
RailsとReactJSを用いてちょっとした掲示板を作ってみましょうというのが今回のコンセプト。
前回のVol.1 は掲示板一覧を表示することだけで終わってしまいましたが、今回は本格的な
掲示板機能を実装してみます。多分長くなると思いますので、よろしくお付き合いください。
前回に引き続き、これから何回かにわたり連載する内容はGitHubの下記のURLで公開しています。
https://github.com/h-mito/rails_with_react
ステートの管理やら、イベントの引き渡し方がいまいちわからないという方は、
Railsとは関係ない ToDoListの記事 を参照してみてください。
完成イメージ
こんな感じのものができればいいなという理想型です。
これをパーツ(ReactJSのクラス)に分解してみるとこんな感じになると思います。
いざ開始
Reactコンポーネントファイルの作成
app/assets/javascripts/components/board_and_commen.js.jsx
ファイルを作成します。
rails g でやってもいいのですが、どうせ全部消しちゃうので手で作りましょう。
では、ファイルには以下のような感じで最上層部のクラスとなる
BoardAndCommentクラスを定義しましょう。
[bash]
class BoardAndComment extends React.Component{
constructor(props){
super(props);
}
render(){
return (
空っぽ。
);
}
}
[/bash]
Viewファイルの作成と、コントローラー・routes調整
app/views/boards/bandc.html.erb
にViewファイルを作ります。
中身は
[bash]
<%= react_component('BoardAndComment') %>
[/bash]
コントローラーにメソッドを追加します。
app/controllers/boards_controller.rb
[bash]
class BoardsController < ApplicationController
def index
@boards = Board.all
end
def bandc
@boards = Board.all
end
end
[/bash]
routes.rbに1行ルートを追記します。
config/routes.rb
[bash]
get 'boards/bandc'
[/bash]
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
こんな感じのものが表示されれば成功です。
2階層目となる3つのクラスを定義して、トップレベル(1階層目)のrenderメソッドで
2階層目をレンダーする
ここでは2階層目となる3つのクラスを定義します。
3つのクラスは先ほどと同じように単純に固定文字を出力するだけです。
[bash]
class BoardAndComment extends React.Component{
constructor(props){
super(props);
}
render(){
return (
);
}
}
class BoardList2 extends React.Component{
constructor(props){
super(props);
}
render(){
return (
掲示板一覧を表示する場所です。
);
}
}
class CommentAdd extends React.Component{
constructor(props){
super(props);
}
render(){
return (
掲示板に投稿を追加する機能を表示する場所です。
);
}
}
class CommentList extends React.Component{
constructor(props){
super(props);
}
render(){
return (
掲示板内の投稿一覧を表示する場所です。
);
}
}
[/bash]
CSSの調整
app/assets/stylesheets/boards.scss
に以下を追記。
[bash]
div.left-side{
float: left;
width: 400px;
}
div.right-side{
float: left;
width: 600px;
}
[/bash]
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
こんな感じのものが表示されれば成功です。
ステートで管理するものを考える
では掲示板に投稿する機能全体でステートとして管理しなきゃならないものは何でしょうか?
変化しそうなものに掲示板一覧・投稿文字を入力するエリアに入力された文字、投稿ユーザー、投稿一覧
の4つほどが考えられます。
今回の最終目標は選択した掲示板に投稿が追加できる というものなので、
掲示板一覧は 最初に読み込んだ値から不変ですので、ステートとして管理する必要はなし。
投稿文字に関しては、毎度毎度違う文字が入力されるので管理する必要あり。
投稿ユーザーは普通の掲示板であればログインした人のIDを用いるので、変化しませんが・・
ログイン処理を設けない、今回の掲示板では投稿の際にユーザーIDをSelectボックスで選択することにします。
つまり変化するので管理する必要ありです。
投稿対象の掲示板も変化するので管理する必要ありです。
投稿一覧も管理する必要があります。
ちょっと難しいかもですね。これ、慣れですので、だんだんわかってくると思います。
つまりこれだけの機能はあるけど、ReactJSで管理するステートは
投稿する文字 と 投稿するユーザー と投稿対象の掲示板 と投稿一覧の4つだけ ということになります。
で、どこで管理するかというとトップレベルのクラスであるBoardAndCommentクラスです。
board_title と board_descriptionについては、それほど重要なステートではないので、
今のところは無視しておいてください。
BoardAndCommentクラスにこのステート管理部分を追記してみましょう。
コンストラクター(constructor)で値を初期化して、投稿文字が変化した時にステートを
変化させるメソッド(handleCommentChange)を定義して、
レンダーメソッドで、CommentAddクラスをレンダーする際に
CommentAddクラス内で起こるイベントを自分のクラスで処理できるように記述を追加します。
投稿ユーザーが変化した時にステートを
変化させるメソッド(handleUserChange)を定義して、
レンダーメソッドで、CommentAddクラスをレンダーする際に
CommentAddクラス内で起こるイベントを自分のクラスで処理できるように記述を追加します。
残りの2つもおいおい説明します。
[bash]
class BoardAndComment extends React.Component{
constructor(props){
super(props);
this.state = {comment: ”, user_id: 1, board_id: -1, comments: [], board_title: ”, board_description: ”};
}
handleCommentChange(cm){
this.setState({comment: cm});
}
handleUserChange(id){
alert(id);
}
handleBoardSelect(id){
alert(id);
}
render(){
return (
this.handleBoardSelect(id)}
/>
this.handleUserChange(uid)}
onCommentChange={(cm) => this.handleCommentChange(cm)} />
);
}
}
[/bash]
掲示板一覧を実装する
これは前回やった内容とほぼ同じですので、ソースだけ提示して、
変化しているごく僅かな部分だけ説明します。
Viewの調整
[bash]
<%= react_component('BoardAndComment', {datas: @boards}) %>
[/bash]
まずはトップレベルのBoardAndCommentクラスの変化箇所。
renderメソッド内で BoardList2をレンダーしている部分で
datasアトリビュートに this.props.datas を渡しています。
[bash]
class BoardAndComment extends React.Component{
constructor(props){
super(props);
this.state = {comment: ”};
}
handleCommentChange(cm){
this.setState({comment: cm});
}
render(){
return (
this.handleCommentChange(cm)} />
);
}
}
[/bash]
次に2階層目のBoardList2クラス。renderメソッドで BoardList2クラスをレンダーする際に
BoardList2クラス内で起こるであろう掲示板選択のイベント(onBoardSelect)を自分の
クラスのメソッド内で処理させている。
もうひとつは、その処理も自分ではハンドリングしようがないので、もう1階層上のクラスに
任せている(handleBoardSelect)
[bash]
class BoardList2 extends React.Component{
constructor(props){
super(props);
}
handleBoardSelect(id){
this.props.onBoardSelect(id);
}
render(){
var lists = [];
var i;
for (i = 0; i < this.props.datas.length; i ++){
var data = this.props.datas[i];
lists.push( this.handleBoardSelect(id)}
/>);
}
return (
掲示板一覧を表示する場所です。
{lists}
);
}
}
[/bash]
最後に3階層目のクラスであるBoardLine2クラス。
行の右端に選択ボタンがあり、そのクリック処理を
onBoardSelectイベントとして1回層上のクラスに任せている。
[bash]
class BoardLine2 extends React.Component{
constructor(props){
super(props);
}
handleOnClick(){
this.props.onBoardSelect(this.props.data.id);
}
render(){
return (
{this.props.data.title}
this.handleOnClick()} >
選択
);
}
}
[/bash]
最後の最後に見栄え調整のためのCSS
[bash]
div.blist2{
.board-title{
display: inline-block;
width: 320px;
}
}
[/bash]
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
こんな感じのものが表示されれば成功です
イベントの上位階層への連携
BoardLine2クラスの「選択ボタン」のクリック処理は1つ上の階層までは伝えましたが、
1つ上の階層がBoardList2クラスなので、もう1階層上まで伝えてあげなきゃなりません。
で、どうするかというと、やり方はほぼ同じで
BoardList2クラスをレンダーする際にイベント処理(onBoardSelect)を定義してあげて、
トップレベルクラスのメソッドで処理できるようにします。
[bash]
class BoardAndComment extends React.Component{
constructor(props){
super(props);
this.state = {comment: ”, user_id: 1, board_id: -1, comments: [], board_title: ”, board_description: ”};
}
handleCommentChange(cm){
this.setState({comment: cm});
}
handleBoardSelect(id){
alert(id);
}
render(){
return (
this.handleBoardSelect(id)}
/>
this.handleCommentChange(cm)} />
);
}
}
[/bash]
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
一覧が表示されて、選択ボタンをクリックすると、こんな感じのものが表示されれば成功です
投稿を追加する部分を実装する
画面左側の掲示板一覧を表示する部分はほぼ実装できたので、今度は投稿を追加する部分
つまり画面の右上の部分について説明していきます。
投稿する対象となる掲示板は画面左の一覧で「選択ボタン」をクリックしたものが対象になりますので、
右上の機能としては考える必要はありません。
右上の機能つまり CommentAddクラスの機能として必要なのは、
投稿の文字が変化したことをキャッチすること、投稿ユーザーが変化したことをキャッチすること。
投稿ボタンがクリックされたことをキャッチすること。
そしてそれらを上位クラスへ伝えてあげることです。
CommentAddクラス
では CommentAddクラスを実装していきましょう。
[bash]
class CommentAdd extends React.Component{
constructor(props){
super(props);
}
handleOnUserChange(e){
this.props.onUserChange(e.target.value);
}
handleOnCommentChange(e){
this.props.onCommentChange(e.target.value);
}
handleCommentAdd(){
this.props.onCommentAdd();
}
render(){
var users = [];
var i;
for (i = 0 ; i < this.props.users.length ; i++){
var user = this.props.users[i];
users.push({user.name} );
}
return (
掲示板に投稿を追加する機能を表示する場所です。
選択された掲示板
{this.props.board_title}
掲示板詳細
{this.props.board_description}
投稿ユーザー
this.handleOnUserChange(e)}>
{users}
投稿内容
);
}
}
[/bash]
コントローラーの調整
投稿ユーザーの一覧が必要になりますので、以下のようにコントローラーを調整します。
app/controllers/boards_controller.rb
[bash]
class BoardsController < ApplicationController
def index
@boards = Board.all
end
def bandc
@boards = Board.all
@users = User.all
end
end
[/bash]
Viewの調整
コントローラーで読み込んだ @usersを コンポーネントの初期値として渡します。
app/views/boards/bandc.html.erb
[bash]
<%= react_component('BoardAndComment', {datas: @boards, users: @users}) %>
[/bash]
BoardAndCommentクラスの調整
renderメソッドで CommentAddクラスをレンダーする際に@usersの値を渡してあげます。
[bash]
class BoardAndComment extends React.Component{
constructor(props){
super(props);
this.state = {comment: ”, user_id: 1, board_id: -1, comments: [], board_title: ”, board_description: ”};
}
handleCommentChange(cm){
this.setState({comment: cm});
}
handleUserChange(id){
alert(id);
}
handleBoardSelect(id){
alert(id);
}
handleCommentAdd(){
alert(“comment post”);
}
render(){
return (
this.handleBoardSelect(id)}
/>
this.handleUserChange(uid)}
onCommentChange={(cm) => this.handleCommentChange(cm)}
onCommentAdd={() => this.handleCommentAdd()}
/>
);
}
}
[/bash]
CSSの調整
見栄えを調整します。
app/assets/stylesheets/boards.scss
[bash]
div.cadd{
.span1{
display: inline-block;
width: 100px;
}
textarea{
width: 450px;
float: left;
}
button {
width: 80px;
height: 60px;
}
}
[/bash]
コメントを投稿する機能を考える
ここまでのところをまず動作確認してみましょう。
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
投稿ボタンをクリックするとメッセージボックスが表示されますね。
クリックするとメッセージボックスがトップレベルのクラスから表示されているということは・・・
あとは投稿ユーザーと投稿内容、対象となる掲示板が分かれば、データを追加することが出来ますね。
トップレベルクラス(BoardAndCommentクラス)のこれまでのソースを覗いてみます。
[bash]
class BoardAndComment extends React.Component{
constructor(props){
super(props);
this.state = {comment: ”, user_id: 1, board_id: -1, comments: [], board_title: ”, board_description: ”};
}
handleCommentChange(cm){
this.setState({comment: cm});
}
handleUserChange(id){
alert(id);
}
handleBoardSelect(id){
alert(id);
}
handleCommentAdd(){
alert(“comment post”);
}
render(){
以下略
[/bash]
投稿ユーザーは handleUserChangeでIDが渡ってきているので、このメソッド内でステートを変化させればいい。
投稿文字列はすでに変化の内容をセット済み。
対象となる掲示板もhandleBoardSelectでIDが渡ってきているので、このメソッド内でステートを変化させればいい。
つまり投稿ボタンがクリックされた処理はトップレベルクラスの handleCommentAddメソッドで感知できますし、
投稿すべき内容は全てステートを見ればわかるような状態になっているのです。。。素晴らしい。
というかそうなるように組んできたので当たり前なのですが・・・
では、上記の部分 正しく書きなおしてみましょう。
[bash]
class BoardAndComment extends React.Component{
constructor(props){
super(props);
this.state = {comment: ”, user_id: 1, board_id: -1, board_title: ”, board_description: ”};
}
handleCommentChange(cm){
this.setState({comment: cm});
}
handleUserChange(id){
this.setState({user_id: id});
}
handleBoardSelect(id){
this.setState({board_id: id});
}
handleCommentAdd(){
//alert(“comment post”);
ステートの値を使ってAjaxでデータをPOSTすればOK。
}
render(){
以下略
[/bash]
ステートの値を使ってAjaxでデータをPOST
BoardAndCommentクラスのhandleCommentAddメソッドを調整します。
[bash]
handleCommentAdd(){
var svThis = this;
if (this.state.board_id == -1){
alert(“書き込み対象の掲示板を選択してください”);
return;
}
var data = {
board_id: this.state.board_id,
user_id: this.state.user_id,
comment: this.state.comment,
};
$.ajax({
type: “POST”,
url: “/boards/insertComment”,
async: false,
dataType: “json”,
data: data,
success: function(data, dataType){
//alert(data.status + “–” + data.row.company_name)
if (data.status == true){
alert(“投稿成功”);
}
},
error :function(XMLHttpRequest, textStatus, errorThrown){
}
});
}
[/bash]
routes.rbを調整
config/routes.rb
末尾に追記
[bash]
post ‘boards/insertComment’
[/bash]
コントローラーにデータ追加処理実装
app/controllers/boards_controller.rb
[bash]
def insertComment
response = {status:true}
c = Comment.new({board_id: params[:board_id], user_id: params[:user_id], comment: params[:comment]})
c.save()
render :json=> response
end
[/bash]
コメントを投稿してみる
ここまでのところをまず動作確認してみましょう。
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
まず左側で掲示板を選択し、投稿文字を入力し、
投稿ボタンをクリックすると投稿成功メッセージボックスが表示されますね。
掲示板が選択されたら、コメントの一覧を表示する
いよいよ最後です。
右下の投稿一覧の部分を作成していきます。
投稿一覧は画面左の掲示板一覧で選択ボタンがクリックされたタイミングで描画される。
または、投稿が終了した時点で描画される。の2箇所になります。
結局 掲示板一覧で選択ボタンがクリックされた時と同じ動作を、投稿処理の後に挟めば良い。
つまり、「選択ボタンがクリック」を実装してあげれば、投稿終了時にはその処理を呼べば良い
ということになります。
では、「選択ボタンがクリック」はどうやって実装するか・・・
これは単純にBoardAndCommentクラスのhandleOnBoardSelectメソッドで、Ajaxを使ってデータを取ってきて、
投稿一覧を保持するステートを変えれば良いということになります。
BoardAndCommentクラスの調整
[bash]
handleBoardSelect(id){
this.setState({board_id: id});
var svThis = this;
$.ajax({
type: “GET”,
url: “/boards/readComments/” + id,
async: false,
dataType: “json”,
data: “name=John&location=Boston”,
success: function(data, dataType){
if (data.status == true){
alert(data.rows.length);
svThis.setState({comments: data.rows});
}
},
error :function(XMLHttpRequest, textStatus, errorThrown){
}
});
}
[/bash]
routes.rbの調整
config/routes.rb
末尾に追記します。
[bash]
get ‘boards/readComments/:board_id’ => ‘boards#readComments’
[/bash]
コントローラーの調整
app/controllers/boards_controller.rb
[bash]
def readComments
response = {status:true}
recs = Comment.eager_load(:user).where(“board_id = ?”, params[:board_id])
.order(“comments.created_at DESC”).as_json(include: :user)
response[“rows”] = recs
render :json=> response
end
[/bash]
モデルの調整
app/models/comment.rb
[bash]
class Comment < ApplicationRecord
belongs_to :user
end
[/bash]
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
まず左側で掲示板を選択し、投稿文字を入力し、投稿ボタンをクリックする。
これを何度か繰り返して、テストデータを作ってください。
掲示板を選択した時、最初はメッセージボックスに「0」が表示されると思いますが、
一旦投稿すると、次に同じ掲示板を選んだ場合、投稿数が増えていることが解ると思います。
投稿一覧を表示してみましょう
いよいよデータが取れたので、投稿一覧を表示してみましょう。
投稿一覧(CommentListクラス)のレンダー時に this.state.comments をアトリビュートとして渡していますので、
Ajaxでデータを取得して
[bash]
this.setState({comments: data.rows});
[/bash]
とした時点で自動的にデータの変化は伝わります。
なので、渡ってきたデータを純粋に表示する部分のみを実装すればOKです。
CommentListクラスの調整
[bash]
class CommentList extends React.Component{
constructor(props){
super(props);
}
render(){
var lists = [];
var i;
for (i = 0 ; i < this.props.datas.length; i++){
var data = this.props.datas[i];
lists.push( );
}
return (
掲示板内の投稿一覧を表示する場所です。
{lists}
);
}
}
[/bash]
CommentLineクラスの調整
[bash]
class CommentLine extends React.Component{
constructor(props){
super(props);
}
render(){
return (
{this.props.data.user.name}
{this.props.data.user.sex == 1 ? ‘男性’ : ‘女性’}
{this.props.data.user.age}歳
{this.props.data.comment}
);
}
}
[/bash]
BoardAndCommentクラスの調整
alert(data.rows.length); をコメントアウトします。
[bash]
handleBoardSelect(id){
this.setState({board_id: id});
var svThis = this;
$.ajax({
type: “GET”,
url: “/boards/readComments/” + id,
async: false,
dataType: “json”,
data: “name=John&location=Boston”,
success: function(data, dataType){
if (data.status == true){
//alert(data.rows.length);
svThis.setState({comments: data.rows});
}
},
error :function(XMLHttpRequest, textStatus, errorThrown){
}
});
}
[/bash]
CSSの調整
[bash]
div.clist{
clear: both;
}
div.cline{
border-top: 1px solid #cccccc;
border-left: 1px solid #cccccc;
border-right: 1px solid #cccccc;
label {
display: inline-block;
}
label.name{
width: 100px;
}
label.sex {
width: 40px;
font-size: 9px;
}
label.age{
width: 50px;
font-size: 9px;
}
.comment{
padding: 10px;
}
}
[/bash]
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
左側で掲示板を選択したら、右下のコメント一覧が表示されることを確認してください。
投稿後に投稿一覧を更新する
これは単純にJavaScriptでメソッドを呼んでやるだけです。
BoardAndCommentクラスの調整
[bash]
handleCommentAdd(){
var svThis = this;
if (this.state.board_id == -1){
alert(“書き込み対象の掲示板を選択してください”);
return;
}
var data = {
board_id: this.state.board_id,
user_id: this.state.user_id,
comment: this.state.comment,
};
$.ajax({
type: “POST”,
url: “/boards/insertComment”,
async: false,
dataType: “json”,
data: data,
success: function(data, dataType){
//alert(data.status + “–” + data.row.company_name)
if (data.status == true){
//alert(“投稿成功”);
svThis.handleBoardSelect(svThis.state.board_id);
}
},
error :function(XMLHttpRequest, textStatus, errorThrown){
}
});
}
[/bash]
おまけというか補助機能
ここまでの部分でほぼ出来上がりなんですが・・
画面左側で「選択ボタンをクリック」したはいいけど、今どの掲示板に書き込もうとしているのか
わかりにくい。この部分を改善してみましょう。
「選択ボタンをクリック」した時には掲示板のidのみがわかる状態なので、
このidの値を使ってAjaxで掲示板の情報を拾いに行って、戻ってきた値で
ステートを更新することで、掲示板のタイトル(title)と(description)を
レンダリングしてあげましょう。
BoardAndCommentクラスの調整
ajaxを1つ増やします。
[bash]
handleBoardSelect(id){
this.setState({board_id: id});
var svThis = this;
$.ajax({
type: “GET”,
url: “/boards/readComments/” + id,
async: false,
dataType: “json”,
data: “name=John&location=Boston”,
success: function(data, dataType){
if (data.status == true){
//alert(data.rows.length);
svThis.setState({comments: data.rows});
}
},
error :function(XMLHttpRequest, textStatus, errorThrown){
}
});
$.ajax({
type: “GET”,
url: “/boards/readBoard/” + id,
async: false,
dataType: “json”,
data: “name=John&location=Boston”,
success: function(data, dataType){
if (data.status == true){
svThis.setState({board_title: data.row.title});
svThis.setState({board_description: data.row.description});
}
},
error :function(XMLHttpRequest, textStatus, errorThrown){
}
});
}
[/bash]
CommentAddクラスのレンダー時に読み込んだ値を渡してやります。
[bash]
render(){
return (
this.handleBoardSelect(id)}
/>
this.handleUserChange(uid)}
onCommentChange={(cm) => this.handleCommentChange(cm)}
onCommentAdd={() => this.handleCommentAdd()}
/>
);
}
[/bash]
コントローラーの調整
app/controllers/boards_controller.rb
[bash]
def readBoard
response = {status:true}
recs = Board.where(“id = ?”, params[:board_id])
response[“row”] = recs[0]
render :json=> response
end
[/bash]
routes.rbの調整
config/routes.rb
末尾に追記
[bash]
get ‘boards/readBoard/:board_id’ => ‘boards#readBoard’
[/bash]
動作確認
[bash]
$ rails s
[/bash]
ブラウザーで
http://localhost:3000/boards/bandc
左側で掲示板を選択したら、右上の掲示板名と掲示板詳細が変化することを確認してください。
以上となります。
どうでしたか?ReactJS面白いでしょ???
是非、自分なりにいろいろ試してみてください。
繰り返しになりますが、連載した内容はGitHubの下記のURLで公開しています。
https://github.com/h-mito/rails_with_react
Leave a comment