Yahoo!ウィジェット で Yahooウェブ検索Webサービスを使う

なぜか Yahoo! widget 作ることになったので、完全に自分用にメモをとっておく。ちょっとづつ追記していくと思う。

追記(2008年5月12日(日))

ソースがめちゃくちゃ汚いけど、時間が無いのでこれで完成としておく。

フォルダ構成
Bookmark_Monitor
  Contents
    bookmark_monitor.kon
    Latin1toUtf8.js
bookmark_monitor.kon
<?xml version="1.0" encoding="UTF-8"?>
<widget minimumVersion="4.0">
  <settings>
    <setting name="debug" value="on" />
  </settings>

  <window width="500" height="100" id="main" title="test widget" style="background-color:#eeeeee">

		<!-- 本当は、画像はasync属性(?)をtrueにして、非同期に読み込ませた方がよいと思うけど、時間があったらやる。 -->
		<image id="hatebu_logo" src="http://b.hatena.ne.jp/images/append.gif" />
		<image id="clip_logo" src="http://parts.blog.livedoor.jp/img/cmn/clip_16_12_b.gif" />

    <image id="hatebu_img0" src=""
			onMouseUp="onHatebuItemClick(0);" />
		<image id="livedoor_clip_img0" src=""
			onMouseUp="onClipItemClick(0);" />
    <image id="hatebu_img1" src=""
			onMouseUp="onHatebuItemClick(1);" />
		<image id="livedoor_clip_img1" src=""
			onMouseUp="onClipItemClick(1);" />
    <image id="hatebu_img2" src=""
			onMouseUp="onHatebuItemClick(2);" />
		<image id="livedoor_clip_img2" src=""
			onMouseUp="onClipItemClick(2);" />
    <image id="hatebu_img3" src=""
			onMouseUp="onHatebuItemClick(3);" />
		<image id="livedoor_clip_img3" src=""
			onMouseUp="onClipItemClick(3);" />
    <image id="hatebu_img4" src=""
			onMouseUp="onHatebuItemClick(4);" />
		<image id="livedoor_clip_img4" src=""
			onMouseUp="onClipItemClick(4);" />

  </window>


  <action trigger="onLoad">
