はじめに
Webページ開発初心者のサーバー・インフラ開発者が簡単なページ開発を行った際の記録その2です。その1はこちら。(要約: CSS設計大事)
今回はJavaScript部分を作ってみます。
環境構築
Webサーバーとして動作させたい: Node.jsの利用
まずはjsで実際にWebサーバーとして動作させたいので、Webサーバーの用意をしましょう。私は自宅に立てたルーターをHTTPサーバー(lighttpd)にします。
…なんて「Webサーバー」とくくられるとハードルが高くなっちゃいますが、Node.jsというサーバーサイド向けのJavaScript実行環境を利用すると、比較的簡単に簡易的なWebサーバーが作れます。こんな感じで
(今回私が作ったページではlighttpdのプラグインを利用する為、上記ではwebサーバーでは一部機能が動作しませんが、index.htmlとcgiくらいならそれなりに動作すると思います。)
npmでのJQueryのインストール
まずはHTTP通信を行うためのajaxを利用する為JQueryをインストールします。公式サイトを見ると、Node.jsのパッケージ管理ツールであるnpmからインストールが出来るとのことなので、npmからインストールします。
npm install jquery
また、npmインストールの参考を見たところ、browserify
というコマンドでJavaScriptのコードをまとめられるというので、こちらも使ってみます。
npm i -D browserify
すると、コマンド実行環境下にnode_modules
というディレクトリが出来、色々なファイルがその中に出来る状態になります。これで環境準備は完了。
他にも便利なnpmのパッケージがあればinstallして追加してください。
rubyのgem, pythonのpip等、スクリプト言語ではパッケージ管理ツールは基本なんですね。
browserifyを利用したjsファイルのまとめ
jsファイル(jQuery含む)は以下のようにbrowserifyコマンドでまとめることが出来ます。
$(npm bin)/browserify ./js/controller.js -o ./bind.js
browserify は指定されたjsファイルが参照(require)しているファイルを検索して、-oで指定されたbind.jsファイルにまとめてくれます。
なので、html側は全てのjsファイルを読み込む必要がなく、こんな感じでbind.jsだけを読み込めば動作出来るようになります。これは楽ちんですね!
<script type="text/javascript" src="bind.js"></script>
requireについて。例えばcontroller.jsは以下のようにview.js, http_service.jsを参照しています。
const CGIRequest = require('./http_service.js');
const View = require('./view.js');
さらに参照しているhttp_server.jsはjqueryを参照しています。これだけでバージョンも気にせず利用することが出来ます。
'use strict';
var $ = require('jquery');
module.exports = class CGIRequest {
...
このようにjsファイル内でrequireを指定しているファイルをまとめて1つのbind.jsファイルにまとめてくれるのがbrowserify。便利です。
HTMLとJavaScriptとの連携部分を作ってみる
作ってみた主な機能は以下です。
- ajaxでのHTTP通信(コマンドリストの取得)
- 1で取得したリストを利用して、クリックイベント付きリストを動的に作成する
- ajaxでの非同期通信(Transfer-encoding:chunked)
以下htmlのcommandlists
部分にcgiで取得したリストを動的に追加。
コマンドリストのクリックに対応してHTTP requestを実行(Transfer-encoding:chunked), 結果をresponse_data
部分に記載するような形にしました。
<div id="main">
<div id="content">
<div id="content_inner">
<!-- ページの中央部分 -->
<h2 id="current_command" disabled="true"></h2>
<div class="post">
<pre id="response_data">
</pre>
</div><!-- / .post -->
</div><!-- / #content_inner --><
</div><!-- / #content -->
<!-- ページの左部分 -->
<div id="leftside">
<table>
<div id="commandlists" class="commandlists">
</div>
</table>
<label for="uname">loop second:</label>
<input id="loopcount" type="number" name="num1" min="0" max="60" value="0" step="1" required><br>
<button id="loop_clear" type="button">stop</button>
</div><!-- / #leftside -->
</div><!-- / #main -->
</body>
</html>
機能実装の前に: html内容の書き換えはclassやidを利用
基本的な知識として。
htmlの中身を取得したりする場合はdocument.getElementById(id名)
やdocument.getElementsByClassName(class名)
等でhtmlの中身を参照する事が出来ます。
例えば上記の2を実施したいならdocument.getElementById('response_data')
でhtmlの中身を参照して中身を書き換えたり。
htmlとのやり取りはidやclassで行うのが基本のようです。
ということはJavaScriptのコード内でhtmlの構成と影響する部分が出てくるということになるので、MVCモデルのようにHTMLに依存する部分と他の部分を切り離すデザインが有効になるんですね。
ajaxでの通信
ajaxでのHTTP通信は$.ajax
で実現可能です。今回はhttp://IP/cgi-bin/コマンド名
を実行する処理としました。
onshot(cmd) {
//cgiのget request
return $.ajax({
type: 'GET',
url: '/cgi-bin/' + cmd,
timeout: 10000
})
}
$.ajax
の応答はPromiseというオブジェクトで、Promise.then(成功時に呼ばれる関数, 失敗時に呼ばれる関数)
を指定することで、HTTPの応答を拾うことが出来ます。こんな感じで。
//成功時に呼ばれる関数
function command_list_success(result) {
//commandをリスト化してクリックイベント付きリストを動的に作成する
view.update_commandlists(result.split('\n'), call_cmd)
}
//失敗時に呼ばれる関数
function command_list_failure(result) {
console.log(result)
}
//コマンド一覧の追加 + 応答用のコールバック設定
cgireq.onshot('command_list.py').then(command_list_success, command_list_failure);
resultはreponse bodyの内容がそのまま入ってきます。
こちらは改行込みのコマンド一覧なので、配列にsplitを利用して配列に変更し、クリックイベント付きリスト作成用関数update_commandlists
に渡しています。
クリックイベント付きリストを動的に作成する
リストの動的作成
$('クラス名').append(html);
(指定したクラスのhtml要素内にhtml文字列を追加する関数)を利用してhtml内に新しい要素を追加するようにしました。
getElementById
で取得した要素に対してinsertAdjacentHTML
を利用するのも試してみたのですが、CSSの設定がうまく反映されず。
CSSにcommandlists
クラス定義を追加し、appendを利用して要素を追加したらうまくいったのでこちらを採用。(やっぱりちょっと触っただけではCSSの挙動が掴みきれない(-_-;))
update_commandlists(cmd_list, cmd_callback) {
var html;
html = '<tr><th class="commandlists_th">cgi command</th></tr>'
//class="commandlists"部分にhtmlの要素をそのまま追加
$('.commandlists').append(html);
for(let i = 0; i < cmd_list.length; i++) {
html = '<tr><td class="commandlists_td" id="'+cmd_list[i]+'">' + cmd_list[i] + '</td></tr>'
$('.commandlists').append(html);
//イベント設定
...
}
}
クリックイベントの追加
各コマンドに追加するHTMLのタグにはCSS反映用のclass="commandlists_td"
とイベント用のid="'+cmd_list[i]
を指定。クリック追加したコマンド毎に別で発火出来るようにidを付けました。
クリックイベントはこんな感じでidと紐づけて$(document).on('click', '[id="XXX"]', 関数)
で追加しています。
update_commandlists(cmd_list, cmd_callback) {
...
//イベント設定
$(document).on('click', '[id="'+cmd_list[i]+'"]', function(){
console.log($(this).text())
var cmd_title = document.getElementById('current_command')
cmd_title.textContent = $(this).text()
cmd_title.disabled = false
//インターバル取得
var loopcount = Number(document.getElementById('loopcount').value)
cmd_callback($(this).text(), loopcount)
});
}
クリックイベントの実体はcallback形式にしてhtmlと切り離ししてます。
ajaxでの非同期通信(Transfer-encoding:chunked)
通常のajaxのPromiseオブジェクトを利用したやり方では、GETのレスポンスが終わってからしか結果が返ってこないためGetのレスポンスを抜き出すような処理が必要になります。
今回はHTTPのレスポンスが開始した際に呼ばれるonloadstart
を利用してsetIntervalでタイマーを設定。
定期的にajaxのresponseTextを抜き出してchunked_callbackを呼ぶようにしました。
また、キャンセル時にタイマーストップケアの為にHTTP処理終了時に呼ばれるonloadend
も定義。
ajax内で使用するthisはajaxのインスタンスになるようで、this.timer
の停止はこのような形にする必要がありました。
//loopcgiの実行
loopcgi(cmd, interval, chunked_callback) {
return $.ajax({
type: 'GET',
url: '/loopcgi',
//Queryの指定
data: {
'cgi': cmd,
'interval': interval,
},
xhrFields: {
//response開始
onloadstart: function() {
var xhr = this;
this.timer = setInterval(function() {
chunked_callback(xhr.responseText);
}, interval*1000);
},
//終了時
onloadend: function() {
clearInterval(this.timer);
}
},
success: function() {
// すぐにクリアしてしまうと最終的なレスポンスに対する処理ができないので
// タイマーと同じ間隔を空けてクリアする必要があるらしいです。
setTimeout('clearInterval(this.timer)', interval*1000);
},
error: function() {
console.log("Stop request " + cmd);
}
})
}
/loopcgiは自作lighttpdプラグイン向けのurlで、/loopcgi??cgi=cgiコマンド名&interval=秒数
で定周期でcgi実行⇒結果をTransfer-encoding:chunked
方式で順々に返すような仕様にしています。
サンプルコード
前回のhtml, cssも含めこちらに格納しています。
今回の感想~ 「機能を実現する」だけならなんとか。でもそれだけでは…
JavaScriptはpythonやrubyといったスクリプト言語を触ったことがある人なら、HTML/CSSと比較するとJavaScriptの方が理解がしやすい気がします。
ただツールやフレームワークによって書き方も変わりますし(今回もがっつりjQueryに依存しています)、HTMLと関連する部分と他のロジックをどう切り分けるか、業務レベルの規模で設計をするにはきちんと勉強する必要がありそうです。(やっぱりCSSの部分が掴み切れないし、ちょっとググって作るだけでは足りないすね(-_-;))
画面デザインはクライアントの要望でガンガン変わるのが想像できるので、前回のCSS設計も含め、画面側の設計は気を遣うことが多そうだな。
参考
環境構築:
もう、jQueryはnpmで管理しようぜ
ajax周りの参考全般:
はじめてajaxを使うときに知りたかったこと
ajax参考:
JavascriptのAjaxについての基本まとめ
Promiseを使う
クリックイベント追加の参考:
jQueryで動的に追加した要素はクリックイベントが発火しない?いやそんなことはないぞ
Encoding-chunkedの参考:
PHPとJavaScriptでHTTPストリーミングする話(Transfer-Encoding: chunked編)
jQueryの$.ajaxで通信途中のresponseTextを取得する
jQueryのajax()を中断する方法
Query指定:
jQuery.ajax()のまとめ