自分好みのガジェットを作る! Windowsサイドバーガジェット作り入門

第3回ガジェットを作ってみる(中編)

スクリプトを書く

今回は前回の続きでWikipedia検索のガジェットの実装です。前回はガジェット本体の見た目の部分を作ったので、今度は実際に検索ボタンを押して検索、結果の表示の動作をするJavaScriptを説明していきます。

SimpleAPIのWikipedia検索を使ってみる

SimpleAPIとWikipediaAPIの説明

Wikipediaを検索するガジェットであるため、どうにかしてWikipediaを検索させるのですが、実のところWikipedia自体は検索するためのAPIを提供してはいません。

そのため「APIを使って検索できない(=一筋縄ではいかない)のでは?」となりそうですが、Wikipediaのページ名を検索できるAPIがSimpleAPIサービスでWikipediaAPIとして提供されています。

SimpleAPIとは、WikipediaAPI以外にもウェブサイトのスクリーンショットサムネイルや最寄り駅検索のためのAPIを無償で公開しているWebサービスです。SimpleAPIで公開されているAPIの説明の特徴は、無償で提供されていること、呼び出す方法が簡単であること、レスポンスの形式としてXMLやJSON、PHPシリアライズなど柔軟に選択できることがあります。

今回はこのサービスをありがたく利用させていただき、ガジェットから検索することにします。

注意)
SimpleAPIのサービスはAPIの説明にあるとおり無保証であり、予告なく終了する可能性があります。

検索結果を取得する関数を作る

まずは、検索して検索結果を取得する関数を作成します。search("検索文字列");とすると、検索結果が戻るような関数です。

具体的な処理内容は検索文字列を受け取って、XMLHttpRequestを利用してWikipediaAPIを呼び出し、検索結果のレスポンスを返す、となります。

関数が返す値はSimpleAPIはレスポンス形式にJSONを選択できるので、手間を省く意味でもそれを評価してそのまま返すということにします。

XMLHttpRequestって何?

Ajaxがどうと話題だった昨今、XMLHttpRequestを知らないという人はあまりいないのではないかと思いますが、JavaScriptからHTTPのリクエストを行うための仕組みのことです。なお、Webブラウザから利用する場合には同ドメイン内のみへリクエストできるという制限がありますが、ガジェットから利用する場合にはその制限は受けません。

JSONって何?

JSONとはJavaScript Object Notationの略称で「JavaScriptの文法を使ったデータ構造表現方法」です。実際にはJavaScriptそのものではなくサブセットなのですが、JavaScriptの形をとるデータを返せばJavaScriptで簡単に読み込むことができるようになるのが特徴です。

例えば、JSONのデータは以下のような形になります。

{
	"foo": "hogehoge",
	"bar": { "gaogao": "hauhau" },
	"baz": [ 1, 2, 3 ]
}

このようなテキストをJavaScriptで受け取って、

var o;
eval("o = " + json); // json変数には上記の文字列が入っている

などという形でevalを利用して文字列を評価すると、スクリプトから

o.foo // "hogehoge"
o.baz[1] // 1

という形で簡単に利用できるようになります。

JavaScript以外の言語からJSONを読み書きするためのライブラリはすでに多く実装されているため、JavaScript以外からも扱いやすくなっています。

実装

ではXMLHttpRequestとJSONの簡単な説明が済んだところで、受け取った引数でWikipediaのページを検索する関数を実装します。

なお今回も、CSSと同じようにHTMLの中に直接書いてしまいます。前回書いたHTMLのstyle要素の下にscript要素を追加して、その中に書いてください。

(略)
</style>
<script type="text/javascript">

</script>
<body>
(略)

まず、関数の外側を書いておきます。引数には検索文字列が来るのでをkeyword変数としておきます。

function search(keyword) {
}

はじめにXMLHttpRequestクラスを使うのでインスタンスを作ります。

var xhr = new XMLHttpRequest();

次にWikipediaAPIを呼び出すリクエストのための指定を行います。

WikipediaAPIの使い方はhttp://wikipedia.simpleapi.net/に書いてあるので見てみましょう。⁠SimpleAPI Wikipedia 入力仕様」というところに、リクエストする先のURIとパラメータについての解説があります。

APIのURI : http://wikipedia.simpleapi.net/api

