（強いて言うなら）サーバー・インフラ開発者の、初めてのWebページ開発　その2. HTMLとJavaScriptとの連携

## **はじめに**

Webページ開発初心者のサーバー・インフラ開発者が簡単なページ開発を行った際の記録その2です。その1は[こちら](https://qiita.com/developer-kikikaikai/items/5f935bed3b090f58506d)。(要約: CSS設計大事)
今回はJavaScript部分を作ってみます。

## **環境構築**

### **Webサーバーとして動作させたい: Node.jsの利用**

まずはjsで実際にWebサーバーとして動作させたいので、Webサーバーの用意をしましょう。私は自宅に立てたルーターをHTTPサーバー(lighttpd)にします。

…なんて「Webサーバー」とくくられるとハードルが高くなっちゃいますが、Node.jsというサーバーサイド向けのJavaScript実行環境を利用すると、比較的簡単に簡易的なWebサーバーが作れます。[こんな感じで](https://qiita.com/developer-kikikaikai/items/56e006e58cecebde8ee1)

(今回私が作ったページではlighttpdのプラグインを利用する為、上記ではwebサーバーでは一部機能が動作しませんが、index.htmlとcgiくらいならそれなりに動作すると思います。)

### **npmでのJQueryのインストール**

まずはHTTP通信を行うためのajaxを利用する為JQueryをインストールします。[公式サイト](http://jquery.com/download/)を見ると、Node.jsのパッケージ管理ツールであるnpmからインストールが出来るとのことなので、npmからインストールします。

```
npm install jquery
```

また、[npmインストールの参考](https://qiita.com/ritukiii/items/a28033b30d7681492c98)を見たところ、`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だけを読み込めば動作出来るようになります。これは楽ちんですね！

```html:index.html
<script type="text/javascript" src="bind.js"></script>
```

requireについて。例えばcontroller.jsは以下のようにview.js, http_service.jsを参照しています。

```javascript:controller.js
const CGIRequest = require('./http_service.js');
const View = require('./view.js');
```

さらに参照しているhttp_server.jsはjqueryを参照しています。これだけでバージョンも気にせず利用することが出来ます。

```javascript:http_service.js
'use strict';
var $ = require('jquery');

module.exports = class CGIRequest {
...
```

このようにjsファイル内でrequireを指定しているファイルをまとめて1つのbind.jsファイルにまとめてくれるのがbrowserify。便利です。

## **HTMLとJavaScriptとの連携部分を作ってみる**

作ってみた主な機能は以下です。

1. ajaxでのHTTP通信(コマンドリストの取得)
2. 1で取得したリストを利用して、クリックイベント付きリストを動的に作成する
3. ajaxでの非同期通信(Transfer-encoding:chunked)

以下htmlの`commandlists`部分にcgiで取得したリストを動的に追加。
コマンドリストのクリックに対応してHTTP requestを実行(Transfer-encoding:chunked), 結果を`response_data`部分に記載するような形にしました。

```html:index.html
  <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/コマンド名`を実行する処理としました。

```javascript:http_server.js
        onshot(cmd) {
                //cgiのget request
                return $.ajax({
                        type: 'GET',
                        url: '/cgi-bin/' + cmd,
                        timeout: 10000
                })
        }
```

`$.ajax`の応答はPromiseというオブジェクトで、`Promise.then(成功時に呼ばれる関数, 失敗時に呼ばれる関数)`を指定することで、HTTPの応答を拾うことが出来ます。こんな感じで。

```javascript:controller.js
//成功時に呼ばれる関数
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の挙動が掴みきれない(-_-;))

```javascript:view.js
        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"]', 関数)`で追加しています。

```javascript:view.js
        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`の停止はこのような形にする必要がありました。

```javascript:http_service.js
        //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も含め[こちら](https://github.com/developer-kikikaikai/lighttpd_setting/tree/master/html)に格納しています。

## **今回の感想～ 「機能を実現する」だけならなんとか。でもそれだけでは…**

JavaScriptはpythonやrubyといったスクリプト言語を触ったことがある人なら、HTML/CSSと比較するとJavaScriptの方が理解がしやすい気がします。

ただツールやフレームワークによって書き方も変わりますし(今回もがっつりjQueryに依存しています)、**HTMLと関連する部分と他のロジックをどう切り分けるか、業務レベルの規模で設計をするにはきちんと勉強する必要がありそう**です。(やっぱりCSSの部分が掴み切れないし、ちょっとググって作るだけでは足りないすね(-_-;))

画面デザインはクライアントの要望でガンガン変わるのが想像できるので、前回のCSS設計も含め、画面側の設計は気を遣うことが多そうだな。

## **参考**

環境構築:
　[もう、jQueryはnpmで管理しようぜ](https://qiita.com/ritukiii/items/a28033b30d7681492c98)

ajax周りの参考全般:
　[はじめてajaxを使うときに知りたかったこと](https://qiita.com/nekoneko-wanwan/items/bedc0e826c0842ca0b11)
ajax参考:
　[JavascriptのAjaxについての基本まとめ](https://qiita.com/katsunory/items/9bf9ee49ee5c08bf2b3d)
　[Promiseを使う](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises)

クリックイベント追加の参考:
　[jQueryで動的に追加した要素はクリックイベントが発火しない？いやそんなことはないぞ](https://qiita.com/ayies128/items/5d044bc08b9308767f4c)


Encoding-chunkedの参考:
　[PHPとJavaScriptでHTTPストリーミングする話(Transfer-Encoding: chunked編)](http://blog.anatoo.jp/entry/20110530/1306695399)
　[jQueryの$.ajaxで通信途中のresponseTextを取得する](https://abicky.net/2011/12/25/042412/)
　[jQueryのajax()を中断する方法](http://www.koikikukan.com/archives/2013/12/06-015555.php)
Query指定:
　[jQuery.ajax()のまとめ](http://www.koikikukan.com/archives/2012/10/02-005555.php)