<![CDATA[
		include('Latin1toUtf8.js');

		var HATEBU_API_URL = "http://b.hatena.ne.jp/entry/image/";
		var CLIP_API_URL = "http://image.clip.livedoor.com/counter/";

		var HATEBU_LINK_URL = "http://b.hatena.ne.jp/entry/";

		var HATENA_API_URL = "http://b.hatena.ne.jp/entry/json/?url=";
		var TOTAL_URLS = 5;

    var main = widget.getElementById("main");
		var hatebu_img = [];
		var clip_img = [];
		var hatebu_img_url = [];
		var clip_img_url = [];
		var pref;
		var clip_request_array = [];
		var LARGER_IMG_WIDTH;

		// 初期化
		widget.getElementById("timer").onTimerFired = checkPrefs;
		for (var i = 0; i < TOTAL_URLS; i++) {
			hatebu_img[i] = widget.getElementById('hatebu_img' + i);
			clip_img[i] = widget.getElementById('livedoor_clip_img' + i);
		}
		

		checkPrefs(); // CLIP_WIDTH などを設定する前に実行する必要がある。画像のsrc属性を設定するため。

		// はてブとLivedoorクリップのロゴの大きさは同じなので、はてブの方を代表として使う。
		var HATEBU_LOGO_WIDTH = widget.getElementById("hatebu_logo").srcWidth;
		var HATEBU_LOGO_HEIGHT = widget.getElementById("hatebu_logo").srcHeight;

		var CLIP_WIDTH = clip_img[0].srcWidth;
		var CLIP_HEIGHT = clip_img[0].srcHeight;
		var HATEBU_WIDTH = hatebu_img[0].srcWidth;
		var HATEBU_HEIGHT = hatebu_img[0].srcHeight;

		// mainウインドウの大きさを調整
		main.height = CLIP_HEIGHT + HATEBU_HEIGHT;
		LARGER_IMG_WIDTH = HATEBU_WIDTH > CLIP_WIDTH ? HATEBU_WIDTH : CLIP_WIDTH;
		main.width = LARGER_IMG_WIDTH * 5 + HATEBU_LOGO_WIDTH;
		// 表示位置の調整
		widget.getElementById("clip_logo").vOffset = HATEBU_LOGO_HEIGHT;
		for (var i = 0; i < TOTAL_URLS; i++) {
			clip_img[i].vOffset = HATEBU_HEIGHT;
			clip_img[i].hOffset = LARGER_IMG_WIDTH * i + HATEBU_LOGO_WIDTH;
			hatebu_img[i].hOffset = LARGER_IMG_WIDTH * i + HATEBU_LOGO_WIDTH;
		}


		function checkPrefs() {
			for (var i = 0; i < TOTAL_URLS; i++) {
				pref = eval("preferences.url" + i);
				if (typeof pref.value == 'undefined' || pref.value.indexOf('http://') != 0) { // URLの値が undefined 又は http:// から始まらなかったら
					// defaultValueに直す。
					pref.value = pref.defaultValue;
				}
				hatebu_img[i].src = HATEBU_API_URL + pref.value;
				hatebu_img[i].src = hatebu_img[i].src + '\\//'; // src属性に代入すると、末尾のスラッシュ「/」が自動的に削除されるので、無理やり追加。なぜうまくいくかは謎。
				clip_img[i].src = CLIP_API_URL + pref.value;
				hatebu_img_url[i] = HATEBU_LINK_URL + pref.value;
				clip_img_url[i] = pref.value;
				getJSON(pref.value, i);
			}
		}

		function onHatebuItemClick(num) {
			log("onHatebuItemClick(" + num + ")");
			log(hatebu_img_url[num]); 
			openURL(hatebu_img_url[num]);
		}
		function onClipItemClick(num) {
			log("onClipItemClick(" + num + ")");
			log(clip_img_url[num]); 
			openURL(clip_img_url[num]);
		}

		String.prototype.trim = function() {
    	return this.replace(/^\s+|\s+$/g, '');
		}

		function getJSON(url, i)
		{
			var hatebu_json_url = "http://b.hatena.ne.jp/entry/json/";
			var clip_json_url = "http://api.clip.livedoor.com/json/comments?";

			clip_request_array[i] = new XMLHttpRequest();

			clip_request_array[i].onreadystatechange = function(){ var hoge = arguments[0]; return function() {clip_handleResult(hoge)};}(i);
			//clip_request_array[i].onreadystatechange = clip_handleResult;
  		clip_request_array[i].open("GET", [
                 'http://api.clip.livedoor.com/json/comments',
                 '?link=', encodeURIComponent(url),
               ].join(''), true);
  		clip_request_array[i].send(null);

/*
			var hatebu_request = new XMLHttpRequest();
  		hatebu_request.onreadystatechange = hatebu_handleResult;
  		hatebu_request.open("GET", [
                 'http://b.hatena.ne.jp/entry/json/',
                  encodeURIComponent(url),
               ].join(''), true);
  		hatebu_request.send(null);
*/
		}

		function clip_handleResult(i) {
			//log("readyState: " + clip_request_array[i].readyState);
			//log("status: " + clip_request_array[i].status);
		  if (clip_request_array[i].readyState == 4 && clip_request_array[i].status == 200) {
    		var json_obj = eval("(" + clip_request_array[i].responseText + ")");
    		log(i + ': ' +  Latin1toUtf8(json_obj.title));
				clip_img[i].tooltip = Latin1toUtf8(json_obj.title);
				hatebu_img[i].tooltip = Latin1toUtf8(json_obj.title);
			}
		}

]]>
  </action>

	<timer id="timer" interval="3600" ticking="true" />

	<action trigger="onPreferencesChanged">
		checkPrefs();
	</action>
	<preferenceGroup name="url" order="0" title="URLs" />

	<preference name="url0">
	  <title>URL1</title>
	  <defaultValue>http://www.yahoo.co.jp/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>

	<preference name="url1">
	  <title>URL2</title>
	  <defaultValue>http://www.livedoor.com/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>

	<preference name="url2">
	  <title>URL3</title>
	  <defaultValue>http://www.hatena.ne.jp/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>

	<preference name="url3">
	  <title>URL4</title>
	  <defaultValue>http://www.rakuten.co.jp/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>

	<preference name="url4">
	  <title>URL5</title>
	  <defaultValue>http://www.google.co.jp/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>
