1: GadgetsPepper.kon
2:
3: <?xml version="1.0" encoding="UTF-8"?>
4: <widget version="1.1" name="Gadgets Pepper">
5: <debug>off</debug>
6: <minimumVersion>3.0</minimumVersion>
7: <!-- メインウィンドウ(非表示) -->
8: <window>
9: <name>mainWindow</name>
10: <title>GadgetsPepper</title>
11: <width>0</width>
12: <height>0</height>
13: <visible>false</visible>
14: </window>
15: <!-- 設定グループ -->
16: <prefGroup>
17: <name>generalPanel</name>
18: <title>全般</title>
19: <order>1</order>
20: </prefGroup>
21: <!-- 設定項目 -->
22: <preference name="Skin">
23: <title>背景画像:</title>
24: <type>popup</type>
25: <option>モザイク模様(赤)</option>
26: <optionValue>1</optionValue>
27: <option>モザイク模様(緑)</option>
28: <optionValue>2</optionValue>
29: <option>空と雲の風景</option>
30: <optionValue>3</optionValue>
31: <option>花柄模様</option>
32: <optionValue>4</optionValue>
33: <option>バブル模様(パープル)</option>
34: <optionValue>5</optionValue>
35: <defaultValue>1</defaultValue>
36: <group>generalPanel</group>
37: </preference>
38: <!-- 環境設定変更時のイベント処理 -->
39: <action trigger="onPreferencesChanged">
40: <![CDATA[
41: it.Skin = preferences.Skin.value;
42: ]]>
43: </action>
44: <!-- 画面ロード時のイベント処理 -->
45: <action trigger="onLoad">
46: <![CDATA[
47: var it;
48:
49: // 画面が移動したときのイベント処理
50: //
51: function Com_Move(xpos, ypos){
52: mainWindow.hOffset = xpos;
53: mainWindow.vOffset = ypos;
54: }
55:
56: // コンテキストメニュー<ウィジェットの環境設定>の処理
57: //
58: function Com_Setting(){
59: showWidgetPreferences();
60: }
61:
62: // コンテキストメニューこのウィジェットを閉じる>の処理
63: //
64: function Com_Close(){
65: closeWidget();
66: }
67:
68: // COMオブジェクト生成
69: try {
70: it = COM.createObject("GadgetsPepper.ComClass1");
71: } catch(e){
72: print( "\nError: " + e + "\n" ) ;
73: closeWidget();
74: exit;
75: }
76: // ファイル展開
77: widget.extractFile( "Resources/button1.png" );
78: widget.extractFile( "Resources/button2.png" );
79: widget.extractFile( "Resources/close.png" );
80: widget.extractFile( "Resources/HotScript.js" );
81: widget.extractFile( "Resources/HotStyle.css" );
82: widget.extractFile( "Resources/next.png" );
83: widget.extractFile( "Resources/prev.png" );
84: widget.extractFile( "Resources/shop_undock.png" );
85: widget.extractFile( "Resources/title.png" );
86: widget.extractFile( "Resources/b1.png" );
87: widget.extractFile( "Resources/b2.png" );
88: widget.extractFile( "Resources/b3.png" );
89: widget.extractFile( "Resources/b4.png" );
90: widget.extractFile( "Resources/b5.png" );
91: // 画面設定
92: it.Skin = preferences.Skin.value;
93: it.X = mainWindow.hOffset;
94: it.Y = mainWindow.vOffset;
95: // COMオブジェクト接続
96: COM.connectObject( it, "Com_" );
97: // 画面表示
98: var url = convertPathToPlatform(widget.extractFile( "Resources/pepper.html" ));
99: it.Show(url);
100: ]]>
101: </action>
102: </widget>
103:
104: pepper.html
105: ?<html>
106: <head>
107: <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
108: <title>Gadgets Pepper</title>
109: <link href="HotStyle.css" rel="stylesheet" type="text/css" />
110: <script src="http://maps.google.com/maps?file=api&v=2&key=(API KEY)" type="text/javascript" charset="utf-8"></script>
111: <script type="text/javascript" src="HotScript.js"></script>
112: <script type="text/javascript">
113: var urlGourmetSearch = "http://api.hotpepper.jp/GourmetSearch/V110/?key=guest";
114: var urlGenreMaster = "http://api.hotpepper.jp/Genre/V110/?key=guest";
115: var googleMaps = null;
116: var shopNodeList = null;
117: var curShopNode = 0;
118: var lastSearchPoint = new GLatLng( 35.681074, 139.767079 );
119: var lastSearchGenre = null;
120: var initZoom = 13;
121: var idIntervalTimer = null;
122: var skinno = 0;
123: var initZoom = 13;
124: var idIntervalTimer = null;
125: var geocoder = null;
126:
127: // HTML bodyのロード時
128: // 各種初期化処理
129: //
130: function load() {
131: try{
132: // Google Mapsの作成
133: loadState();
134: if( GBrowserIsCompatible() ){
135: googleMaps = new GMap2( document.getElementById( "map" ) );
136: googleMaps.addControl( new GSmallMapControl() );
137: googleMaps.addControl( new GScaleControl() );
138: googleMaps.enableScrollWheelZoom();
139: geocoder = new GClientGeocoder();
140: if( lastSearchPoint )
141: googleMaps.setCenter( lastSearchPoint, initZoom, G_NORMAL_MAP );
142: }
143: // お店検索、ジャンル一覧を初期化する
144: // HTTP Requestを実行し、onGenreLoadで処理
145: var oReq = createHttpRequest();
146: oReq.onreadystatechange = function(){
147: if( oReq.readyState==4 ){ //readyState値は4で受信完了
148: onGenreLoad( oReq );
149: }
150: }
151: oReq.open( "GET", urlGenreMaster );
152: oReq.send();
153: setDockState();
154: if( lastSearchPoint != null )
155: findAtPoint( lastSearchPoint, lastSearchGenre );
156: }
157: catch( e ){}
158: }
159:
160: // クッキーの設定
161: //
162: function saveState(){
163: try{
164: ckstr = "";
165: ckstr += 0 + "%00";
166: if( lastSearchPoint != null ){
167: ckstr += escape(lastSearchPoint.y) + "%00";
168: ckstr += escape(lastSearchPoint.x) + "%00";
169: }
170: if( lastSearchGenre != null ){
171: ckstr += escape(lastSearchGenre) + "%00";
172: }
173: document.cookie = "pepper=" + ckstr + "; expires=Tue, 31-Dec-2030 23:59:59; "
174: }
175: catch( e ){}
176: }
177:
178: // HTML bodyのアンロード時
179: //
180: function unload(){
181: GUnload();
182: }
183:
184: // クッキーの取得
185: //
186: function loadState(){
187: try{
188: cklng = document.cookie.length;
189: ckary = document.cookie.split("; ");
190: ckstr = "";
191: i = 0;
192: while (ckary[i]){
193: if (ckary[i].substr(0,7) == "pepper="){
194: ckstr = ckary[i].substr(7,ckary[i].length);
195: break;
196: }
197: i++;
198: }
199: ckary = ckstr.split("%00");
200: if (ckary[0]) skinno = eval(ckary[0]);
201: if (ckary[1]) search_latitude = ckary[1];
202: if (ckary[2]) search_longitude = ckary[2];
203: if (ckary[3]) lastSearchGenre = ckary[3];
204: if( search_latitude != null && search_longitude != null )
205: lastSearchPoint = new GLatLng( search_latitude, search_longitude );
206: }
207: catch( e ){}
208: }
209:
210: // ジャンル一覧のデータ取得
211: //
212: function onGenreLoad( oReq ){
213: try{
214: var elmSel = document.getElementById( "genreSelect" );
215: var oXMLDocument = oReq.responseXML;
216: genreNodeList = oXMLDocument.selectNodes( "//Genre" );
217: if( genreNodeList == null )
218: return;
219: for( var i = 0 ; i < genreNodeList.length ; i++ ){
220: var node = genreNodeList.item( i );
221: var ndName = node.selectSingleNode( "GenreName" );
222: var ndCD = node.selectSingleNode( "GenreCD" );
223: if(ndName != null && ndCD != null ){
224: var opt = document.createElement( "OPTION" );
225: elmSel.options.add( opt );
226: opt.innerText = ndName.text;
227: opt.value = ndCD.text;
228: if( opt.value == lastSearchGenre )
229: opt.selected = true;
230: }
231: }
232: }
233: catch( e ){}
234: }
235:
236: // 時刻表示のための書式規格化
237: //
238: function format(Num, Format){
239: var s = new String(Num);
240: var z = "";
241: for(c = 0 ; c < Format - s.length ; c++){
242: z = z + "0";
243: }
244: return (z + s);
245: }
246:
247: // 現在時刻の表示
248: //
249: function showCurrentTime(){
250: var date = new Date();
251: var y = date.getFullYear();
252: var mo = date.getMonth();
253: var d = date.getDate();
254: var h = date.getHours();
255: var m = date.getMinutes();
256: var s = date.getSeconds();
257: document.getElementById( "timeView" ).innerHTML = format(y,4) + "." + format(mo+1,2) + "." + format(d,2) + " " + format(h,2) + ":" + format(m,2);
258: }
259:
260: // 緯度経度による、お店の検索
261: //
262: function findAtPoint( pt, genre ){
263: var oReq = createHttpRequest();
264: oReq.onreadystatechange = function(){
265: if( oReq.readyState==4 ){ //readyState値は4で受信完了
266: onXmlLoad( oReq, true, false );
267: }
268: }
269: var url = urlGourmetSearch + "&Latitude=" + pt.y + "&Longitude=" + pt.x;
270: if( ! isNullOrEmpty( genre ) )
271: url = url + "&GenreCD=" + genre;
272: oReq.open( "GET", url );
273: oReq.send();
274: }
275:
276: // お店検索の実行
277: //
278: function onFindShops(){
279: try{
280: lastSearchPoint = googleMaps.getCenter();
281: lastSearchGenre = document.getElementById( "genreSelect" ).value;
282: findAtPoint( lastSearchPoint, lastSearchGenre );
283: }
284: catch( e ){//document.writeln( e );}
285: }
286:
287: //住所検索のためのキーダウン処理
288: //
289: function onKeydown(){
290: if(event.keyCode == 13){findAddress();}
291: }
292:
293: // 住所検索
294: //
295: function findAddress(){
296: try{
297: var address = document.getElementById( "address" ).value;
298: if( isNullOrEmpty( address ) )
299: return;
300: if (geocoder) {
301: geocoder.getLatLng(
302: address,
303:
304: function( point ) {
305: if (!point) {
306: // alert(address + " not found");
307: } else {
308: googleMaps.setCenter(point, initZoom, G_NORMAL_MAP );
309: }
310: });
311: }
312: }
313: catch( e ){
314: document.write("error = " + e.description + "<BR>");
315: document.write("error # = " + e.number + "<BR>");
316: document.writeln( e );
317: }
318: }
319:
320: // 地図上へのマーカーの追加
321: //
322: function addMarker( pt, idx ){
323: var marker = new GMarker( pt );
324: GEvent.addListener( marker, "click", function(){
325: setShopInfo( idx );
326: } );
327: googleMaps.addOverlay( marker );
328: }
329:
330: // お店一覧のXMLデータ、読み込み処理
331: //
332: function onXmlLoad( oReq, bSetMarker, bForceCenter ){
333: try{
334: var oXMLDocument = oReq.responseXML;
335: shopNodeList = oXMLDocument.selectNodes( "//Shop" );
336: if( shopNodeList == null )
337: return;
338: googleMaps.clearOverlays();
339: for( var i = 0 ; i < shopNodeList.length ; i++ ){
340: var node = shopNodeList.item(i);
341: var ndLatitude = node.selectSingleNode( "Latitude" );
342: var ndLongitude = node.selectSingleNode( "Longitude" );
343: if( ndLatitude != null && ndLongitude != null ){
344: var latitude = parseFloat( ndLatitude.text ); // 経度
345: var longitude = parseFloat( ndLongitude.text ); // 緯度
346: if( bSetMarker ){
347: addMarker( new GPoint( longitude, latitude ), i );
348: }
349: if( bForceCenter ){
350: googleMaps.setCenter( new GLatLng( latitude, longitude ) );
351: bForceCenter = true;
352: }
353: }
354: }
355: }
356: catch( e ){}
357: }
358:
359: // お店情報を表示設定する
360: //
361: function setShopInfo( idx ){
362: if( shopNodeList == null || idx < 0 || shopNodeList.length <= idx )
363: return;
364: curShopNode = idx;
365: document.getElementById("shop").style.visibility = "visible";
366: node = shopNodeList.item( idx );
367: var ndShopName = node.selectSingleNode( "ShopName" );
368: var ndShopURL = node.selectSingleNode( "ShopUrl" );
369: var ndGenreCatch = node.selectSingleNode( "GenreCatch" );
370: var ndPictureURL;
371: ndPictureURL = node.selectSingleNode( "PictureUrl/PcLargeImg" );
372: if( ndShopName != null )
373: document.getElementById( "shopName" ).innerHTML = ndShopName.text;
374: if( ndShopURL != null )
375: document.getElementById( "shopName" ).setAttribute( "href", ndShopURL.text );
376: if( ndGenreCatch != null )
377: document.getElementById( "genreCatch" ).innerHTML = ndGenreCatch.text;
378: if( ndPictureURL != null )
379: document.getElementById( "shopPicture" ).setAttribute( "src", ndPictureURL.text );
380: if( ( curShopNode + 1 ) < shopNodeList.length )
381: document.getElementById("btnNext").style.visibility = "inherit";
382: else
383: document.getElementById("btnNext").style.visibility = "hidden";
384: if( curShopNode > 0 )
385: document.getElementById("btnPrev").style.visibility = "inherit";
386: else
387: document.getElementById("btnPrev").style.visibility = "hidden";
388: }
389:
390: // 前のお店情報に移動する
391: //
392: function prevShop(){
393: if( curShopNode > 0 ){
394: setShopInfo( curShopNode -1 );
395: }
396: }
397:
398: // 次のお店情報に移動する
399: //
400: function nextShop(){
401: if( ( curShopNode + 1 ) < shopNodeList.length ){
402: setShopInfo( curShopNode + 1 );
403: }
404: }
405:
406: // お店情報表示を閉じる
407: //
408: function closeShopInfo(){
409: document.getElementById("shop").style.visibility = "hidden";
410: }
411:
412: // 表示を設定する
413: //
414: function setDockState(){
415: document.getElementById( "shopName" ).style.fontSize = "10px";
416: document.getElementById( "genreCatch" ).style.fontSize = "10px";
417: document.body.style.width = "320px";
418: document.body.style.height = "320px";
419: document.getElementById( "base").style.width = "320px";
420: document.getElementById( "base").style.height = "320px";
421: cklng = document.cookie.length;
422: ckary = document.cookie.split("; ");
423: ckstr = "";
424: i = 0;
425: while (ckary[i]){
426: if (ckary[i].substr(0,7) == "pepper="){
427: ckstr = ckary[i].substr(7,ckary[i].length);
428: break;
429: }
430: i++;
431: }
432: // 背景の状態を設定する
433: var n = eval(ckstr.substring(0,1));
434: if( n != null ){
435: switch( n ){
436: case 2:
437: document.getElementById("base").style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=b2.png)";
438: document.getElementById( "timeView" ).style.color = "#8B795E";
439: break;
440: case 3:
441: document.getElementById("base").style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=b3.png)";
442: document.getElementById( "timeView" ).style.color = "#8B795E";
443: break;
444: case 4:
445: document.getElementById("base").style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=b4.png)";
446: document.getElementById( "timeView" ).style.color = "#8B795E";
447: break;
448: case 5:
449: document.getElementById("base").style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=b5.png)";
450: document.getElementById( "timeView" ).style.color = "#8B795E";
451: break;
452: default:
453: document.getElementById("base").style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=b1.png)";
454: document.getElementById( "timeView" ).style.color = "#8B795E";
455: break;
456: }
457: }
458: document.getElementById( "titlelogo").style.position = "relative";
459: document.getElementById( "address").style.position = "relative";
460: document.getElementById( "Button1").style.position = "relative";
461: document.getElementById( "btnPrev").style.position = "relative";
462: document.getElementById( "btnNext").style.position = "relative";
463: document.getElementById( "Button4").style.position = "relative";
464: document.getElementById( "genreSelect").style.position = "relative";
465: idIntervalTimer = setInterval( "showCurrentTime()", 1000 );
466: }
467: </script>
468: </head>
469:
470: <body onload="load()" onunload="unload()">
471: <div id="base">
472: <table width="100%" cellpadding="4px" cellspacing="4px">
473: <tr style="height:20px;">
474: <td>
475: <span id="titlelogo" style="width:132px;height:22px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=title.png);" onclick="saveState()"></span>
476: </td>
477: <td align="right"><b><span id="timeView"></span></b></td>
478: </tr>
479: <tr>
480: <td valign="middle">
481: <input id="address" style="width:180px;" type="text" onKeyPress="onKeydown()" />
482: </td>
483: <td>
484: <span id="Button4" style="cursor:hand;width:117px;height:20px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=button1.png);" onclick="findAddress()"></span>
485: </td>
486: </tr>
487: <tr>
488: <td colspan="2">
489: <div id="map" style="width:304px;height:210px;" onmousedown=""></div>
490: </td>
491: </tr>
492: <tr>
493: <td valign="middle" colspan="2">
494: <select id="genreSelect" style="cursor:hand;width:200px;"><option value="">全てのジャンル</option></select>
495: <span id="Button1" style="cursor:hand;width:97px;height:20px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=button2.png)" onclick="onFindShops()"></span></td>
496: </td>
497: </tr>
498: </table>
499: <div id="poweredby" style="color:#363636;font-size: 9px; width:300px;text-align:right;" >Powered by ホットペッパー.jp</div>
500: </div>
501: <div id="shop" align="center" style="width:283px;height:195px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=shop.png)" >
502: <table width="100%" cellpadding="4px" cellspacing="4px">
503: <tr valign="top" style="height:25px">
504: <td colspan="2" align="right">
505: <div id="Button1" style="cursor:hand;width:21px;height:21px;background-image:url(close.png)" onclick="closeShopInfo()"></div>
506: </td>
507: </tr>
508: <tr valign="top" style="height:15px;">
509: <td colspan="2">
510: <a id="shopName" target="_blank"></a>
511: </td>
512: </tr>
513: <tr valign="top" style="height:100px;">
514: <td style="text-align:left;width:110px;" >
515: <img id="shopPicture" src="null" alt="写真" style="width:100px;height:100px;"/></td>
516: <td align="left">
517: <span id="genreCatch"></span>
518: </td>
519: </tr>
520: <tr valign="bottom">
521: <td align="left">
522: <span id="btnPrev" style="cursor:hand;width:22px;height:22px;background-image:url(prev.png)" onclick="prevShop()"></span>
523: </td>
524: <td align="right">
525: <span id="btnNext" style="cursor:hand;width:22px;height:22px;background-image:url(next.png)" onclick="nextShop()"></span>
526: </td>
527: </tr>
528: </table>
529: </div>
530: </body>
531: </html>
ウィジェットのタイトルなど、ウィジェット属性情報を設定します。
設定できるオプションを定義しています。GadgetsPepperでは、背景画像を選ぶことができるようにしています。
COM(Component Object Model)はWindowsに備わっているWindowsプログラムを部品化する仕組みですが、Yahoo! WidgetsにもCOMを呼び出する仕組みがあります。COM形式のコンポーネントを作成するにはVisualBasic などWindows用のコンパイラが必要となります。Yahoo! Widgetsから呼びだす際にはCOM.createObject()という関数を使い、COMコンポーネントそれぞれに割り当てられているProgIDを渡すようにします。
オプション設定の読み込みを行っています。オプション設定の読み込みにはPreferencesオブジェクトを使用します。ここでは、オプション設定によって設定されている背景画像の種類(値)を、COMのプロパティを通じて、画面の方に反映しています。
Windowsプログラムとのやり取りにはCOMのプロパティとイベントを利用しています。プロパティの利用は比較的容易ですが、イベントを利用するためには、COM.connectObject()という関数を用い、また、イベントに対応する関数をJavascriptで用意しする必要があります。このとき関数名の先頭部分をCOM.connectObject()の引数と一意させると連動するようになります。ここでは引数に"Com_"を渡しているため、Com_○○という名前の関数がイベントに対応しています。
HotScript.jsを読み込みます。HotScript.jsではXMLHttpRequestオブジェクトの生成、変数のnullの判定、URLエンコードなど、プログラムで繰り返し使用されるファンクションをまとめて記述しています。
HTML bodyがロードされた際に、各種初期化処理を行います。GMap2オブジェクトの生成および地図の初期表示、GClientGeocoderオブジェクトの生成、料理ジャンルの一覧の初期化、背景画像の設定、時計表示のためのインターバルタイマーの設定。ガジェットが前回終了した時の状態を復帰する処理も行います。
Cookieを設定します。第三者サイト由来のCookie情報取得に対して、ブラウザがエラーを起こすため、保存情報を連結した形式でCookieを作成しています。
HTML bodyがアンロードされた時に、アプリケーションの状態を保存します。
Cookieを取得します。連結された情報を分解し、そこから検索条件を再構築しています。
ジャンルマスターを含むXMLデータを処理します。GenreName(ジャンル名)ノード、GenreCD(ジャンルコード)ノードの値をHTMLのOPTION要素に設定し、プルダウンメニューを設定します。
ウィジェットの右上に表示される日付と時間をセットします。
緯度経度、料理ジャンルを指定して、グルメサーチAPIを呼び出します(お店を検索します)。HTTP Requestで取得したデータはonXmlLoad()で処理します。
「読込」ボタンが押されたときに呼び出されます。地図中心の緯度経度、選択されている料理ジャンルを取得し、findAtPoint()を実行します。
住所検索時のリターンキーを処理します。キーコードが13であれば、findAddress()を実行します。
ジオコーダーGClientGeocorderを用い住所を緯度経度に変換します。変換できればその位置に地図中心点を移動、変換できなかった場合はエラーメッセージを表示します。
地図にマーカーを追加します。addListenerメソッドで、マーカーがクリックされた時にsetShopInfo()が呼ばれるようにします。
グルメ検索APIの結果データを処理します。Shop(グルメ店)ノードを詳細画面表示用に保存します。また、Latitude(緯度)ノードとLongitude(経度)ノードを使い、地図にグルメ店マーカーを追加します。
onXmlLoad()内で保存したShop(グルメ店)ノードのリストから、idx番目のお店の情報を表示します。ShopName(店名)、ShopUrl(店URL)、GenreCatch(店キャッチコピー)、PcLargeImg(店画像URL)の各ノードの値を取得し、該当するHTML要素にセット。グルメ店詳細情報の表示は、div要素のvisibilityスタイルをhiddenからvisibleに変えることで行います。
idxの値により「前へ」「次へ」ボタンの表示、非表示を制御します。
表示されているグルメ店詳細情報を閉じます。グルメ店詳細情報のdiv要素をhiddenにします。
ウィジェットの表示状態を設定します。背景画像の設定もここで行っています。
|