はじめに
Webアプリケーションをイチから作ってみたく、hubotと連携したアプリを作成したいと思います。
はじめての開発なので、完成までに考えたこと、調べたことをメモしたものになります。
作りたいものと作り方の方針
だいたい、こんな感じのものを作ろうと思います。

入力欄に内容を入力して、ボタンを押したら、別に用意したhubotのHTTPリスナーにリクエストを送り、返ってきた内容を表示する。という簡単な構成にします。
hubotについては、以下のページで用意したものを利用します。
Webとhubotの連携については単純に以下のような構成を想定。

開発環境の準備
index.htmlファイルを作成し、ブラウザでアクセスをすると、http://ではなくfile://となります。
ブラウザの仕様により、http://ではなく、file://でアクセスをすると、javascriptのセキュリティが厳しく、動作確認に支障が出てしまいます。
そのため、動作確認用にWebサーバを立て、さらに、ファイルを更新した際にブラウザも自動で表示を更新してくれるように、自動化ツールのgulpを導入します。
gulpの設定については以下のページで簡単に記載しています。
gulpfile.jsを準備したら、必要パッケージをインストールし、実行すればOK。
$ npm init -y
$ npm install -D gulp
$ npm install -D gulp-watch
$ npm install -D browser-sync
$ gulp
レイアウトの準備
まずはざっくりとファイル構成を考えます。
とはいっても、大した内容でもなく、各メニュー毎にファイルを分割しています。
.
│ index.html
│
├─css
│ index.css
│
└─js
│ index.js
│
└─components
app.js
footer.js
header.js
mainArea.js
sideArea.js
まずは、index.htmlを作成します。
CDNとして、React、ReactDOMをロードし、css/index.cssを読み込みます。
次に、Reactの画面表示のベースを作成します。
id=”root”に対して、いろいろとreact側で操作を行っていくことになります。
Reactの本体についてはjs/index.jsのほうで記述することにします。
また、JSXという、javascriptの拡張表現を利用するため、babelというプラグインのCDNも読み込んでおきます。
<!DOCTYPE html>
<head>
<title>hubot app</title>
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div id="root"></div>
<script type="text/babel" src="js/index.js"></script>
</body>
</html>
次に、index.jsにreactのベースを書いていきます。
components配下は後々に、index.jsの中身が大きくなってきたときに分割するためのもので、最初のうちはindex.jsの中に、全レイアウトの内容を書いていきます。
ReactDOM.render(
//wrapは表示画面全体部
<div className="wrap">
<div className="side-area"></div>
<div className="main-area">
<header className="header"></header>
<footer className="footer"></footer>
</div>
</div>,
document.getElementById('root')
)
ReactDOM.renderにReact DOMで管理する対象のID(root)を渡してあげます。

次に、index.cssでレイアウトを整えていきます。
各レイアウトはindex.jsの中でclassNameを定義しているので、それを対象とします。
html, body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#root {
width: 100%;
height: 100%;
}
.wrap {
width: 100%;
height: 100%;
/* side-areaとmain-areaを並列に並べます */
display: flex;
}
.side-area {
background: #795656;
width: 300px;
height: 100%;
}
.main-area {
background: #D3D3D3;
width: calc(100% - 300px);
height: 100%;
/* footerとheaderをmain-area内に閉じ込める */
position: relative;
}
.main-area .input-Area {
position: relative;
top: 30px;
left: 20px;
}
.main-area .response-Area {
position: relative;
top: 50px;
left: 20px;
}
.header {
background: #795656;
box-sizing: border-box;
width: 100%;
padding: 20px;
}
.footer {
background: #795656;
box-sizing: border-box;
/* main-areaの中での位置を指定。一番下 */
position: absolute;
width: 100%;
padding: 10px;
bottom: 0;
left: 0;
}
ここまででレイアウトは完成。
調べたこと1.CSSのpositionについて
デフォルトだと入力ボックスが以下の位置にあるとします。

やりたいこととしては、入力位置を少し右下(20pxずつ)に下げたいと思います。
対象の入力項目はclassnameをinput-Areaとしているので、CSSとしては、以下がベースとなります。
.main-area .input-Area {
top: 20px;
left: 20px;
}
ここで、上記の状態では位置を決定する際の基準点の指定がありません。
デフォルトだと、基準点がstaticとなり、topやleftを指定しても反映されません。
基準点を指定するには基準点が相対(relative)か、絶対(absolute)かを指定する必要があります。

それぞれの基準点のイメージは上記。
ここでは基準点をrelativeとします。
.main-area .input-Area {
position: relative;
top: 20px;
left: 20px;
}
すると、以下のような位置となります。