</widget>


以下は、
2006-08-07 - nazonoDiary
のコードを勝手にコピーさせてもらった。まずかったら削除します。

Latin1toUtf8.js
// JSONで返された文字コードが、たとえUTF-8でも、latin1として解釈されてしまい、UTF-8に変換しようとする問題を解決する関数。
// http://d.hatena.ne.jp/nazoking/20060807#1154937052 のコピペ。内容は理解していない。
// 詳しくは、「http://d.hatena.ne.jp/n_shuyo/20070418/widgets」を参照。
// Latin1 -> utf8 変換表 0 の部分があるけど大丈夫かな…
Latin1_ary = [8364,0,8218,402,8222,8230,8224,8225,710,8240,352,8249,338,0,381,0,0,8216,8217,8220,8221,8226,8211,8212,732,8482,353,8250,339,0,382,376]
latin1=String.fromCharCode.apply(String,Latin1_ary);

// 問題のあるところだけ取り出す正規表現
Latin1_reg = [];for(var i=0;i<Latin1_ary.length; i++){ if(Latin1_ary[i]!=0) Latin1_reg.push(Latin1_ary[i]) };Latin1_reg = new RegExp( "["+String.fromCharCode.apply(String,Latin1_reg)+"]", "g" );

// 変換の必要のあるところだけ変換
function unLatin1(str){
  return str.replace( Latin1_reg,function(m){
    var i=latin1.indexOf(m[0]);
    if(i!=-1)c=i+128;
    return String.fromCharCode(c);
  } );
}

// UTF-8 な文字を取り出す正規表現
Utf8_reg = /[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xef][\x80-\xbf][\x80-\xbf]|\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]|[\xf1-\xf3][\x80-\xbf][\x80-\xbf][\x80-\xbf]|\xf4[\x80-\x8f][\x80-\xbf][\x80-\xbf]/g
// UTF-8 から文字列に変換
function Utf8ToString(str){
  return str.replace( Utf8_reg, function(s){
    var c = s.charCodeAt(0);
    if((c > 191) && (c < 224)) {
      return String.fromCharCode(((c & 31) << 6) | (s.charCodeAt(1) & 63));
    } else {
      return String.fromCharCode(((c & 15) << 12) | ((s.charCodeAt(1) & 63) << 6) | (s.charCodeAt(2) & 63));
    }
  });
}

function Latin1toUtf8(str){ return Utf8ToString(unLatin1(str))}

追記(2008年5月11日(土))

今日やっと何を作るか決めた。登録したURLのはてブLivedoorクリップのブクマ数を表示するというもの。最初は、ブログの総ブクマ数を取ってきたかったのだけど、はてなしか総ブクマ数の画像を返してくれるAPIを公開してなかったのでやめた。以下に作りかけのソースを載せておく。

参考にさせてもらったページ

ソーシャル・ブックマーク・サービスの被ブックマーク数取得まとめ/楽
各ソーシャルブックマークサービス(SBM)のブックマーク数画像表示APIを調べた [C!]