パラメータは、GETあるいはPOSTで指定します。文字コードは入力は現在UTF-8のみ。出力はUTF-8固定。

  • keyword:キーワード。エイリアスとして、qも指定可能
  • output:出力方式(xml,rss,json,html,javascript,php,tsvを指定可能。デフォルトはxml)
  • callback:出力形式がJSONP時の名称を指定可能
  • lang:現在未実装。現在は日本語(ja)のみ。
  • search:現在未実装につき1のみ。⁠0.その特定キーワードのみを取り出す、1.前方一致、2.後方一致、3.前後方一致、4.FULLTEXT⁠

引用:SimpleAPI Wikipedia 入力仕様

ベースとなるURIはhttp://wikipedia.simpleapi.net/apiで、リクエストのメソッドはGETでよさそうです。

肝心のパラメータはlangとsearchは利用できないので不要として、callbackも今回は利用しないので不要です。残った二つのkeywordとoutputですが、keywordには検索文字列を、outputには結果としてJSONがほしいのでjsonを指定する必要があります。

ということで、まとめるとGETメソッドで

http://wikipedia.simpleapi.net/api?keyword=[検索文字列]&output=json

にリクエストを投げればよいということになります。

試しにhttp://wikipedia.simpleapi.net/api?keyword=Windows&output=jsonをブラウザで開いてみましょう。以下のようなテキストがずらずらと出てくるはずです。

