連載
» 2012年05月17日 00時00分 公開

基幹系システムでCloud SQLは使えるか試してみたGoogle Cloud SQLは基幹系で使えるのか(後編)(2/4 ページ)

[清野克行,有限会社サイバースペース]

再帰呼び出しと非同期通信のWebクライアント

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>App Engine Cloud SQL部品構成表示</title>
<style type="text/css">
<!--
#upain{
  position:absolute; top:70px; left:10px; width:700; height:125px; 
}
#lpain{ position:absolute; top:170px; left:10px; width:700; height:140px;}
.area { id="scroll-container"; }
-->
</style>
<script type="text/javascript" src="../jslib/jquery-1.7.1.min.js"></script>
<script type="text/javascript" src="../jslib/funcs.js"></script>
<script type="text/javascript">
var finalItems, finalItem;
var finalFlags = new Array();
var nSibling;
var startTime, endTime;
var query = {};
var p_item = "";
$(function(){                                                        //(1)
    var i, j, tagNode, docNode;
    $.get("/bomsvlt?mode=getfinal_j", {}, function(json){                //(2)
        var dat = $.parseJSON(json);
       for(var i = 0; i < dat.entries.length; i++){                        //(3)
           p_item = dat.entries[i].p_item;
            docNode = document.createTextNode('[0] '+p_item);        //(4)
             tagNode = document.createElement("li");                    //(5)
             tagNode.setAttribute("id", p_item);                         //(6)
            tagNode.appendChild(docNode);                            //(7)
            document.getElementById("tree").appendChild(tagNode);    //(8)
        }
    });
    $("#tree").click(function(e){                                        //(9)
        var level, p_item;
        startTime = new Date();                                    //開始期間測定
        getChild(e, level, p_item);                                    //(10)
        endTime = new Date();                                        //終了時間測定
        var exeTime = endTime - startTime;
        $("#stat").text(exeTime);
    });
    $("#tree"). mouseover (function(e){                                    //(11)
        var item_no = e.target.id;                                    //(12)
        if(item_no != "tree" && item_no.charAt(0) != "A"){                //(13)
            query["item_no"] = item_no; 
           $.get("/bomsvlt?mode=getiteminfo_j", query, function(json){
               var dat = $.parseJSON(json);
                var out = "<table width='500' border='1'><tr>"
                + "<td>部品番号</td><td>部品名</td><td>ベンダNO</td><td>ベンダ名</td></tr><tr>"
                + "<td>" + item_no + "</td><td>" + dat.item_name + "</td><td>" + dat.vendor_no + "</td><td>" + dat.vendor_name + "</td>"
                + "</tr></table>";  
                $("#item").html( out);
                $("#stat").html(dat.stat);
            });            
        }
    });    
});        
function getChild(e, level, p_item){                                    //(14)
      var i, j, k, index, pad = '';
    var childItems, childItem, tagNode, docNode; 
    var eid = e.target.id;                                            //(15)
    if(!level){                                                        //(16)
        level = 1;                                                    //(17)
        p_item = eid;                                                //(18)
        nSibling = document.getElementById(eid).nextSibling;
          if(eid.substr(0, 1) != 'A'){
             return(0);
          }else{    }    
    }else{
        level++; 
    }
    for(k=0; k<level; k++){
        pad+='------';                                            //(19)
    }    
    index = '['+level+'] ' + pad;        
    json = getsyn("/bomsvlt?mode=getchild_j&p_item="+p_item);        //(19)
    var dat = $.parseJSON(json);                                    //(20)
    for(i = 0; i < dat.entries.length; i++){                                //(21)
          tagNode = document.createElement("li");
          tagNode.setAttribute("id", dat.entries[i].c_item);
          docNode = document.createTextNode(index + dat.entries[i].c_item + "   在庫数量:" + dat.entries[i].stock);
          tagNode.appendChild(docNode);
          document.getElementById("tree").insertBefore(tagNode, nSibling);
          if(dat.entries[i].make == "y"){                                //(22)
              p_item = dat.entries[i].c_item;                            //(23)
            getChild(e, level, p_item);                                //(24)
          }
    }     
}
</script>
</head>
<body>
<h2>App Engine Cloud SQL 部品構成表示</h2>
<div>◆最終完成本のクリックで部品構成を表示 ◆構成部品のマウスオーバで部品情報表示</div>
<p> 実行時間: <span id="stat"> </span></p>
<table id="upain"><tr><td> 部品情報</td><td id="item"></td></tr></table>
<div id="lpain" class="area"><ul id="tree"></ul></div>
</body>
</html>
リスト1 HTML+JavaScript(bomtree_j.htm)

 Webクライアントのサンプルでは再帰呼び出しを行っているため、一般的なJavaScriptからの非同期通信処理よりは、やや複雑な処理内容です。

  1. 画面ロード完了イベントから起動される関数で完成品の一覧を表示
  2. 表示された完成品の部品番号クリックで再帰呼び出し関数内から非同期通信で部品構成情報を取得し構成子部品を全表示。この部分では、マウスクリックから全件表示までに要する時間計測も行っている
  3. 表示された構成子部品のマウスクリックベントから非同期通信で部品情報を取得し、画面を表示

 再帰関数内で非同期通信する場合は、jQueryのように、引数の中にコールバック関数を指定する関数形式では処理が行えません。従って、生のAjax非同期処理で対応するか、引数にコールバック関数を含まない独自関数を作成して対応する必要があります。2.の完成品クリックで、子部品展開表示では筆者作成の独自ライブラリ関数を使用しています。

