jQueryではじめるAjax

第3回Youtube APIとjQueryを使いこなす

今回は、Youtube APIを掘り下げて説明します。また、サンプルの機能を拡張して、jQueryの使い方をより詳しく説明します。

Youtube API - フィード

データの種類

Youtube APIでは、使い方に応じたデータが用意されています。フィードをリクエストするURLのprojectionの部分にどのようなデータを返してほしいかを指定します。

http://gdata.youtube.com/feeds/projection/videos

現在のところ、projectionは2種類用意されています。

表1 projectionの値
projection説明
api ytスキーマやmediaスキーマ等の全ての拡張要素を含んだデータを返します。全てのプロパティはテキストです。通常はこちらを使います。
base いくつかの拡張要素が除かれたデータを返します。summaryプロパティやcontentプロパティにはHTMLが含まれています。フィードリーダなどに読み込ませるときなどに使います。

たとえば、検索結果を表示する簡単なアプリケーションのために、整形されたHTMLを含むデータを取得するには次のように指定します。

http://gdata.youtube.com/feeds/base/videos

フィードの種類

データを取得するフィードは数種類あります。

通常はVideoフィードを使えば十分ですが、投稿者の情報やコメントの情報等を取得するフィードもありますので、必要に応じて使い分けてください。

全てのフィードは読み取り専用であり、認証無しでリクエストすることができます。 また、新しく投稿されたビデオがフィードで取得できるようになるには、インデックス化のため数時間かかります。

Videoフィード

Videoフィードには、ビデオ、関連動画、standard、お気に入り、再生リスト、コメントのフィードがあります。

ビデオフィード

ビデオを検索するには、ビデオフィードを使います。ビデオフィードは、次のURLにリクエストします。

http://gdata.youtube.com/feeds/projection/videos

パラメータを付与して検索条件を指定することができます。使用できるパラメータについては後述します。

たとえば、catというキーワードで、最大件数を10件として検索するには以下のようにリクエストします。

http://gdata.youtube.com/feeds/api/videos?vq=cat&max-results=10

関連動画フィード

関連動画フィードは、videoidにビデオIDを指定して取得します。最大100件まで取得することができます。 関連動画フィードは、次のURLにリクエストします。

http://gdata.youtube.com/feeds/projection/videos/videoid/related

Standardフィード

Standardフィードは、様々な基準でカテゴライズされたビデオを返します。 Standardフィードは、次のURLにfeedidを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/standardfeeds/feedid

feedidは8種類用意されています。

表2 feedidの値
feedid説明
top_rated 評価の高い動画
top_favorites お気に入り登録の多い動画
most_viewed 再生回数が多い動画
most_discussed 最も議論された動画
most_linked リンクの多い動画
most_responded 動画レスポンスの多い動画
(動画レスポンスとは、動画に対する意見を動画で投稿できる機能です)
recently_featured 最近のおすすめ動画
watch_on_mobile 携帯電話のための動画

お気に入りフィード

ユーザのお気に入りに登録されているビデオを取得します。お気に入りフィードは、次のURLにユーザ名usernameを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/users/username/favorites

再生リストフィード

再生リストのビデオを取得します。再生リストフィードは、次のURLに再生リストIDplaylistIDを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/playlists/playlistID

ユーザ再生リストフィードを使うと、ユーザが登録している再生リストIDを取得することができます。

コメントフィード

ビデオのコメントを取得します。コメントフィードは、次のURLにビデオIDvideoIDを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/videos/videoID/comments

ユーザプロファイルフィード

ユーザのプロファイルを取得できます。ユーザプロファイルフィードは、次のURLにユーザ名usernameを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/users/username

ユーザ再生リストフィード

ユーザが登録している再生リストを取得できます。ユーザがプライバシーを「公開」にしている再生リストのみ取得できます。 ユーザ再生リストフィードは、次のURLにユーザ名usernameを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/users/username/playlists

ユーザコンタクトリストフィード

ユーザのユーザコンタクトリストに登録されているユーザを取得できます。 ユーザコンタクトリストは、次のURLにユーザ名usernameを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/users/username/contacts

コンタクトリストはカテゴライズすることができますが、カテゴリで絞り込むには次のようにカテゴリ名categoryを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/users/username/contacts/-/category

ユーザ登録チャンネルフィード

ユーザの登録チャンネルを取得できます。 ユーザ登録チャンネルフィードは、次のURLにビデオIDvideoIDを指定してリクエストします。

http://gdata.youtube.com/feeds/projection/users/username/subscriptions

登録チャンネルには、チャンネル、お気に入りなどを登録することができますが、その種別を判別するにはfeed.entry[n].category[n].termプロパティを参照します。

参考

Youtube API - パラメータ

Google Dataパラメータ

Youtube APIは、次のGoogle Dataパラメータを使用することができます。