<?xml version="1.0" encoding="UTF-8"?>
<widget minimumVersion="4.0">
  <settings>
    <setting name="debug" value="on" />
  </settings>

  <window width="500" height="100" id="main" title="test widget" style="background-color:#eeeeee">

		<image id="hatebu_logo" src="http://b.hatena.ne.jp/images/append.gif" />
		<image id="clip_logo" src="http://parts.blog.livedoor.jp/img/cmn/clip_16_12_b.gif" />

    <image id="hatebu_img0" src="" />
		<image id="livedoor_clip_img0" src="" />
    <image id="hatebu_img1" src="" />
		<image id="livedoor_clip_img1" src="" />
    <image id="hatebu_img2" src="" />
		<image id="livedoor_clip_img2" src="" />
    <image id="hatebu_img3" src="" />
		<image id="livedoor_clip_img3" src="" />
    <image id="hatebu_img4" src="" />
		<image id="livedoor_clip_img4" src="" />


  </window>

  <action trigger="onLoad">
<![CDATA[
		var HATEBU_API_URL = "http://b.hatena.ne.jp/entry/image/";
		var CLIP_API_URL = "http://image.clip.livedoor.com/counter/";

//		var HATEBU_LOGO_URL = "http://b.hatena.ne.jp/images/append.gif";
//		var CLIP_LOGO_URL = "http://parts.blog.livedoor.jp/img/cmn/clip_16_12_b.gif";

		var HATENA_API_URL = "http://b.hatena.ne.jp/entry/json/?url=";
		var TOTAL_URLS = 5;

    var main = widget.getElementById("main");
		var hatebu_img = [];
		var clip_img = [];

		// 初期化
		widget.getElementById("timer").onTimerFired = checkPrefs;
		for (var i = 0; i < TOTAL_URLS; i++) {
			hatebu_img[i] = widget.getElementById('hatebu_img' + i);
			clip_img[i] = widget.getElementById('livedoor_clip_img' + i);
		}

		checkPrefs(); // CLIP_WIDTH などを設定する前に実行する必要がある。画像のsrc属性を設定するため。

		// はてブとLivedoorクリップのロゴの大きさは同じなので、はてブの方を代表として使う。
		var HATEBU_LOGO_WIDTH = widget.getElementById("hatebu_logo").srcWidth;
		var HATEBU_LOGO_HEIGHT = widget.getElementById("hatebu_logo").srcHeight;

		var CLIP_WIDTH = clip_img[0].srcWidth;
		var CLIP_HEIGHT = clip_img[0].srcHeight;
		var HATEBU_WIDTH = hatebu_img[0].srcWidth;
		var HATEBU_HEIGHT = hatebu_img[0].srcHeight;

		main.height = CLIP_HEIGHT + HATEBU_HEIGHT;
		main.width = CLIP_WIDTH * 5;
		// 表示位置の調整
		widget.getElementById("clip_logo").vOffset = HATEBU_LOGO_HEIGHT;
		for (var i = 0; i < TOTAL_URLS; i++) {
			clip_img[i].vOffset = HATEBU_HEIGHT;
			clip_img[i].hOffset = CLIP_WIDTH * i + HATEBU_LOGO_WIDTH;
			hatebu_img[i].hOffset = CLIP_WIDTH * i + HATEBU_LOGO_WIDTH;
		}

		var pref;
		function checkPrefs() {
			for (var i = 0; i < TOTAL_URLS; i++) {
				pref = eval("preferences.url" + i);
				if (typeof pref.value == 'undefined' || pref.value.indexOf('http://') != 0) { // URLの値が undefined 又は http:// から始まらなかったら
					// defaultValueに直す。
					pref.value = pref.defaultValue;
				}
				hatebu_img[i].src = HATEBU_API_URL + pref.value;
				clip_img[i].src = CLIP_API_URL + pref.value;
			}
		}

		String.prototype.trim = function() {
    	return this.replace(/^\s+|\s+$/g, '');
		}
/*
		function getJSON()
		{
			var request = new XMLHttpRequest();
  		request.onreadystatechange = handleResult;
  		request.open("GET", [
                 'http://api.search.yahoo.co.jp/WebSearchService/V1/webSearch',
                 '?appid=', encodeURIComponent(appid),
                 '&query=', encodeURIComponent(query.data),
                 '&site=',  encodeURIComponent(domain.data),
                 '&results=10',
                 '&format=html',
                 '&language=ja',
                 '&output=json',
               ].join(''), true);
  request.send();
*/
]]>
  </action>

	<timer id="timer" interval="10.0" ticking="true" />

	<action trigger="onPreferencesChanged">
		checkPrefs();
	</action>
	<preferenceGroup name="url" order="0" title="URLs" />

	<preference name="url0">
	  <title>URL1</title>
	  <defaultValue>http://www.yahoo.co.jp/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>

	<preference name="url1">
	  <title>URL2</title>
	  <defaultValue>http://www.livedoor.com/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>

	<preference name="url2">
	  <title>URL3</title>
	  <defaultValue>http://www.hatena.ne.jp/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>

	<preference name="url3">
	  <title>URL4</title>
	  <defaultValue>http://www.rakuten.co.jp/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>

	<preference name="url4">
	  <title>URL5</title>
	  <defaultValue>http://www.google.co.jp/</defaultValue>
	  <description>ブックマーク数を取得したいパーマリンク</description>
	  <type>text</type>
	  <group>url</group>
	</preference>