1.完成品の一覧表示

 画面ロードの完了イベントで(1)の匿名関数を呼び出し、その中で画面左に並ぶ完成品の一覧を表示します。

 (2)でjQueryの$.getでリクエストを送信し、受信したJSONデータから(3)のfor文内でDOMノード操作によって表示しています。DOMノード操作での表示処理は、次の手順です。

  • (4)受信データ(部品番号)を使用したテキストノードを生成
  • (5)LIタグの要素ノードを生成
  • (6)DIVタグへの部品番号をID値とする、ID属性ノードを追加
  • (7)LIタグへテキストノードを子ノードとして接続
  • (8)以上の処理で生成したノード構成を、HTMLで記述されているTDタグに子ノード接続

 HTMLで記述されたタグに作成した子ノード構成を接続した瞬間に、画面表示(レンダリング)が行われます。

2.完成品クリックで子部品展開表示

 この関数処理がサンプルの重要なポイントです。再帰呼び出し関数の中からAjaxの非同期通信でサーバにリクエストを送信し、Cloud SQLで検索・レスポンス送信される部品構成情報で子部品を展開表示しています。このようなツリー構造の表示機能は、ここで紹介する部品構成表示などのほかにも、階層構造を持つアイテムの表示に共通して使用でき、適用できるアプリケーションも多いはずです。

 完成品一覧は、HTMLのタグ記述によるID値「tree」を持つTDタグの子ノード、として構成・表示されています。従って任意の完成品の部品番号をクリックすると、イベントの上位伝搬から、上位ノードのTDタグでイベントが検知され、(9)の匿名関数が呼び出されます。

 匿名関数内では、(10)のgetChild関数の前後で表示時間を測定しています。getChildは再帰呼び出し関数なので、子部品のツリー構成がすべて表示されるまで再帰的に繰り返し実行されます。従って、処理ステップが次の行に移った瞬間に構成表示は完了しており、ここで表示終了時間を測定しています。