表3 Google Dataパラメータ
パラメータ説明
max-results 最大取得件数を数値で指定します。
start-index 何件目から取得するのかを、1から始まる数値で指定します。
/category カテゴリを指定します。
カテゴリを指定するには、次のように/-/の後に検索するカテゴリ文字列を付与してURLを生成し、リクエストします。
例)Comedyカテゴリからビデオを検索する。
http://gdata.youtube.com/feeds/api/videos/-/Comedy/
author 投稿者を指定します。
alt レスポンスデータを指定します。atom(Atom feed),rss(RSS 2.0),json(JSON),json-in-script(JSONP)のいずれかを指定します。 何も指定をしないと、atomになります。

Youtube APIパラメータ

Youtube APIは、次のYoutube APIパラメータを使用することができます。

表4 Youtube APIパラメータ
パラメータ説明
vq 検索のキーワードを指定します。
複数のキーワードをAND条件で指定するには、スペースで区切ります。
例:keyword1 keyword2
複数のキーワードをOR条件で指定するには、ORで区切ります。
例:keyword1 OR keyword2
検索結果から除きたいキーワードを指定するには、⁠-」をキーワードの前につけます。
例:keyword1 -keyword2
format ビデオフォーマットを指定します。1(モバイル再生用のRTSPストリーミングURL⁠⁠、または5(SWFプレーヤーへのHTTP URL)を指定することができます。
racy 制限つきビデオを検索結果に含めるかどうかを指定します。制限つきビデオを検索結果に含めるにはincludeを指定します。
orderby 検索結果の順序を指定します。 relevance(関連度⁠⁠、updated(追加日⁠⁠、viewCount(再生回数⁠⁠、rating(評価)のいずれかを指定します。 何も指定をしないと、relevanceになります。
time Standardフィードの対象期間を指定します。 today(本日⁠⁠、this_week(今週⁠⁠、this_month(今月⁠⁠、all_time(すべての期間)のいずれかを指定します。 何も指定をしないと、all_timeになります。

Youtube API - レスポンスデータ

今回は最も使用頻度の高い、Videoフィードのレスポンスデータ(JSON)を解説します。

Videoフィードのレスポンスデータ(JSON)

レスポンスデータの構造
{
ヘッダ部
フィード部
}
構造説明
ヘッダ部 データのヘッダ情報を格納します。
フィード部 データのフィード情報を格納します。

ヘッダ部

ヘッダ部の構造
"version":
"encoding":
プロパティ説明
version バージョンです。現在は1.0です。
encoding 文字エンコーディングです。JSONの場合はUTF-8です。

フィード部

フィード部の構造
"feed": {
スキーマ部
フィードヘッダ部
エントリリスト部
}
構造説明
スキーマ部 フィードで使用されているスキーマを宣言しています。
フィードヘッダ部 フィードのヘッダ情報を格納します。
エントリリスト部 フィードの各エントリデータを格納する配列です。

スキーマ部

スキーマ部の構造
"xmlns":
"xmlns$openSearch":
"xmlns$gml":
"xmlns$georss":
"xmlns$media":
"xmlns$yt":
"xmlns$gd":
プロパティ説明
xmlns フィードの名前空間を宣言します
xmlns$openSearch OpenSearchスキーマの宣言です。
xmlns$gml GML(the Geography Markup Language)スキーマの宣言です。
xmlns$georss GeoRSSスキーマの宣言です。
xmlns$media YahooのMedia RSSスキーマの宣言です。
xmlns$yt Youtubeスキーマの宣言です。
xmlns$gd Google Dataスキーマの宣言です。

フィードデータがこれらのスキーマを使用している場合は、JSONでは次のようにプロパティ名が決定されます。

スキーマ名(xmlns$を除いたもの)$名前

たとえば、OpenSearchスキーマを使用しているフィードデータは次のようなプロパティ名です。

openSearch$名前

フィードヘッダ部

フィードヘッダ部の構造(抜粋)
"id":{"$t":}
"logo":{"$t":}
"openSearch$totalResults":{"$t":}
"openSearch$startIndex":{"$t":}
プロパティ説明
id.$t idが格納されています。リクエストしたフィードのURLです(パラメータを除く⁠⁠。
logo.$t YoutubeのロゴのURLです。
openSearch$totalResults.$t 検索結果の件数です。
openSearch$startIndex.$t 検索結果の開始です。

エントリリスト部

エントリリスト部の構造
"entry":[
エントリ部 1...n
]
プロパティ説明
entry[n] エントリデータを格納する配列です。

ここに、検索結果のビデオの情報が配列として格納されます。 検索結果が0件の場合は、entryプロパティ(エントリリスト部)は存在しません。

エントリ部

エントリ部の構造
エントリヘッダ部
メディアグループ部
構造説明
エントリヘッダ部 エントリデータのヘッダ情報です。
メディアグループ部 ビデオの詳細情報です。

エントリヘッダ部

エントリヘッダ部の構造(抜粋)
"id":{"$t":}
"published":{"$t":}
"title":{"$t":}
"content":{"$t":}
"author":{"name":{"$t":}}
"yt$statistics":{"viewCount":}
プロパティ説明
id.$t idが格納されています。このビデオ情報のフィードのURLです。
published.$t 投稿日時です。RFC 3339フォーマットです。
title.$t ビデオのタイトルです。
content.$t ビデオの説明です。
author.name.$t 投稿者の名前です。
yt$statistics.viewCount 再生回数です。

メディアグループ部

メディアグループ部の構造(抜粋)
media$group{
"media$title":{"$t":}
"media$description":{"$t":}
"media$keywords":{"$t":}
"yt$duration":{"seconds":}
"media$content":[{"url":}]
"media$player":[{"url":}]
"media$thumbnail":[{"url":}]
}
プロパティ説明
media$title.$t ビデオのタイトルです。
media$description.$t ビデオの説明です。
media$keywords.$t ビデオにつけられたカテゴリもしくはタグです。カンマ区切りで格納されています。
yt$duration.seconds ビデオの秒数です。
media$content[n].url ビデオへのURLです。
media$content[n].yt$formatが1の場合はモバイル再生用のRTSPストリーミングURLです。
media$content[n].yt$formatが5の場合はSWFへのURLです。
media$player[n].url プレーヤーページへのURLです
media$thumbnail[n].url サムネイル画像へのURLです。

全体の構造

{
ヘッダ部
"version":
"encoding":
フィード部
"feed": {
スキーマ部
"xmlns":
"xmlns$openSearch":
"xmlns$gml":
"xmlns$georss":
"xmlns$media":
"xmlns$yt":
"xmlns$gd":
フィードヘッダ部(抜粋)
"id":{"$t":}
"logo":{"$t":}
"openSearch$totalResults":{"$t":}
"openSearch$startIndex":{"$t":}
エントリリスト部
"entry":[
エントリヘッダ部(抜粋)
"id":{"$t":}
"published":{"$t":}
"title":{"$t":}
"content":{"$t":}
"author":{"name":{"$t":}}
"yt$statistics":{"viewCount":}
メディアグループ部(抜粋)
media$group{
"media$title":{"$t":}
"media$description":{"$t":}
"media$keywords":{"$t":}
"yt$duration":{"seconds":}
"media$content":[{"url":}]
"media$player":[{"url":}]
"media$thumbnail":[{"url":}]
}
]
}
}

「~部」というのは筆者がつけた名称です。

参考

サンプルの機能拡張(1)

第2回で実装したサンプルの機能を拡張し、jQueryの使い方をより詳しく説明します。

実装のイメージをつかみやすくするため、先にサンプルをご紹介します。画像をクリックすると、別ウィンドウでサンプルが開きます。

図1 第3回サンプル
図1 第3回サンプル

機能概要

検索結果サムネイル表示機能

  • 検索を実行すると、サムネイル画像と再生回数を9件(3×3)表示する。
  • サムネイル画像をクリックすると、ビデオの再生ページに遷移する。
  • 「関連度」⁠追加日」⁠再生回数」⁠評価」をクリックすると検索結果を並べ替えることができる。
  • 「前へ」⁠次へ」をクリックするとページ送りを行う。
  • 検索結果件数、表示しているサムネイルのインデックス番号を表示する。

検索履歴機能

  • 検索を実行すると、検索履歴に履歴が残る。
  • 検索キーワードが検索履歴に存在する場合は、その履歴を最上位に移動する。
  • 検索履歴には、検索結果の第1番目のサムネイル画像、検索キーワード、削除リンク「[x]」を表示する。
  • 検索履歴をクリックすると、その検索キーワードで再度検索する。
  • 削除リンク「[x]」をクリックすると、その検索履歴を消去する。
  • 「クリア」をクリックすると、全ての検索履歴を消去する。

画面要素

タイトル

いままでタイトルもつけていなかったので、⁠Youtube 検索」とタイトルをつけましたリスト1⁠。

リスト1 タイトル
<h1>Youtube 検索</h1>

検索フォーム

検索キーワードを入力するためのテキストボックスと、検索を実行する「検索」ボタンを作りますリスト2⁠。

リスト2 検索フォーム
<!-- 検索フォーム -->
<form id="frmSearch">
  <input type="text" id="keyword" size="35">
  <input type="submit" value="検索">
</form>

id=frmSearchとしてform要素を定義します。検索を実行する「検索」ボタンを作ります。ここは前回と変わりませんね。

検索履歴

検索履歴を表示する領域ですリスト3⁠。

リスト3 検索履歴
<!-- 検索履歴 -->
<div id="history">
    <!-- ヘッダ -->
    <div class="header">
        <div class="submenu"><a id="clear">クリア</a></div>
        <div class="caption">検索履歴</div>
    </div>

    <!-- 履歴リスト -->
    <ul></ul>
</div>

id=historyとしてdiv要素を定義します。 ⁠検索履歴」というタイトルと、検索履歴を消去するid=clearのa要素「クリア」を定義します。 また、履歴のリストを保持するul要素を定義します。

検索結果

検索結果を表示する領域ですリスト4⁠。

リスト4 検索結果
<!-- ビデオ -->
<div id="view">
    <!-- ヘッダ -->
    <div class="header">
        <div id="sort" class="submenu">
            並び替え:
            <a id="relevance">関連度</a>|
            <a id="updated">追加日</a>|
            <a id="viewCount">再生回数</a>|
            <a id="rating">評価</a>
        </div>
        <div class="caption">ビデオ</div>
    </div>

    <!-- サムネイル -->
    <div id="videos"></div>

    <!-- フッタ -->
    <div id="footer">
        <div id="result"></div>
        <div id="navi">
            <a id="back">前へ</a> 
            <a id="next">次へ</a>
        </div>
    </div>
</div>

id=viewとしてdiv要素を定義します。 ⁠ビデオ」というタイトルと、並び替えのための4つのa要素を定義します。 サムネイル画像を表示する領域を、id=videosとしてdiv要素を定義します。 ページングを行う「前へ」⁠次へ」リンクとページング情報を表示するフッタを、id=footerとしてdiv要素を定義します。

以上で、画面の定義は終わりです。jQueryを使うとイベントハンドラの定義が簡単に分離できるため、HTMLがとても見やすいですね。

サンプルの機能拡張(2)

初期化処理

jQueryでページ読み込み時の初期化処理を実装するには、次のように定義するのでしたね。

// 初期化
$(function(){
    // --- イベントハンドラ ---
    
    // --- 初期処理 ---

});

今回は、イベントハンドラと初期処理を定義します。

イベントハンドラ

検索

検索を実行した時のイベントを定義しますリスト5⁠。

リスト5 検索イベント
// --- イベントハンドラ ---
// 検索
$("#frmSearch").submit(function(){
    searchNew({"keyword": $("#keyword").val()});
    $("#keyword").select();
    return false;
});

このsubmitイベントハンドラの書き方は第2回で出てきましたね。 入力されたキーワードを取得し引数として、searchNewメソッドを呼び出します。 今回は、キーワードをそのまま引数にせず、オブジェクトリテラルの形で渡しています。

searchNew({"keyword": $("#keyword").val()});

プロパティkeywordに入力されたキーワードを値としてオブジェクトリテラルを生成しています。

オブジェクトリテラルの書式はこれまで見たことないですか?第2回で説明したJSONにそっくりですね。 JSONはJavascriptのオブジェクトリテラルから派生したものなので、とても似ています。詳しくは第2回を参照してください。

もし、オブジェクトリテラルの書式以外で記述するには、たとえば次のように書くことになります。

var obj = new Object();
obj.keyword = $("#keyword").val();
searchNew(obj);

プロパティが増えたら記述が大変そうですね。オブジェクトリテラルの書式を使うとすっきりと記述できるので是非覚えてください。

また、検索後に次の検索キーワードをすぐに入力できるように、select()メソッドを呼び出して、テキストボックスの文字列を選択状態にしています。

$("#keyword").select();

並び替え

並び替えのリンク「関連度」⁠追加日」⁠再生回数」⁠評価」をクリックしたときのイベントを定義しますリスト6⁠。

リスト6 並び替えイベント
// 並び替え
$("#sort > a").click(function(){
    searchHistory({"orderby": this.id, "page":1});
});

4つのリンクのイベントが1つのイベントハンドラで簡潔に定義できてますね。 これがjQueryの面白いところです。 それでは1つずつ解説しましょう。

説明のため、並び替えのリンクのHTMLを抜粋して再掲しますリスト7⁠。各リンクのidの値には、Youtube APIのorderbyパラメータの値を指定しています。

リスト7 並び替えのリンク
<div id="sort" class="submenu">
    並び替え:
    <a id="relevance">関連度</a>|
    <a id="updated">追加日</a>|
    <a id="viewCount">再生回数</a>|
    <a id="rating">評価</a>
</div>

まず、4つのリンクのjQueryオブジェクトを生成します。

$("#sort > a")

このセレクタは、id=sortで定義される要素の子要素であるa要素を選択しています。 つまり、ここでは4つのa要素をあらわすjQueryオブジェクトを生成しています。

このjQueryオブジェクトに対して、click(fn)メソッドを呼び出すと、これらの4つのリンクをクリックした時のイベントを定義することができます。

$("#sort > a").click(function(){
    // クリック時のイベント
});

クリック時には、orderbyプロパティにa要素のid、pageプロパティに1を持つオブジェクトをsearchHistoryメソッドに渡し呼び出しています。

searchHistory({"orderby": this.id, "page":1});

orderbyプロパティの値の記述に出てきたthisとは何でしょうか? この場合はリンクのDOM要素の参照です。jQueryのイベントハンドラのコールバック関数内では、thisはその対象のDOM要素の参照を表します。 たとえば、<a id="relevance">関連度</a>がクリックされればそのリンクのDOM要素への参照になります。

また、$(this)とすれば、そのDOM要素のjQueryオブジェクトを生成することもできます。とてもよく使うので覚えてください。

よって、それぞれの並び替えのリンクをクリックすると、そのDOM要素のidプロパティを取得してオブジェクトを作りsearchHistoryメソッドに渡しています。

イレギュラーな使い方ですが、それぞれのidの値にYoutube APIのorderbyパラメータの値を指定しているので、 実際の検索を行うメソッド内では、どのリンクがクリックされたかを判別することも必要ありません。 idの値をそのままorderbyパラメータの値として使うだけです。

また、今後並べ替えのリンクが増えても、イベントハンドラの実装に手を加える必要がありません。 このようにjQueryを使うと非常にすっきりとした実装ができます。

前へ

「前へ」リンクをクリックしたときのイベントを定義しますリスト8⁠。

リスト8 ⁠前へ」リンククリックイベント
// 前へ
$("#back").click(function(){
    if (searchCond.page return;
    searchHistory({"page": searchCond.page-1});
});

まず、現在のページ数が1以下の場合は前のページがないので、処理を終了しています。ページ数は1から始まる数値としました。

if (searchCond.page return;

ここで出てくるsearchCondオブジェクトは、現在の検索条件を保持するグローバルなスコープのオブジェクトです。 pageプロパティが検索条件のページ数を保持します。 グローバルスコープで次のように、定義しました。

var searchCond = {}; // 検索条件

このように適当にグローバルスコープにオブジェクトを作ったり、メソッドを定義すると、名前空間がどんどん汚れていやな感じですね。 これを回避した実装は次回説明します。

そして、現在の検索条件のページ数から1を減算してsearchHistoryメソッドを呼び出しています。

searchHistory({"page": searchCond.page-1});

次へ

「次へ」リンクをクリックしたときのイベントを定義しますリスト9⁠。

リスト9 ⁠次へ」リンククリックイベント
// 次へ
$("#next").click(function(){
    if (searchCond.page*VIEW_COUNT+1 > searchCond.total) return;
    searchHistory({"page": searchCond.page+1});
});

まず、次ページのビデオのインデックスが検索結果件数よりも多くなる場合は、処理を終了しています。

if (searchCond.page*VIEW_COUNT+1 > searchCond.total) return;

そして、現在の検索条件のページ数から1を減算してsearchHistoryメソッドを呼び出しています。

searchHistory({"page": searchCond.page+1});

VIEW_COUNTは1ページに表示する数です。次のように定義しました。

var VIEW_COUNT = 9;  // 画面に表示する件数

また、searchCondオブジェクトのtotalプロパティは、検索結果件数を保持します。

クリア

検索履歴にある「クリア」リンクをクリックしたときのイベントを定義しますリスト10⁠。

リスト10 ⁠クリア」リンククリックイベント
// クリア
$("#clear").click(function(){
    $("#history > ul").empty();
    $("#videos").empty();
    $("#result").empty();
});

検索履歴を保持するリスト$("#history > ul")ビデオのサムネイルを表示する$("#videos")検索結果件数を表示する$("#result")のそれぞれの要素に対してempty()メソッドを呼び出し、子要素を削除しクリアしています。

初期処理

ページが読み込まれたときに、検索テキストボックスをフォーカスし、すぐに検索キーワードを入力して検索できるようにしましょう。

リスト11 初期処理
// 検索テキストボックスをフォーカス
$("#keyword").focus();

サンプルの機能拡張(3)

新規検索

新規検索時の処理を定義しますリスト12⁠。ここで新規検索とは、検索履歴に存在しないキーワードで検索した場合を指します。

リスト12 新規検索処理
// --- 新規検索 ---
function searchNew(cond) {
    search({
        "keyword": cond.keyword,
        "page": 1,
        "orderby": "relevance",
        "fromHistory": false
    });
}

検索キーワードを表すkeywordプロパティに、引数のオブジェクトのkeywordプロパティを設定しています。 また、新規検索時にはページ数を1、並べ替えを関連度(relevance⁠⁠、fromHistoryフラグ(履歴検索かどうか)をfalseに指定しています。

そして、その検索条件オブジェクトを引数にsearchメソッドを呼び出しています。

履歴検索

履歴検索時の処理を定義しますリスト13⁠。ここで履歴検索とは、検索履歴からの検索、または、検索結果の並べ替えを指します。

リスト13 履歴検索処理
// --- 履歴検索 ---
function searchHistory(cond) {
    if ($("#history li").size() == 0) return;
    search($.extend({}, searchCond, cond, {"fromHistory": true}));
}

まず、検索履歴が無い場合は処理を終了しています。 検索を実行すると、次の履歴リストへ検索内容を保持します。

<div id="history">

    <!-- 履歴リスト -->
    <ul></ul>
</div>

$("#history li")とすることで、履歴リスト内のli要素を走査し、jQueryオブジェクトを生成しています。 size()メソッドは、jQueryオブジェクトのDOM要素数を返すので、返り値が0かどうかを判別すれば履歴が存在するかどうかが分かります。

if ($("#history li").size() == 0) return;

そして、検索条件オブジェクトを生成し、searchメソッドを呼び出しています。

search($.extend({}, searchCond, cond, {"fromHistory": true}));

$.extend()メソッドは、オブジェクトを拡張するjQueryのユーティリティメソッドです。書式は次のようになります。

$.extend(target, object1, [objectN])

targetは拡張されるオブジェクトを指定します。 その後ろには、拡張元のオブジェクトをいくつでも引数に指定できます。 拡張元のオブジェクトに同じプロパティが存在した場合は、右に指定したオブジェクトのプロパティの値で上書きされます。

ここでは、検索条件を保持するsearchCondオブジェクトを元に、引数で与えられた検索条件condと、fromHistoryフラグ(履歴検索かどうか)をtrueに指定して拡張した、新しい検索条件オブジェクトを生成しています。

検索

引数の検索条件オブジェクトを元に、検索を実行する処理を定義しますリスト14⁠。

リスト14 検索処理
// --- 検索 ---
function search(cond) {
    // (1) 検索条件の検査
    if (cond == null) return;
    if (cond.keyword == null || cond.keyword.length == 0) {
        alert("検索キーワードを入力してください。");
        return;
    }

    // (2) 検索取得開始インデックス
    var index = (cond.page-1)*VIEW_COUNT+1;

    // (3) 検索条件の保存
    $.extend(searchCond, cond);

    // (4) サムネイル表示を初期化
    $("#videos").text("Loading...");

    // (5) ajax通信定義
    $.ajax({
        dataType: "jsonp",
        data: {
            "vq": cond.keyword,
            "orderby": cond.orderby,
            "start-index": index,
            "max-results": VIEW_COUNT,
            "alt":"json-in-script"
        },
        cache: true,
        url: "http://gdata.youtube.com/feeds/api/videos",
        success: function (data) {
            // (6) サムネイル表示をクリア
            $("#videos").empty();

            // (7) 検索結果件数を取得・表示
            searchCond.total = parseInt(data.feed.openSearch$totalResults.$t,10);
            showTotal(index, searchCond.total);

            // 検索結果が0件
            if (searchCond.total == 0) {
                alert("検索キーワードにマッチするビデオはありませんでした。");
                return;
            }

            // (8) エントリを参照してサムネイルを生成
            $.each(data.feed.entry, function(i,item){
                var group = item.media$group;

                $("<div/>").addClass("thumbnail")
                    .append($("<img/>").attr("src", group.media$thumbnail[0].url)).append("<br/>")
                    .append(item.title.$t).append("<br/>")
                    .append($("<span/>").addClass("info").text("再生回数:" + ((item.yt$statistics == null) ? "0" : item.yt$statistics.viewCount)))
                    .click(function(){window.open(group.media$player[0].url, null)})
                    .appendTo("#videos");
            });

            // (9) 検索履歴に追加
            if (!cond.fromHistory) {
                addHistory($("#videos img:first").clone(), cond.keyword);
            }
        }
    });
}

基本的な構造は第2回のものと変わりません。よって、jQueryのDOM操作部分を抜粋して説明します。

サムネイルを生成

サムネイルのDOMの構成は次のようになります。

図2 サムネイルのDOMの構成
図2 サムネイルのDOMの構成

対応するjQueryの実装部分を再掲しますリスト15⁠。

リスト15 サムネイルDOM生成
$("<div/>").addClass("thumbnail")  // (1)
    .append($("<img/>").attr("src", group.media$thumbnail[0].url)).append("<br/>")  // (2)
    .append(item.title.$t).append("<br/>")  // (3)
    .append($("<span/>").addClass("info").text("再生回数:" + ((item.yt$statistics == null) ? "0" : item.yt$statistics.viewCount)))  // (4)
    .click(function(){window.open(group.media$player[0].url, null)})  // (5)
    .appendTo("#videos");  // (6)

ソースコードを見ただけで、図2のどの部分を実装しているか一目で分かるのではないでしょうか。 また、この実装部分は、実際は1行です。 とても簡潔に記述できますね。

まず、サムネイルのdiv要素を生成します。class属性にthumbnailを指定します15-(1)⁠。 クラス属性を追加するには、addClass(class)メソッドをクラス名を引数にして呼び出します。

サムネイルのdiv要素にimg要素を追加します15-(2)⁠。最後の子要素として追加するには、append(content)メソッドを追加する要素を引数にして呼び出します。 ここでは、追加するimg要素を次のように生成しています。

$("<img/>").attr("src", group.media$thumbnail[0].url)

attr(key, value)メソッドは、1番目の引数に属性名、2番目の引数に値を指定して呼び出すと、指定した属性に値を設定します。ここではsrc属性に、サムネイル画像のURLが設定されているgroup.media$thumbnail[0].urlを指定しています。

動画のタイトルを追加します15-(3)⁠。ここではTEXTノードとしてタイトルをそのまま追加しています。

動画の再生回数を追加します15-(4)⁠。 ここでは、class属性がinfoのspan要素を生成しています。また、yt$statistics.viewCountを参照し、動画の再生回数を取得しています。

以上で、DOM要素の構築部分は終わりです。とても簡潔ですね。 次の行からはクリック時のイベントと親要素への追加です。

サムネイルをクリックした時のイベントハンドラを定義します15-(5)⁠。 今回は、クリック時に新しいウィンドウを開くようにしました。

window.open(group.media$player[0].url, null)

最後に、このサムネイル要素をid=videosで定義される要素に追加します15-(6)⁠。 appendTo(target)メソッドは、追加先の親要素を引数にして呼び出します。

検索履歴に追加

新規検索時に、検索履歴に検索条件を追加します。 検索の内容が分かりやすいように、検索結果の1番目のサムネイル画像と、検索キーワードを表示します。

// (9) 検索履歴に追加
if (!cond.fromHistory) {
    addHistory($("#videos img:first").clone(), cond.keyword);
}

検索結果の1番目のサムネイル画像を表すjQueryオブジェクトに対してclone()メソッドを呼び出しています。 こうしないと、img要素そのものが検索履歴領域に追加(つまり移動)してしまいます。

サンプルの機能拡張(4)

検索結果件数表示

右下にある検索結果件数表示を実装しますリスト16⁠。 検索結果件数と、表示しているサムネイルのインデックス番号を表示します。

図3 検索結果件数表示部分
図3 検索結果件数表示部分
リスト16 検索結果件数表示処理
// --- 検索結果件数表示 ---
function showTotal(index, total) {
    $("#result").text(
        ((total == 0) ? 0 : index)
        + " - "
        + (index+VIEW_COUNT > total ? total : index+VIEW_COUNT-1)
        + " / "
        + total
        + "件"
    );
}

このメソッドは、ページに表示されている最初のサムネイルのインデックス番号と、検索結果件数を引数に取ります。 ページに表示されている最後のサムネイルのインデックス番号を計算し、表示します。

text(value)メソッドは、対象のDOM要素のテキストノードに引数valueの文字列を設定します。

検索履歴

検索履歴機能を実装します。検索履歴機能を再掲します。

検索履歴機能

  • 検索を実行すると、検索履歴に履歴が残る。
  • 検索キーワードが検索履歴に存在する場合は、その履歴を最上位に移動する。
  • 検索履歴には、検索結果の第1番目のサムネイル画像、検索キーワード、削除リンク「[x]」を表示する。
  • 検索履歴をクリックすると、その検索キーワードで再度検索する。
  • 削除リンク「[x]」をクリックすると、その検索履歴を消去する。
  • 「クリア」をクリックすると、全ての検索履歴を消去する。
図4 検索履歴機能
図4 検索履歴機能

検索履歴追加

検索履歴に履歴を追加する処理を実装しますリスト17⁠。

リスト17 検索履歴追加処理
// --- 検索履歴追加 ---
function addHistory(img, keyword) {
    // (1) 履歴に検索キーワードが存在するか
    var exists = $.grep($("#history li"), function(item, index){
        return ($(item).children(".key").text() == keyword);
    });

    if (exists.length == 0) {    // (2) 存在しない
        $("<li/>")
            .append(img).append("<br/>")
            .append($("<span/>").addClass("key").append(keyword))
            .append(
                $("<a/>").addClass("del").append("[x]")
                .click(function(){
                    $(this).parent().remove();
                    if (searchCond.keyword == keyword) {
                        $("#videos").empty();
                        $("#result").empty();
                    }
                })
            )
            .click(function(){searchHistory({"keyword":keyword, "page":1, "orderby":"relevance"});})
            .prependTo("#history > ul");
    } else {    // (3) 存在する
        $(exists)
            .prependTo($(exists).parent())
            .children("img").attr("src", img.attr("src"));

    }

}

検索履歴の検索

すでに検索履歴に存在する検索キーワードの場合は、その履歴を最上位に移動します。 よって、検索履歴に検索キーワードが存在するかどうかを判別しなければなりません。 次のように検索します。

// (1) 履歴に検索キーワードが存在するか
var exists = $.grep($("#history li"), function(item, index){
    return ($(item).children(".key").text() == keyword);
});

$.grep(arr, fn)は、arrに指定された配列をイテレーションし、コールバック関数fnがtrueを返した要素で構成される配列を返します。 コールバック関数でarrをフィルタリングするようなユーティリティメソッドです。

コールバック関数には2つの引数が与えられます。1番目(ここではitem)は配列の要素です。2番目(ここではindex)は要素のインデックス番号です。

イテレーション対象の配列に、li要素を指定しています。 そのli要素の子要素には、検索キーワードを保持するclass属性がkeyのspan要素があります。

この検索キーワードは次のように取得しています。

まず、li要素のjQueryオブジェクトを生成します。

$(item)

その子要素を選択するために、children(expr)メソッドを呼び出しています。 children(expr)は引数のセレクタにマッチする子要素を返すメソッドです。

$(item).children(".key")

text()メソッドを呼び出して、検索キーワードを取得します。

$(item).children(".key").text()

この履歴にある検索キーワードと、検索時の検索キーワードが一致する場合は、そのli要素を返すようにします。 このコールバック関数がtrueを返せばよいので、次のように記述します。

return ($(item).children(".key").text() == keyword);

この$.grep(arr, fn)の実行結果を保持する変数existsには、検索履歴に検索キーワードが存在した場合、そのli要素を含む配列が返されます。 もし、存在しない場合は、空の配列が返されます。 よって、存在するかどうかを判別するには次のように記述します。

if (exists.length == 0) {    // (2) 存在しない

} else {    // (3) 存在する

}

履歴に存在しない場合

サムネイル画像、検索キーワード、削除リンクを含むli要素を追加します。

まず、li要素を生成します。

$("<li/>")

サムネイルの画像を追加します。この要素は引数imgとして渡されるのでそのまま追加します。

.append(img).append("<br/>")

検索キーワードを追加します。class属性がkeyのspan要素を追加します。検索キーワードは引数keywordとして渡されます。

.append($("<span/>").addClass("key").append(keyword))

削除リンクを生成します。削除リンクは、class属性がdelのa要素です。[x]という文字にします。

$("<a/>").addClass("del").append("[x]")

削除リンクをクリックしたときのイベントハンドラを定義します。 クリックすると、その要素が属するli要素を削除します。

$(this).parent().remove();

イベントハンドラのコールバック関数内では、イベント対象のDOMオブジェクトはthisで参照できるのでしたね。このDOM要素のjQueryオブジェクトを生成します。 親要素を返すメソッドparent()を呼び出し、要素が属するli要素を取得します。 そして、remove()メソッドを呼び出して、その要素を削除します。

また、現在表示されている検索結果が、この削除した検索履歴のものであれば、サムネイル表示領域と検索結果件数表示領域をクリアしなければなりません。 よって、次のように記述します。

if (searchCond.keyword == keyword) {
    $("#videos").empty();
    $("#result").empty();
}

これで、削除リンクの実装は終わりです。

次に、この検索履歴をクリックした場合、その検索キーワードで再度検索するようにしましょう。 クリックイベントハンドラを次のように定義します。

.click(function(){searchHistory({"keyword":keyword, "page":1, "orderby":"relevance"});})

検索キーワードを表すkeywordプロパティに、引数のkeywordを設定しています。 また、履歴検索時にはページ数を1、並べ替えを関連度(relevance)に指定しています。 そして、その検索条件オブジェクトを引数にsearchHistoryメソッドを呼び出しています。

最後に、このli要素を、id=historyで定義される要素の子要素ul要素に追加します。

.prependTo("#history > ul");

履歴に存在する場合

そのli要素を履歴の先頭に移動し、画像を入れ替えます。次のように実装します。

$(exists)
    .prependTo($(exists).parent())
    .children("img").attr("src", img.attr("src"));

まず、li要素を、prependTo(target)メソッドを呼び出して、履歴の先頭に移動します。 この場合の移動先は親要素(変わらない)ので、$(exists).parent()として親要素を取得します。

.prependTo($(exists).parent())

最後に履歴の画像を、最新の検索結果の画像で置き換えます。

.children("img").attr("src", img.attr("src"))

以上で全ての実装が終了しました。サンプルを実行して動作を確認してみてください。

まとめ

今回はYoutube APIを少し詳しく解説しました。 YouTube Data APIを使ったアプリケーションの開発をする際に参考にしてください。

サンプルの機能を拡張して、jQueryの使い方をより詳しく説明しました。 今回は、数多くのjQueryメソッドが出てきました。 また、jQueryを使った基本的な実装方法も紹介することができました。 jQueryをはじめて使う方は是非参考にしてください。

おすすめ記事

記事・ニュース一覧