</widget>
Lain1Utf8.js
// JSONで返された文字コードが、たとえUTF-8でも、latin1として解釈されてしまい、UTF-8に変換しようとする問題を解決する関数。
// http://d.hatena.ne.jp/nazoking/20060807#1154937052 のコピペ。内容は理解していない。
// 詳しくは、「http://d.hatena.ne.jp/n_shuyo/20070418/widgets」を参照。
// Latin1 -> utf8 変換表 0 の部分があるけど大丈夫かな…
Latin1_ary = [8364,0,8218,402,8222,8230,8224,8225,710,8240,352,8249,338,0,381,0,0,8216,8217,8220,8221,8226,8211,8212,732,8482,353,8250,339,0,382,376]
latin1=String.fromCharCode.apply(String,Latin1_ary);

// 問題のあるところだけ取り出す正規表現
Latin1_reg = [];for(var i=0;i<Latin1_ary.length; i++){ if(Latin1_ary[i]!=0) Latin1_reg.push(Latin1_ary[i]) };Latin1_reg = new RegExp( "["+String.fromCharCode.apply(String,Latin1_reg)+"]", "g" );

// 変換の必要のあるところだけ変換
function unLatin1(str){
  return str.replace( Latin1_reg,function(m){
    var i=latin1.indexOf(m[0]);
    if(i!=-1)c=i+128;
    return String.fromCharCode(c);
  } );
}

// UTF-8 な文字を取り出す正規表現
Utf8_reg = /[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xef][\x80-\xbf][\x80-\xbf]|\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]|[\xf1-\xf3][\x80-\xbf][\x80-\xbf][\x80-\xbf]|\xf4[\x80-\x8f][\x80-\xbf][\x80-\xbf]/g
// UTF-8 から文字列に変換
function Utf8ToString(str){
  return str.replace( Utf8_reg, function(s){
    var c = s.charCodeAt(0);
    if((c > 191) && (c < 224)) {
      return String.fromCharCode(((c & 31) << 6) | (s.charCodeAt(1) & 63));
    } else {
      return String.fromCharCode(((c & 15) << 12) | ((s.charCodeAt(1) & 63) << 6) | (s.charCodeAt(2) & 63));
    }
  });
}

function Latin1toUtf8(str){ return Utf8ToString(unLatin1(str))}

追記(2008年5月7日(水))

すごい久しぶりに再開。なんとなくウィジェット製作の感じはつかめたと思うので、本番用も作り始めたい。まずは何を作るか決めないと…。


HTMLのdiv要素に突っ込むやり方と同じような方法がウィジェットでも(めんどくさいけど)できることがわかった。Frame作って、その中にコンテンツを突っ込んで、そのFrameをメインウインドウに appendChild() してやればよいらしい(handleResult()などを参照)。