●getChild関数

 ここで(10)のgetChild関数定義を見ると、引数が第4引数まで指定されています。第3引数の「p_item」は値のない引数指定です。この値のないp_itemはgetFinalが再帰呼び出しされるときに意味を持つもので、最初のgetFinal呼び出しでは、第3引数は意味がなく、反対にgetFinal内から再帰的に呼び出される場合は、第1引数は意味を持たず、第2引数以下の値が呼び出し時にセットされ意味を持つことになります。

 getChild関数内では、最初のgetFinal呼び出しでは「level = 0」から「!level」がtrueで(16)、if文以下のブロックが実行されます。クリックされた部品が完成品「eid.substr(0, 1) == 'A'」の場合は、先頭子部品レベルを1に設定し(17)、親部品の変数(p_item)に選択された親部品番号を設定します(18)。

 その後、レベル表示のパディング設定(19)を行ったあと、(19)では、処理モード(mode=getchild)、と親部品番号(p_item)をパラメータ指定して、サーブレット(mrpsvlt)へリクエストを送信しています。

 ここで使用されているGETモードの非同期通信関数用「getsyn」は、筆者が作成したライブラリ「funcs.js」に含まれます。関数定義は次のようになっています。

function getasyn(prop){    
    var xhrObj = getXhrObj();
    xhrObj.open("get", prop);
    xhrObj.setRequestHeader("If-Modified-Since", "01 Jan 2000 00:00:00 GMT");
    xhrObj.onreadystatechange = function(){
    if(xhrObj.readyState == 4){
      if(xhrObj.status == 200){
        return xhrObj.responseText;  
      }
    }
  }
  xhrObj.send(null);
}
リスト2 getasyn関数

 リスト2のように、getasyn関数は「非同期型」のGETモードでのAjax非同期通信そのもので、戻り値としてサーバからのレスポンスデータを返します。従って、(19)のように関数呼び出しからの戻り値(ここでは、json)を指定でき、再帰呼び出し関数内でも使用できるようになります。

 サーバからのレスポンスはJSON形式の文字列になっているため、(20)の「$.parseJSON」でJavaScriptオブジェクトに変換し、(21)のfor文以下でDOMノード操作により、部品番号、ID値、在庫数量をセットして、表示しています。

●再帰処理を繰り返すか抜けるかの判断

 表示は受信した子部品の数だけforループとDOMノード操作で行います。(22)のif文で表示処理中(childItem[2]=="y")の評価がtrueの場合は、(24)でgetChild関数を再帰的に呼び出しています。再帰呼び出しで指定されるgetChildの引数では、第1引数は意味がなく、第2にはインクリメントされたlevelを、第3引数のp_itemには(23)で処理中の子部品を親部品としてセットしています。

 再帰的に呼び出されたgetChild関数では、(16)のif判定はfalseとなり、引数渡しのlevelとp_itemを使用して初回と同様の非同期通信処理が実行されます。この再帰的な関数呼び出しは「childItem[2] !="y"」となるまで繰り返されます。これはCloud SQLでキー検索されるレコードのフィールド項目「make」が「n」のケースに対応しています。makeのフィールドは内作品の場合は「y」となり、購入品では「n」となっています。

 つまり購入品の場合は、その下に子部品がないため、getChild関数の再帰呼び出しが必要なく、(22)のif文判断から呼び出し元のgetChild関数に処理が戻り、処理が戻されたgetChildで(21)のfor文による子部品処理が完了していない場合は、次の子部品に対して同様の処理が繰り返されます。

3.子部品マウスクリックで部品明細表示

 部品展開表示後、子部品をマウスクリックすると(11)から匿名関数が呼び出されます。子部品以外のエリアもID値「tree」の範囲内にはあるので、それを除くための処理が必要となります。(12)の「e.target.id」でクリックされた場所で認識されるID値を取得し、子部品以外のエリアがクリックされている場合を除いています。

 その後、(14)でサーバへリクエストを送信し、レスポンス受信した部品からテーブルタグを生成して(15)で表示 しています。

 ここでリスト1では、(9)と(11)の両方でID値「tree」に対するクリックイベントで匿名関数を呼び出しています。(9)をクリックする時点では、まだ完成品だけが表示されている状態なので、子部品クリックに対する処理は必要ありません。

 これに対して(11)の場合は、完成品と、クリックされた完成品に対する子部品がすべて展開表示された状態でのクリックになるので、子部品のクリックかどうかを判断する処理(13)が加わってきます。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。