Get Utility: Getting a Script Node with JSON Data

先までのConnection Managerをつかったサンプルでは、same-origin policyにより、他のサイトのサービス(WebService)を利用するためにサーバーサイドのプログラム(Proxy)を使用した。

これは、他のサイトのコンテンツをJavaScriptに直接読み込んでeval()してしまうことで、クロスサイトスクリプティング(XSS)の脅威にさらされてしまうためである。

ただ、自分自身の用意したサーバーコンテンツや、一般に信頼のおけるサイトで提供されるサービスについては、JavaScriptで直接取りにいきたくなるのが人情。
Get Utilityは、そのような場合に用いるツール。Javascriptから、直接、他のサイトのサービスを取得しに行ける。

以下に、YUIで挙げられているサンプルを若干modifyしつつ解析していくが、その前に、このサンプルが「Module Pattern」と呼ばれるパターンで書かれていることに注目したい。
詳しくは、丁寧に書かれているYUI BLogの記事を参照だが、なるほどと納得。
何より、スコープがはっきりするのがよい。

YUIに挙げられているサンプルは、サイトを指定して、そのInbound Link(バックリンク)を検索するもの。
バックリンクというと、Googleのオーガニック検索で、Page Rankと言われて利用されているサイトの評価指標のひとつ。
これを検索するWebサービスが、YAHOO! Developer Networkで「Site Explorer Inbound Links API」として提供されている(Site Explorer Inbound Links APIの仕様はこちらを参照)。

以下のサンプリングでは、ラベルの日本語化とスタイルの変更、Exampleではscriptがbodyの後に来ているのを、前に変更した。

サンプルの初期画面は以下。

URLを入れて、「Click here to get JSON data」ボタンを押すと、全体の取得件数、トップから20件のバックリンクのタイトルとリンクが表示される。

以下に、Javascriptを含むhtmlのソースを示す。
(注)ソース中でWebServiceを呼ぶためには、Application IDというのが必要になる。
YAHOO! Developer Networkで簡単取得できるので、それに置き換えること。
また、「Site Explorer Inbound Links API」のページのRATE LIMITで書かれているように、1つのIPアドレスあたり5000クエリー/日で非商用目的であることが定められている。

<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<META http-equiv="Content-Style-Type" content="text/css">
<TITLE>Ajax_Sampling</TITLE>
<link rel="stylesheet" type="text/css" href="scripts/yui/button/assets/skins/sam/button.css" />
<style type="text/css">
body {
	margin:0;
	padding:0;
}

#container {
	width: 550px;
	hight: auto;
	padding: 3px;
    background-color: #6D739A;
    color: white;
	border: 1px solid green;
	
}

#container ol {
	/*bringing lists on to the page with breathing room */
	margin-left:2em !important;
}

#container ol li {
	/*giving OL's LIs generated numbers*/
	list-style: decimal outside !important;	
}

#container h3 {
	margin-top: 1em;
}

</style>

<!-- 読み込むjs --> 
<script type="text/javascript" src="scripts/yui/utilities/utilities.js" >
</script> 
<script type="text/javascript" src="scripts/yui/button/button-min.js" >
</script> 

<script type="text/javascript">
// このサンプルは、module patternで実装されています。
// 内容はYUI Blogへ。http://yuiblog.com/blog/2007/06/12/module-pattern
// プログラムの注釈も、それに沿っています。
//
// << module pattern >>
// Name Spaceの定義;
// YAHOOオブジェクトのメンバーとして、空のオブジェクト(YAHOO.example.SiteExplorer)
// を作る。
YAHOO.namespace("example.SiteExplorer");