hoge0507.kon
<?xml version="1.0" encoding="UTF-8"?>
<widget minimumVersion="4.0">
  <settings>
    <setting name="debug" value="on" />
  </settings>

  <window width="180" height="200" id="main" title="test widget" style="background-color:#ffff00">

    <text id="button" style="color:#ff0000;font-size:15px">
      <hOffset>5</hOffset>
      <vOffset>20</vOffset>
      <data>検索</data>
    </text>

    <textarea id="query" columns="15" lines="1" style="background-color:#ffffff;color:#888888;">
      <hOffset>40</hOffset>
      <vOffset>7</vOffset>
      <data>検索キーワードを入力</data>
      <onKeyPress>
        if (system.event.keyString == "Return" || system.event.keyString == "Enter") {
        doSearch();
        }
      </onKeyPress>
    </textarea>

    <textarea id="domain" columns="15" lines="1" style="background-color:#ffffff;color:#888888;">
      <hOffset>40</hOffset>

      <data>検索ドメインを入力</data>
      <onKeyPress>
        if (system.event.keyString == "Return" || system.event.keyString == "Enter") {
        doSearch();
        }
      </onKeyPress>
    </textarea>

    <frame id="frame0">
<!--     <vOffset>120</vOffset> -->
    <hOffset>10</hOffset>
<!--     <width>255</width> -->
<!--     <height>280</height> -->
    </frame>

  </window>

  <action trigger="onLoad">
    include("init.js"); // need to load first.
    include("search.js");
    init();
  </action>

  <script src="search.js" charset="utf-8" />
</widget>
init.js
// <TODO> Add resize function.
var main,
query,
domain,
frame0;
function init() {

  main = widget.getElementById("main");
  query = widget.getElementById("query");
  domain = widget.getElementById("domain");
  domain.vOffset = query.vOffset + query.height + 10;

  frame0 = widget.getElementById("frame0");
  frame0.vOffset = domain.vOffset + domain.height + 10;
  default_width = main.width - frame0.hOffset * 2; // align center.

  query.onGainFocus = function () {
    if (this.data.match(/検索キーワードを入力/)) {
      this.data = "";
      this.style.color = "#000000";
    }
  };
  domain.onGainFocus = function () {
    if (this.data.match(/検索ドメインを入力/)) {
      this.data = "";
      this.style.color = "#000000";
    }
  };
//   query.onLoseFocus = function () {
//     if (query.data.match(/^\s*$/)) {
//       this.style.color = "#888888";
//       this.data = "検索キーワードを入力";
//     } else {
//       // do nothing.
//     }
//   };
//   domain.onLoseFocus = function () {
//     if (domain.data.match(/^\s*$/)) {
//       this.style.color = "#888888";
//       this.data = "検索ドメインを入力";
//     } else {
//       // do nothing.
//     }
//   };
}
search.js
    /* START 検索処理 */
var ITEM_ARRAY = new Array();
var URL_ARRAY = new Array();
function doSearch()
{
  var appid = 'sato_YAPI_id';

  var request = new XMLHttpRequest();
  request.onreadystatechange = handleResult;
  request.open("GET", [
                 'http://api.search.yahoo.co.jp/WebSearchService/V1/webSearch',
                 '?appid=', encodeURIComponent(appid),
                 '&query=', encodeURIComponent(query.data),
                 '&site=',  encodeURIComponent(domain.data),
                 '&results=10',
                 '&format=html',
                 '&language=ja',
                 '&output=json',
               ].join(''), true);
  request.send();


//   for (var i = 0, length = 3; i <= length; i++) {
//     var text = createText(i, "テキスト");
//     frame0.appendChild(text);
//     ITEM_ARRAY[ITEM_ARRAY.length] = text;
//   }

//  main.appendChild(frame0);
//  print(text.data);
}

// [TODO] move this function to proper place.
// remove all items from Frame Object.
function removeAllItems(frame) {
  for (var i in ITEM_ARRAY) {
    frame.removeChild(ITEM_ARRAY[i]);
  }
}

function createText(offset, data) {
  var text = new Text();
  text.data = data + offset;
  text.id = 'text' + offset;
  text.color = '#000000';
  text.bgColor = '#00aaaa';
  text.bgOpacity = 170;
  text.width = default_width;
  text.vOffset = text.height * (offset + 1);
  return text;
}