[{"language":"ja","id":"3030","url":"http:\/\/wikipedia.simpleapi.net\/ja\/3030\/","title":"Windows","body":"\u300eMicrosoft Windows\u300f\u3(略)

これがレスポンスのJSONです。正しく返ってこなかった場合にはリクエストの文字列を確認するか、サービスが停止していないかなどを確認してみてください。

どこにリクエストを投げればよいのかわかったところで、リクエスト先の指定をします。リクエスト先の指定はXMLHttpRequestのopenメソッドで行います。openメソッドは以下の3つの引数をとります。

  • 第一引数:HTTPメソッド(HEAD,GET,POST)
  • 第二引数:URL
  • 第三引数:同期・非同期かどうかのフラグ(リクエストが完了するまでブロックする)

以上を踏まえてここのコードは以下のようになります。なお今回は非同期処理としないので第三引数はfalseにします。

xhr.open("GET", "http://wikipedia.simpleapi.net/api?keyword="+ keyword +"&output=json", false);

URLのkeywordパラメータに関数の引数のkeyword変数(検索文字列)をくっつけているのですが、実はこのままではダメです。このままでは一部の文字や日本語などが正しく通らないため、URLエンコードする必要があるのです。URLエンコードするには、組み込みのencodeURI関数を使えばよいので以下のように修正します。

xhr.open("GET", "http://wikipedia.simpleapi.net/api?keyword="+ encodeURI(keyword) +"&output=json", false);

次にリクエストを実際に送信します。送信にはsendメソッドを使います。sendメソッドは第一引数に送信するデータを取りますが、今回はGETで取得するだけなので空を表すnullを指定しておきます。

xhr.send(null);

リクエストの実行後、検索結果が返ってくるのでレスポンスのJSONを評価して返します。レスポンスの中身はXMLHttpRequestのresponseTextに入っているのでそれを直接evalします。

return eval(xhr.responseText);

以上がWikipediaAPIを呼び出す流れになります。まとめると以下のようになります。

function search(keyword) {
    var xhr = new XMLHttpRequest();

    xhr.open("GET", "http://wikipedia.simpleapi.net/api?keyword="+ encodeURI(keyword) +"&output=json", false);
    xhr.send(null);
    
    return eval(xhr.responseText);
}

エラー時の処理などは今回省略していますが、それなりのものを作る場合にはエラー時には何らかのアラートを表示するなどを盛り込んでください。

動作確認

ところでこれ、ちゃんと動くのでしょうか? ということで、ちょっと試してみましょう。以下のコードをsearch関数の下に追加して、Main.htmlをInternet Explorerで開いてみます。情報バーが出てスクリプトの実行がとめられた場合には、情報バーに従って実行を許可してください。

alert(search("Windows Vista")[0].body);

このコードはWindows VistaでWikipediaのページを検索して一件目のページのサマリをalertで表示しています。

何かサマリのようなものが表示されれば、ちゃんと検索結果を取得できています。スクリプトエラーが発生した場合には、ブラウザのエラーメッセージを頼りにコードをチェックしてみてください。

取得できることを確認したら今書いたalertの行は不要なので削除してください。

これで取得の関数は終わりです。XMLHttpRequestとWikipediaAPIの説明でちょっと長くなってしまいました。

検索結果を表示する部分を作る

次に検索結果を表示する部分を作ります。検索結果を表示する部分の動作に関してはここでは作らず、表示するのに必要なものを作ります。

Flyoutの説明

検索結果の表示は完成スクリーンショットにある通り、ガジェットからぴょこっと飛び出した部分に表示します。このぴょこっと飛び出したところを、Windowsサイドバーでは「Flyout」と呼んでいます。

図1 Windowsサイドバーでは、ぴょこっと飛び出したところを「Flyout」と呼ぶ
図1 Windowsサイドバーでは、ぴょこっと飛び出したところを「Flyout」と呼ぶ

Flyoutはガジェット本体とは別のHTMLファイルで、まったく別のドキュメントとして扱われます。表示はガジェットから任意のタイミングで行え、表示中はガジェット本体側からFlyoutのドキュメントへアクセス、また逆にFlyoutからガジェット側にアクセス可能です。

今回のようにガジェット部分だけでは表示しきれないような情報などを表示するのに利用します。

HTMLを書く

それでは、Flyout用のHTMLをガジェット用のHTMLとは別に作成します。ファイル名はそのままFlyout.htmlとしておきます。

検索結果はガジェット側から差し込むことにしたいので、図2のようにul要素に検索結果一件ずつをli要素にして表示することを考えたHTMLにします。li要素自体をガジェット側から差し込む感じです。本当はli要素の中身まで作っておいてテンプレート的にすると良いのですが、今回はそこまでは手をかけません。

図2 Flyout用のHTMLの構成
図2 Flyout用のHTMLの構成

以上を踏まえて、検索結果を表示するHTMLは以下のようになります。

<title>Wikipedia Search Results</title>
<style type="text/css">
* { margin: 0; padding: 0; }
body { width: 480px; height: 300px; overflow: auto; }
li { margin: 0.5em; }
h1 { background-color: black; color: white; font-size: 1em; font-weight: normal; }
</style>
<body>
<h1>Wikipedia 検索(Powered by SimpleAPI): <span id="resultInfo">-</span></h1>
<ul id="results">
</ul>
</body>

配置や見た目の細かい部分に関しての説明はあまり必要ないと思うので省きますが、ガジェットと同様body要素に対して表示するサイズ(widthとheightプロパティ)の指定が必要だということには注意してください。また、このFlyoutでは検索結果が表示されるため、縦に長くなるのでbody要素にoverflow: autoを指定し、高さを超えた場合にはスクロールするようにしています。

ガジェット側から操作する必要があるため、2つの要素にidを振っています。

一つ目はh1要素下にある、span要素のresultInfoです。これは検索結果の件数を表示するための部分です。

二つ目はul要素のresultsです。これは検索結果を表示するための要素です。検索結果のヒットごとをひとつのli要素としてul要素に追加して表示することを考え、idを振ります。

今回のFlyoutではスクリプトを使わないため、HTMLはこれでおしまいです。

Flyoutを表示設定と表示方法

Flyoutの利用方法を知る意味でも、試しにガジェットのほうから呼び出して表示してみます。

まず、Flyoutがどのファイルであるのかを設定する必要があります。設定は組み込みのSystem.Gadget.Flyoutオブジェクトのfileプロパティにファイル名を指定します。

先ほど作ったFlyoutの名前はFlyout.htmlとしたので、以下のように指定します。この行をMain.htmlのscript要素の先頭に追加します。

System.Gadget.Flyout.file = "Flyout.html";

そして実際に表示するにはSystem.Gadget.Flyoutオブジェクトのshowプロパティにtrueを指定します。

System.Gadget.Flyout.show = true;

試しにこの行をファイル指定の後に追加して、ガジェットを追加してみてください。ガジェットがサイドバーに出た直後、シュルシュルと結果の入っていないFlyoutが表示されるはずです。

そして別のアプリケーションなどにフォーカスを移すとシュルシュルと消えてゆきます。Flyoutはフォーカスが外れると自動的に非表示となります。

表示できることを確認したら、showの行は削除しておいてください。検索した結果を表示すればよいので最初は非表示にしておきます。

なおここで出てきたSystem.Gadget.Flyoutオブジェクトはその名の通りFlyout関連のオブジェクトで、fileプロパティ、showプロパティ以外にもFlyoutが表示されたら発生するonShowイベントや、Flyoutのドキュメントにアクセスするdocumentプロパティが用意されています。

これでFlyoutの準備はできました。あとは検索して結果を反映して、適切なタイミングで表示すればよいことになります。

ボタンを押して検索できるようにする

今度はまたガジェット本体にコードを書いていきます。そもそも、まだ「ボタンを押して検索」という動作すら実装していませんので、まずはそれを実装します。

イベントハンドラをつける

ボタンを押したら検索されるようにするには、フォームの送信イベントであるonsubmitイベントで関数を呼び出すようにします。⁠ボタンを押したら」であれば「ボタンのinput要素のonclickイベントで呼び出せばよいのでは?」と思われるかもしれませんが、検索文字列入力欄のinput要素に入力してEnterを押した場合に検索できません。それを簡単に回避するために、フォームの送信のタイミングを利用します。

それではイベント発生時に処理を行うために、以下の属性をform要素に追加します。

onsubmit="doSearch(); return false;"

onsubmitイベントではdoSearch関数を呼んで、falseを返します。doSeachはこの後実際に中身を書く予定の関数名です。

次にfalseを返している理由ですが、onsubmitは送信直前のイベントでありfalseを返すことで送信をキャンセルできます。今回はXMLHttpRequestで通信するため、フォームから送信する必要がないのでfalseを返してキャンセルしています。

検索文字列入力にidをつけておく

入力された検索文字列をスクリプトから取得できるようにしなければいけません。そのためinput要素にkeywordというidを振っておきます。

<input type="text" id="keyword" style="width: 145px">

検索ボタンを押されたときの処理を書く

ボタンが押され、イベントが発生したら関数が呼ばれるようにしたので実際の処理を書きます。先ほどdoSearch関数という名前で呼ぶようにしたのでdoSearch関数を実装します。

doSearch関数で行うことは次のようになります。

  • 入力文字列の取得
  • 検索
  • Flyoutの表示と検索結果更新(Flyoutが表示されていない場合)
  • Flyoutの検索結果更新(Flyoutが表示されている場合)

Flyoutの検索結果更新処理は若干長めで、Flyoutが表示されている場合と非表示な場合で別々なところで同じ処理を行うため、updateFlyoutという関数に記述します。

この処理のコードは短いですが、説明するにはそれなりの長さになっています。小分けにしてでは全体が把握しづらくなってしまいますし、DOM操作ばかりで難しいところはほとんどないので、実際のコードをまとめて載せてコメントと重要なところは後から細かく解説します。

// Flyoutが表示されるタイミングで検索結果を更新します
System.Gadget.Flyout.onShow = function () { updateFlyout(); };

// 検索結果をdoSearchからupdateFlyoutに引き渡すための変数
var searchResult;
// 検索文字列をdoSearchからupdateFlyoutに引き渡すための変数
var keyword;

// 検索ボタンを押したときに呼ばれる関数
function doSearch () {
    // 検索文字列を取得してkeyword変数に保持しておく
    keyword = document.getElementById("keyword").value;

    // 入力された文字列を元にsearch関数を呼んで検索し、
    // その結果をsearchResultに保持する
    searchResult = search(keyword);

    // Flyoutは既に表示されているかどうかを取得します。
    if (System.Gadget.Flyout.show) {
      // Flyoutは現在表示中なので検索結果を更新します。
      updateFlyout();
    } else {
      // Flyoutは表示されていないので表示を指示します。
      // 検索結果の反映は表示されるタイミングで行われます
      System.Gadget.Flyout.show = true;
    }
}

// Flyoutの検索結果を更新するための関数
function updateFlyout () {
  // 変数fDにFlyoutのドキュメントオブジェクトを保持しておく
  var fD = System.Gadget.Flyout.document;
  // 検索結果を反映するためのul要素をあらかじめつけておいたidで取得する
  var results = fD.getElementById('results');
  // ulの中身を削除する(既存結果の削除)
  results.innerHTML = "";

  // 検索結果の件数などを表示するためのspan要素をIDで取得する
  var resultInfo = fD.getElementById('resultInfo');

  // searchResultがnullかどうか。検索ヒット数が0件ならnullとなる。
  if (searchResult == null) {
    // ヒット数は0件だったのでその旨を表示して終了する。
    resultInfo.innerText = keyword + "は見つかりませんでした。";
    return;
  } else {
    // ヒット数が1件以上合ったので検索文字列とヒット件数を表示する。
    resultInfo.innerText = keyword + " (" + searchResult.length + "件)";
  }

  // 検索結果を見つかった個数分、一つずつul要素に追加する
  for (var i = 0; i < searchResult.length; i++) {
    // li要素を作る
    // li要素の中にリンクとページ内容が入る
    var li = fD.createElement('li'); // ヒット一つの結果

    // ページ内容
    var p = fD.createElement('p');
    // 返ってきたページ内容はHTMLが含まれるのでタグはすべて削除する
    var summary = searchResult[i].body.replace(/<[^>]+>/g, '');
    // 長すぎたら切り詰めて...をつけるようにしておく
    if (summary.length > 255) { summary = summary.substring(0, 255) + "..."; }
    // 内容をp要素にセットする
    p.innerHTML = summary;

    // リンク
    var a = fD.createElement('a');
    // リンク先をセットする
    a.href = searchResult[i].url;
    // リンク文字列をセットする
    a.innerText = searchResult[i].title;

    // li要素にリンクとページ内容を追加する
    li.appendChild(a);
    li.appendChild(p);

    // Flyoutの検索結果(ul要素)に1件を追加する
    results.appendChild(li);
  }
}

それでは重要そうなところを解説します。まず、先頭の行についてです。

System.Gadget.Flyout.onShow = function () { updateFlyout(); };

System.Gadget.FlyoutオブジェクトのonShowイベントはFlyoutが表示されたときに呼ばれるイベントで、ここではFlyoutが表示されたらupdateFlyout関数を呼ぶようイベントハンドらをセットしています。

補足)
System.Gadget.Flyout.showをtrueにした後、updateFlyout関数を呼んでも同じように思えるのですが、そうはいかないのです。なぜなら「showをtrueにした」「Flyoutが表示された」とイコールにならず、Flyoutはシステムに処理が返ったタイミングで表示されるためです。showをtrueにした直後はFlyoutのドキュメントにアクセスできず内容の更新に失敗します。それを回避するために「表示された」というイベントで始めて内容の更新をかけるようにしています。

onShowイベントを使う都合上、更新がdoSearch関数の外から呼ばれることになり、検索結果などを別な関数に渡す必要があるので、グローバルな変数keywordとsearchResultを用意しています。

var searchResult;
var keyword;

その後doSearch関数は大体コメントの通りです。その次はFlyoutの検索結果を更新するupdateFlyout関数です。関数の頭の一文はFlyoutを操作するために必要な文です。

  var fD = System.Gadget.Flyout.document;

前のFlyoutの節でも触れましたが、System.Gadget.FlyoutオブジェクトのdocumentプロパティでFlyoutのドキュメント要素を取得できます。普通のdocumentオブジェクトと同じですが、ガジェットのものではなくFlyoutのdocumentオブジェクトです。このdocumentオブジェクトを介して、FlyoutのHTMLなどを操作できます。

そして検索結果の追加周りはほぼDOM操作ですが、そもそもsearch関数(WikipediaAPIの検索)の結果として「何が返ってきていているのか?」ということを確認しなければなりません(検索結果を表示するのに、何が返ってきているのかわからないのでは表示のしようがないからです⁠⁠。

検索した結果返ってくるものはWikipediaAPIのJSONを評価したそのものなので、見つかったページの情報(オブジェクト)の配列です。以下のような構造のデータが返ってきます。

[
  // 1件目
  {
    url: "URL",
    title: "ページのタイトル",
    body: "ページの内容",
    /* その他情報 */
  },
  // 2件目
  {
    url: "URL",
    title: "ページのタイトル",
    body: "ページの内容",
    /* その他情報 */
  },
  // ...
  // n件目
  {
     ...
  }
]

配列なので検索個数はsearchResult.lengthで取得でき、n件目のデータはsearchResult[n]で取得、タイトルならsearchResult[n].titleで取得できます。

また結果が一件もない場合にはnullが返ってきます。それを踏まえてupdateFlyout関数内でFlyoutに検索結果を表示するべく要素を組み立てて、検索結果一覧に追加します。

一通りできたので確認

後半大分駆け足感がありましたが、これで動作に必要なものは一通りそろいました。ガジェットをサイドバーに追加して、好きな言葉で検索して検索結果が表示されたら完成です。おつかれさまでした。

なお、ここまでのサンプルがダウンロードできますので、ご利用ください。

次回は、作成したガジェットを配布する方法についての説明を書く予定です。

おすすめ記事

記事・ニュース一覧