// 上で作ったName Space一杯に関数を定義する。
// オブジェクトの階層は、YAHOO=>YAHOO.example.SiteExplorerとなる。
// これにより、YAHOO.example.SiteExplorerオブジェクトは、ひとつの関数で占められる。
// この内部では、プロパティーや関数は、thisで指定できる。
YAHOO.example.SiteExplorer = function() {
	
    //
    // このオブジェクト(YAHOO.example.SiteExplorer)スコープで
    // 共通に使う変数
    //
       var 	Event  = YAHOO.util.Event,
       		Dom    = YAHOO.util.Dom,
       		Button = YAHOO.widget.Button,
       		Get    = YAHOO.util.Get,
    		// Example通りのこの場所はうまくない
    		//(ExampleではScriptが末尾にあるのでよい)。  
    		// elResults = Dom.get("results"),
       		elResults,
       		tIds = {},
       		loading = false,
       		current = null;

    //
    // << module pattern >>
    // module patternに沿ったprivate method.(その1)
    //
	// Get Utility のsuccessハンドラー。
    var onSiteExplorerSuccess = function(o) {

        loading = false;

        // この例では、WebServiceのAPIにより、return内のcallbackメソッドを
        // 呼ぶようになっているので、そこに実際の処理を記入している。
  	// (Get utilityのコールバックは、responseさえあればsuccessとなるが、
  	// Webserviceのcallbackは、それ以上に正確であるため)
  	//
        // ただし、Get utilityの機能の機能により、success時のコールバックとして
        // このメソッドもコールされる。
        // 
        if (o.tId in tIds) {
            // WebserviceのCallbackと、Get Utilityのcallbackが一致する場合。
            // 普通は、こっちにはいる。
        } else {
            // WebserviceのCallbackと、Get Utilityのcallbackが一致しない場合。
	    alert("Get utility はonSuccessで発火したが、"+ 
                 "webserviceのcallbackは発火しなかった。 " +
	          "ここで、トランザクションをリトライするか、"+
                  "ユーザーに失敗を通知するかをすることができる");
        }
    }

    //
    // << module pattern >>
    // module patternに沿ったprivate method.(その2)
    //
    // Get Utility のfailureハンドラー。
    var onSiteExplorerFailure = function(o) {
		alert("データの取得に失敗しました。");
    }

    //
    // << module pattern >>
    // module patternに沿ったprivate method.(その3)
    //
    // Yahoo! Site Explorer WebServiceから指定したURLへのバックリンク
    // を取得する。
    // http://developer.yahoo.com/search/siteexplorer/V1/inlinkData.html
    var getSiteExplorerData = function() {

        if (loading) {
            return;
        }
        loading = true;
        
        //Load the transitional state of the results section:
        elResults.innerHTML = "<h3>次のキーワードのLinkを検索しています。 " +
            Dom.get("searchString").value + ":</h3>" +
            "<img src='http://l.yimg.com/us.yimg.com/i/nt/ic/ut/bsc/busybar_1.gif' " +
            "alt='お待ちください...'>";
        
        // the Yahoo Site Explorer APIのURL(とQueryString)を準備する。
        // (注)appidは自分で取得したもの。outputはjson形式で20件取得する。
        // call backにYAHOO.example.SiteExplorer.callbackを指定している。
        var sURL = "http://search.yahooapis.com/SiteExplorerService/V1/inlinkData?" +
            "appid="your_id" +
            "&results=20&output=json&omit_inlinks=domain" +
            "&callback=YAHOO.example.SiteExplorer.callback" +
            "&query=" + encodeURIComponent(Dom.get("searchString").value);
        
        // Get Utilityを使い、Web Serviceからデータを取得するcallbak定義。
        // この例では、WebserviceのAPIで指定している。
        // なので、以下は、スタブ(中身はないもの定義する)。
        // ただし、Get Utilityの機能で、下記に定義したcallbackには飛んでくる。
        var transactionObj = Get.script(sURL, 
        			{
            	onSuccess: onSiteExplorerSuccess,
            	onFailure: onSiteExplorerFailure,
            	scope    : this
        	}
        );
        
		// ScriptメソッドはトランザクションIDを含むオブジェクトを戻す。
		// そのトランザクションIDを保管する。
        current = transactionObj.tId; 
    }
        //
        // << module pattern >>
	// module patternに従い、return 配下に無名関数を定義する。
	// 
	// この無名関数は、ただちに実行されて、この場合、init,callbackというプロパティーが
	// YAHOO.example.SiteExplorerオブジェクトに追加される。
	//    
    return {
        // DOMReadyで最初に呼ばれる初期化関数。
        init: function() {
      		// (注意)  	
        	// DOMReady後でなければできない、メンバー変数の定義。
        	elResults = Dom.get("results");
               //id=siteExplorerのsubmitイベントにハンドラーを仕掛ける。
            Event.on("siteExplorer", "submit", 
            	function(e) {
            		// submit formのデフォルトの動きを抑止する。
                	Event.preventDefault(e);
            		// WebServiceの呼び出しを実行する。
                	getSiteExplorerData();
            	}, 
            	this, 
            	true
            );
	    // input type=submit にbuttonをインスタンス化する。
            var oButton = new Button("getSiteExplorerData");
        },
        
	// WebServiseの呼び出し時に定義するcallback関数。 
	// resultsにJSONデータが戻ってくる。
        callback: function(results) {
            // currentは、Webservice実行時のトランザクションID。
	    // trueを入れて、「うまくいった」とマークしておく。
            tIds[current] = true;
            
	    // Webserviceの戻り値から情報を取得する。
            var aResults = results.ResultSet.Result;
            var totalLinks = results.ResultSet.totalResultsAvailable;
            var returnedLinkCount = results.ResultSet.totalResultsReturned;

            if(aResults) {
			// 結果がある場合;処理と表示をする。          
                var html = "<h4>" +
                    totalLinks + 
                    " のリンクが取得できた; 以下に最初の " + 
                    returnedLinkCount +
                    "リンクを表示</h4><ol>";
                
                for (var i=0; i < aResults.length; i++) {
                    html += "<li><strong>" +
                        aResults[i].Title +
                        ":</strong> <a href='" +
                        aResults[i].Url +
                        "'>" + aResults[i].Url +
                        "</a></li>";
                }
                
                html += "</ol>";
            } else {
    			// データが取得できなかった場合          
            	var html = "<h3>リンクが見つかりませんでした。</h3>";
            }
            //結果の編集
            elResults.innerHTML = html;
        }
    // return の終わり    
    };
}();

// 以下は、グローバル(ページスコープ)に定義する。
//
// DOMは完全にloadされたら、サンプルを初期化する。
YAHOO.util.Event.onDOMReady(
    //DomReadyイベントで発火するハンドラ
    YAHOO.example.SiteExplorer.init, 
	//ハンドラに渡すオブジェクト(関数)
    YAHOO.example.SiteExplorer,
    //ハンドラは、上記のオブジェクトのスコープをもつ。   
 	true
);

</script>

</HEAD>

<!-- class=" yui-skin-sam"の指定はbuttunのskin適用に必要 -->
<BODY class=" yui-skin-sam">
<div id="container">
	<p>
	入力したサイトのバックリンクを検索します。
	</p>	
    <!--Use a real form that works without JavaScript:-->
    <form  id="siteExplorer" method="GET" action="http://siteexplorer.search.yahoo.com/advsearch">

        <label for="searchString">Site URL:</label> 
        <input id="searchString" type="text" name="p" value="http://developer.yahoo.com/yui" size="40">
        
        <input type="hidden" name="bwm" value="i">
        <input type="hidden" name="bwms" value="p">
    
        <input id="getSiteExplorerData" type="submit" value="Click here to get JSON data.">
    
    </form>
    <div id="results">
    </div>
</div>
</BODY>
</HTML>