function handleResult()
{
  if (this.readyState == 4) {
    var json_obj = eval("(" + this.responseText + ")");
    var r = json_obj.ResultSet;
    debug(r);

//     [TODO] Add Frame named frame1 and put r.totalResultsAvailable in it.
//     [TODO] Change color of items onmouseEnter. refer to hyuki's RSS Reader sample.
//     var text = createText(0, r.totalResultsAvailable + ' websites found');
//     frame0.appendChild(text);
    removeAllItems(frame0);
    for (var i = 0, l = r.totalResultsReturned; i < l; i++) {
      var item = (i + r.firstResultPosition) + ': ' + r.Result[i].Title;
      var text = createText(i, item);
      URL_ARRAY[i] = r.Result[i].Url;
      text.onMouseUp = function() {
        openURL(URL_ARRAY[i]);
      }
      frame0.appendChild(text);
      ITEM_ARRAY[ITEM_ARRAY.length] = text;
    }
    main.appendChild(frame0);
  }
}

/* END 検索処理 */

function debug(result)
{
  print(result.totalResultsAvailable + "件見つかりました");
  print(result.totalResultsReturned + "件表示します");
  for (var i in result.Result) {
    print("[" + i + "]" + result.Result[i].Url);
  }
}

追記(2008年5月7日(水))終わり

なんだよ「Yahoo!ウィジェット」って?

XMLウィジェットの構造)+JavaScript(動作)で記述する

なんだよ「Yahooウェブ検索Webサービス」って?

ご利用ガイド - Yahoo!デベロッパーネットワークSDKをダウンロードしてJavaScriptのソース読めばなんとなく分かる

自分にとっての問題点・疑問点

・いちいち背景に画像使うのめんどくさいので代替案があっても良いと思う。とりあえずTextオブジェクトで済ましてみる。


XML扱いにくいから、JSONでデータ取って来たはいいけど、結局表示するためにXMLに直さなくちゃなのか?HTMLだったら、「element.innerHTML=hoge」とかやれば楽だけど、XMLの場合はどうするか分からない。Frameオブジェクトの中に挿入すればいいのかなぁ?


・素直にXMLRSS)形式でとってきて、XPathでパースするやり方が普通なのか?


Xpath使ってパースするやり方だったら「Yahoo!ジオシティーズ - エラー」が参考になりそう