調べたこと2.イベントハンドラについて
送信ボタンもしくは、testボタンを押すと、onclickButtonが呼び出される。
このとき、eventにはイベントオブジェクトが渡される。
イベントオブジェクトはどのようなイベントが起きたかの情報が含まれている。
testを押して呼び出されると、testボタンが押されたというイベント情報が含まれる。
onclickButton(event){
console.log(event)
this.setState({InputValue:""});
this.setState({HubotResponse:"hogege"});
}
render() {
return (
<div className="wrap">
<div className="side-area"></div>
<div className="main-area">
<header className="header">
hubot テストサイト
</header>
<div className="input-Area">
<input type="text"
className="chat-input"
value={this.state.InputValue}
onChange={this.onChangeInput.bind(this)} />
<button className="submit-button"
onClick={this.onclickButton.bind(this)}>送信</button>
<button className="test-button"
onClick={this.onclickButton.bind(this)}>test</button>
</div>
<div className="response-Area">
{this.state.HubotResponse}
</div>
<footer className="footer">
test
</footer>
</div>
</div>
)
}
調べたこと3.bindの引数について
reactでボタンを押したときに関数を呼び出したい場合は以下のような内容になる。
このとき、関数に渡す引数はbindの中で指定する。
その際に、関数側で引き取る第一引数はthis以降となる。
また、特に指定しなくても、最後にイベントオブジェクトが引っ付く形となる。
<button className="test-button"
onClick={this.onclickButton.bind(this,1,2)}>test</button>
consoleには、1,2,イベントの順で表示がされる。
onclickButton(haha,hoge,huga){
console.log(haha)
console.log(hoge)
console.log(huga)
}
hubotとHTTP通信でやりとりをする
より簡単にHTTP通信を行えるように、axiosというライブラリを利用することにします。
本来はXMLHTTPRequestで操作するのが原始的なやり方らしいですが、axiosを使うことで、より簡単にHTTP通信のやりとりができるとのことです。
まずは、index.htmlのほうでCDNでライブラリを読み込みます。
<head>
<title>hubot app</title>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<link rel="stylesheet" href="css/index.css">
</head>
まずは、Hubotにリクエストを投げ、レスポンスが返るかを確認します。
onclickButton(event,hoge,huga){
axios.post('http://localhost:8080/hubot/test',{
req: this.state.InputValue
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.finally(function () {
});
}
ここで、CORSのエラーが出力され、ブラウザ側のセキュリティでブロックされてしまいました。
そのため、hubot側のレスポンスにCORS対策でヘッダを含めるようにしました。
module.exports = (robot) ->
robot.router.get '/hubot/test', (req, res) ->
chat = "hogehoge"
console.info req.body
res.header('Content-Type','application/json;charset=utf-8')
res.header('Access-Control-Allow-Origin','*')
res.header('Access-Control-Allow-Methods','OPTIONS,POST,GET')
res.send "
<html>
<body>test</body>
</html>
"
robot.router.post '/hubot/test', (req, res) ->
chat = "hogehoge"
console.info req.body
res.header('Content-Type','application/json;charset=utf-8')
res.header('Access-Control-Allow-Origin','*')
res.header('Access-Control-Allow-Methods','OPTIONS,POST,GET')
res.send "
<html>
<body>test</body>
</html>
"
相変わらず、解決しませんでした。
Chromeの開発者ツールで確認をしたところ、何故かOPTIONSメソッドが利用されていました。
OPSIONSなんて使わずに、直接GET、POSTを使ってほしかったので、以下に改修。
onclickButton(event){
const params = new URLSearchParams();
params.append('chat', this.state.InputValue);
axios.post('http://localhost:8080/hubot/test',params)
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
}
無事、応答が返ってくるところまで確認ができました。

参考にさせていただきました。


調べたこと4.関数内の関数でthisを使う方法
React.Componentのインスタンスのthis.stateにアクセスするためには、そのインスタンスの関数内でしか利用できず、関数内の関数で利用しようとするとthisが未定義となる。
関数内の関数で、さらにインスタンスのstateにアクセスする場合、いったん、関数内の変数にthisを落とし込んで、関数内の関数でも利用できるようにスコープを調整する必要がある。
class Index extends React.Component {
constructor(props) {
super(props);
this.state = {
InputValue: "",
HubotResponse:"Hubot Response"
}
}
onclickButton(event){
const params = new URLSearchParams();
params.append('chat', this.state.InputValue);
var self = this;
axios.post('http://localhost:8080/hubot/test',params)
.then(function (response) {
self.setState({
HubotResponse:response.data
})
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
}
結局は、同じ内容で悩んでいる人がいた。

コメント