・自分では未確認だが、JSONでとってきたデータの日本語が文字化けするらしい…(参考:Yahoo!ウィジェット の作り方と注意点 (3) - 文字化け&二重入力 - Mi manca qualche giovedi`?


SDKのサンプルコード(yj_search_api.js)で以下のようにやってるけど、常に環境は変わらずYahoo!ウィジェット上でコードが実行されるのだから、XMLHttpRequest()だけ存在して、ActiveXObject()は存在しないんじゃないのか?Webサービスとして実装するなら、どのブラウザでアクセスされるか分からないから、こんな感じにしなくちゃいけないと思うけど…。

function createXmlHttp() {
    xmlhttp = false;
    try {
	xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
	MSXMLHTTP = true;
    } catch (e) {
	try {
	    xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	    MSXMLHTTP = true;
	} catch (E) {
	    xmlhttp = false;
	}
    }
    if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
	xmlhttp = new XMLHttpRequest();
    }
    return xmlhttp;
}

今回の内容とは直接関係ないけど、以下のようにやる方が見やすい気がする(参考:XMLHttpRequest, REST and the Rich User Experience

function getHTTPObject() {
  if (typeof XMLHttpRequest != 'undefined') {
    return new XMLHttpRequest();
  }
  try {
    return new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
    try {
      return new ActiveXObject("Microsoft.XMLHTTP");
    } catch (e) {}
  }
  return null;
}


サイト内検索はここを参考にする:サイト内検索


以下に作りかけのソースを載せておく。作り変えるたびにメモしておく。

作りかけのソース(相当ひどい)

JSONで検索データとってくるとこまでやった。次は、パースの方法と、表示の方法を考える。デザインは最後ということで…。

<?xml version="1.0" encoding="UTF-8"?>
<widget>
  <debug>on</debug>
  <window>
    <name>main_window</name>
    <title>はじめてのウィジェット</title>
    <height>300</height>
    <width>300</width>
    <visible>true</visible>

	  <text>
	    <name>myText</name>
	    <color>#000000</color>
			<bgColor>#00AAAA</bgColor>
			<bgOpacity>170</bgOpacity>
	    <size>18</size>
			<width>205</width>
	    <alignment>left</alignment>
	    <vOffset>25</vOffset>
	    <hOffset>2</hOffset>
	  </text>

		<textarea data="検索キーワードを入力">
			<name>q</name>
			<hOffset>2</hOffset>
			<vOffset>43</vOffset>
			<width>205</width>
			<alignment>left</alignment>
			<font></font>
			<size>12</size>
			<color>#000000</color>
			<bgColor>#ffffff</bgColor>
			<bgOpacity>255</bgOpacity>
			<lines>1</lines>
			<scrollbar>false</scrollbar>
			//検索テキストエリア内でEnterが押されたときの処理
			<onKeyPress>
				if (system.event.keyString == "Return" || system.event.keyString == "Enter") {
					doSearch(d.data);
				}
			</onKeyPress>
		</textarea>

		<textarea data="検索対象のドメイン名を入力">
			<name>d</name>
			<hOffset>2</hOffset>
			<vOffset>83</vOffset>
			<width>205</width>
			<alignment>left</alignment>
			<font></font>
			<size>12</size>
			<color>#000000</color>
			<bgColor>#ffffff</bgColor>
			<bgOpacity>255</bgOpacity>
			<lines>1</lines>
			<scrollbar>false</scrollbar>
			//検索テキストエリア内でEnterが押されたときの処理
			<onKeyPress>
				if (system.event.keyString == "Return" || system.event.keyString == "Enter") {
					doSearch(d.data);
				}
			</onKeyPress>
		</textarea>

	  <text>
	    <name>text0</name>
	    <color>#000000</color>
			<bgColor>#00AAAA</bgColor>
			<bgOpacity>170</bgOpacity>
	    <size>18</size>
			<width>205</width>
	    <alignment>left</alignment>
	    <vOffset>120</vOffset>
	    <hOffset>2</hOffset>
	  </text>

		<frame>
			<name>frame0</name>
	    <vOffset>120</vOffset>
	    <hOffset>2</hOffset>
			<width>255</width>
			<height>280</height>
		</frame>

  </window>

  <action trigger="onLoad">
		/* START 検索処理 */
		var appid = 'YOUR ID';
		function doSearch(what)
		{
			var request = new XMLHttpRequest();
			request.onreadystatechange = handleResult;
			request.open("GET", [
		    'http://api.search.yahoo.co.jp/WebSearchService/V1/webSearch',
		    '?appid=', encodeURIComponent(appid),
		    '&query=', encodeURIComponent(q.data),
		    '&site=',  encodeURIComponent(d.data),
		    '&results=10',
		    '&format=html',
		    '&language=ja',
		    '&output=json',
			].join(''), true);
			request.send();

			var doc2 = XMLDOM.createDocument();
			//var text = doc2.createElement( "text" );
			var text = new Text();
			text.data = 'てきすとdaYO.。';
			text.name = 'text1';
			text.color = '#000000';
			text.bgColor = '#00aaaa';
			text.bgOpacity = 170;
			text.width = 205;
			text.vOffset = 150;
			text.vOffset = 2;
			frame0.addSubview(text);
			main_window.appendChild(frame0);
			print(text.data);
		}

		function handleResult()
		{
			if (this.readyState == 4) {
				print(this.responseText);
			}
		}
		/* END 検索処理 */
	</action>

  <action trigger="onPreferencesChanged">
  </action>
</widget>