はてブfooterの作成(with JSONP) http://www.ark-web.jp/sandbox/wiki/147.html
目次 †
はじめに †
ARK-Web SandboxWikiではフッタ情報を表現するために、PukiWikiのblikifooterプラグインを利用して
#blikifooter(進地);
とコマンドを記述することで
投稿者 進地|パーマリンク|trackback(10)|comment(5)
のように投稿者、Permalink、Trackback、コメント数を表示していましたが、追加ではてブへの登録ボタン、はてブ数カウント、はてブ詳細情報を表示するためにblikifooterプラグインをカスタマイズしました。(追加後のイメージは下の画像の通り)
特にはてブ数カウントとはてブ詳細情報の取得/表示にはJSONP(JSON with Padding)を利用できるはてなブックマークエントリー情報取得APIを使用して取得しています。
また、blikifooterと切り離して、はてブ登録ボタン、はてブ数カウント、はてブ詳細情報の表示機能のみを持たせたPukiWikiのhatebufooterプラグインを作成しました。
以下、このエントリーでは
- はてブfooterの外部仕様
(※ここではhatebufooterプラグインを説明に用います。blikifooterに組み込んだカスタマイズは原則hatebufooterと変わりはありません。両者を総称してここでははてブfooterと表記しています) - ソース解説
- JSONP利用部の解説
- JSONscriptRequestクラスを使ったJSONPコールバック関数の実行
- JSONPコールバック関数の定義
を行います。
#blikimore;
はてブfooterの外部仕様 †
はてブfooterの外部仕様となる機能は下記になります。
- はてブ登録ボタンの表示
- はてブ数カウントの表示
- はてブ詳細情報の表示
はてブ登録ボタンは[B!]のボタンで、クリックすると当該Wikiページをはてブ登録できます。
はてブ数カウントの表示と、はてブ詳細情報の表示は、当該Wikiページが1件以上はてブされている場合にのみ有効になります。
今、n(n=1〜N)人によって当該Wikiページがはてブされているとすると、
はてブ数カウントは
このエントリーをブックマークしているユーザー( n users )
と表示され、
はてブ詳細情報ははてブ数カウントに続いて
<div> ユーザ1のはてブ登録日時(yyyy/mm/dd hh:mm:ss) <a href="ユーザ1のHatena::BookmarkのURL">ユーザ1</a><br/> - ユーザ1のコメント(ユーザ1のコメントがもしあれば表示される) </div> <div> ユーザ2のはてブ登録日時(yyyy/mm/dd hh:mm:ss) <a href="ユーザ2のHatena::BookmarkのURL">ユーザ2</a><br/> - ユーザ2のコメント(ユーザ2のコメントがもしあれば表示される) </div> : : <div> ユーザNのはてブ登録日時(yyyy/mm/dd hh:mm:ss) <a href="ユーザNのHatena::BookmarkのURL">ユーザN</a><br/> - ユーザNのコメント(ユーザNのコメントがもしあれば表示される) </div>
という構造で生成、表示されます。
hatebufooterは
#hatebufooter();
と表記することで当該ページの当該箇所で実行されます。
ソース解説 †
はてブfooterのメイン部のソースは下記の通りです(定数部の宣言等は載せていません)
01: function plugin_hatebufooter_convert() { 02: 03: global $script, $vars; 04: 05: $args = func_get_args(); 06: $retval = ""; 07: 08: if ( empty($args[1]) || ( ( !empty($args[1]) ) && $args[1] == true ) ) { 09: 10: $permalink = $script . '?' . urlencode($vars['page']); 11: $permalink4bookmark = urlencode($permalink); 12: 13: // 「このエントリをはてなブックマークに登録」ボタンの表示 14: $retval .= sprintf('<a href="http://b.hatena.ne.jp/append?"><img alt="append.gif" src="" alt="このエントリをはてなブックマークに登録" width="" height="" border="0" /></a>', $permalink4bookmark, HATEBUFOOTER_HATENA_BOOKMARK_APPEND_IMG_URL, HATEBUFOOTER_HATENA_BOOKMARK_APPEND_IMG_WIDTH, HATEBUFOOTER_HATENA_BOOKMARK_APPEND_IMG_HEIGHT); 15: 16: // はてブ数カウントとブックマークしているユーザの詳細表示(JSONPを利用) 17: $md5_page = md5($vars['page']); 18: $retval .=<<< HATENA_BOOKMARK_COUNT 19: <script type="text/javascript" src="./js/jsr_class.js"></script> 20: <div id="hatena_bookmark_count_detail_$md5_page"> 21: </div> 22: <script type="text/javascript"> 23: var hatena_jsonp_base = 'http://b.hatena.ne.jp/entry/json/'; 24: var jsr = new JSONscriptRequest(hatena_jsonp_base + '?url=$permalink4bookmark&callback=hatena_footer_hundler_$md5_page'); 25: jsr.buildScriptTag(); 26: jsr.addScriptTag(); 27: 28: function hatena_footer_hundler_$md5_page(data) { 29: 30: if (data != null && data.count > 0) { 31: var parent_div = document.getElementById('hatena_bookmark_count_detail_$md5_page'); 32: 33: // カウンタ画像を取得 34: var counter_a = document.createElement('a'); 35: counter_a.setAttribute('href', 'http://b.hatena.ne.jp/entry/' + '$permalink'); 36: var counter_img = document.createElement('img'); 37: counter_img.setAttribute('src', 'http://b.hatena.ne.jp/entry/image/' + '$permalink'); 38: counter_a.appendChild(counter_img); 39: 40: // このエントリーをブックマークしているユーザー(xx users) 41: // のフォーマットでfooterの下に表示する 42: parent_div.appendChild(document.createTextNode('このエントリーをブックマークしているユーザー(')); 43: parent_div.appendChild(counter_a); 44: parent_div.appendChild(document.createTextNode(')')); 45: 46: // 各ブックマークの詳細をリスト表示させる 47: var bookmarks = data.bookmarks; 48: for (var i=0, bookmark; bookmark = bookmarks[i]; i++) { 49: 50: var user_div = document.createElement('div'); 51: 52: // ブックマークタイムスタンプの表示 53: user_div.appendChild(document.createTextNode(bookmark.timestamp + ' ')); 54: 55: // ブックマークしたユーザの表示 56: var user_a = document.createElement('a'); 57: // yyyy/mm/dd hh:mm:ssからyyyymmdd文字列を作成する 58: var user_a_timestamp = bookmark.timestamp.substring(0, bookmark.timestamp.indexOf(' ', 0)); 59: while (true) { 60: var tmp_timestamp = user_a_timestamp; 61: user_a_timestamp = tmp_timestamp.replace('/', ''); 62: if (user_a_timestamp == tmp_timestamp) { 63: break; 64: } 65: } 66: user_a.setAttribute('href', 'http://b.hatena.ne.jp/' + bookmark.user + '/' + user_a_timestamp + '#bookmark-' + data.eid); 67: user_a.appendChild(document.createTextNode(bookmark.user)); 68: user_div.appendChild(user_a); 69: 70: // コメントの表示 71: if ( bookmark.comment != '' ) { 72: user_div.appendChild(document.createElement('br')); 73: user_div.appendChild(document.createTextNode(' - ' + bookmark.comment)); 74: } 75: 76: parent_div.appendChild(user_div); 77: } 78: } 79: jsr.removeScriptTag(); 80: } 81: </script> 82: HATENA_BOOKMARK_COUNT; 83: } 84: 85: return sprintf(HATEBUFOOTER_TEMPLATE, $retval); 86: }
簡単に解説します。
- 10,11行目
- はてなが提供する自分のブログに被ブックマーク数を表示する機能、およびはてなブックマークエントリー情報取得APIを利用する為に、当該WikiページのPermalinkを生成しています(両機能、APIは対象ページのPermalinkを引数として要求するため)。はてなブックマークエントリー情報取得APIは文字%もurlencodeされたURLを要求するため、Permalinkを2回urlencode()にかけています。
- 13〜15行目
- はてブ登録ボタンのタグを生成しています。はてブ登録機能は
http://b.hatena.ne.jp/append?$permalink4bookmark(←Permalinkを2回urlencode()にかけたURL)
にリンクすることで実現されます。
- 16〜83行目
- JSONPを利用して当該Wikiページに対して1件以上はてブがされていた場合にはてブ数カウント表示と詳細情報の表示を行います。JSONP利用部の解説に詳細を記述します。
- 85行目
- 生成したタグ(=はてブfooterの出力)を返します。
JSONP利用部の解説 †
ソース解説の16〜83行目は下記のHTMLタグとJavaScriptコードを生成して返す為のロジックになっています。$md5_pageは実際には当該Wikiページのページ名をPHPのmd5()関数でハッシュ化した値が指定されます。これはトップページなどで複数Wikiページが並んで表示される場合に、はてブfooterによって各Wikiページ単位に出力されるdivのidやJSONPコールバック関数名がバッティングしないようにするための仕組みです。
<script type="text/javascript" src="./js/jsr_class.js"></script> <div id="hatena_bookmark_count_detail_$md5_page"> </div> <script type="text/javascript"> var hatena_jsonp_base = 'http://b.hatena.ne.jp/entry/json/'; var jsr = new JSONscriptRequest(hatena_jsonp_base + '?url=$permalink4bookmark&callback=hatena_footer_hundler_$md5_page'); jsr.buildScriptTag(); jsr.addScriptTag(); function hatena_footer_hundler_$md5_page(data) { if (data != null && data.count > 0) { var parent_div = document.getElementById('hatena_bookmark_count_detail_$md5_page'); // カウンタ画像を取得 var counter_a = document.createElement('a'); counter_a.setAttribute('href', 'http://b.hatena.ne.jp/entry/' + '$permalink'); var counter_img = document.createElement('img'); counter_img.setAttribute('src', 'http://b.hatena.ne.jp/entry/image/' + '$permalink'); counter_a.appendChild(counter_img); // このエントリーをブックマークしているユーザー(xx users) // のフォーマットでfooterの下に表示する parent_div.appendChild(document.createTextNode('このエントリーをブックマークしているユーザー(')); parent_div.appendChild(counter_a); parent_div.appendChild(document.createTextNode(')')); // 各ブックマークの詳細をリスト表示させる var bookmarks = data.bookmarks; for (var i=0, bookmark; bookmark = bookmarks[i]; i++) { var user_div = document.createElement('div'); // ブックマークタイムスタンプの表示 user_div.appendChild(document.createTextNode(bookmark.timestamp + ' ')); // ブックマークしたユーザの表示 var user_a = document.createElement('a'); // yyyy/mm/dd hh:mm:ssからyyyymmdd文字列を作成する var user_a_timestamp = bookmark.timestamp.substring(0, bookmark.timestamp.indexOf(' ', 0)); while (true) { var tmp_timestamp = user_a_timestamp; user_a_timestamp = tmp_timestamp.replace('/', ''); if (user_a_timestamp == tmp_timestamp) { break; } } user_a.setAttribute('href', 'http://b.hatena.ne.jp/' + bookmark.user + '/' + user_a_timestamp + '#bookmark-' + data.eid); user_a.appendChild(document.createTextNode(bookmark.user)); user_div.appendChild(user_a); // コメントの表示 if ( bookmark.comment != '' ) { user_div.appendChild(document.createElement('br')); user_div.appendChild(document.createTextNode(' - ' + bookmark.comment)); } parent_div.appendChild(user_div); } } jsr.removeScriptTag(); } </script>
構造は大きく分けて2つです。
- JSONscriptRequestクラスを使ったJSONPコールバック関数の実行
- JSONPコールバック関数の定義
JSONscriptRequestクラスを使ったJSONPコールバック関数の実行 †
JSONPはフォーマットとしてはJSON(JavaScript Object Notation)フォーマットのデータ(テキスト)をコールバック関数名で括ったものにすぎません。例えば、
"bindings": [ {"ircEvent": "PRIVMSG", "method": "newURI", "regex": "^http://.*"}, {"ircEvent": "PRIVMSG", "method": "deleteURI", "regex": "^delete.*"}, {"ircEvent": "PRIVMSG", "method": "randomURI", "regex": "^random.*"} ]
のようなJSONデータをコールバック関数名をhundlerでJSONP化すると
hundler("bindings": [ {"ircEvent": "PRIVMSG", "method": "newURI", "regex": "^http://.*"}, {"ircEvent": "PRIVMSG", "method": "deleteURI", "regex": "^delete.*"}, {"ircEvent": "PRIVMSG", "method": "randomURI", "regex": "^random.*"} ] );
となります。
はてなが提供するはてなブックマークエントリー情報取得APIはJSON形式ではてブ情報を返してくれます。そのフォーマットは下記の例になります。
{ "count":"14", "url":"http://www.ark-web.jp/", "bookmarks":[ { "timestamp":"2006/09/07 01:35:08", "comment":" \u4e2d\u91ce\u5b97\u3000webSig24/7", "user":"pongpongland", "tags":["web","design","contents","dh","TVBlog"] }, { "timestamp":"2006/08/10 15:35:29", "comment":"", "user":"keepintouch", "tags":["web\u5236\u4f5c"] }, { : : }, : ], "title":"Web\u5236\u4f5c(\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u4f5c\u6210) \u30a2\u30fc\u30af\u30a6\u30a7\u30d6:Web\u30c7\u30b6\u30a4\u30f3(HTML,Flash)\u30fb\u30b7\u30b9\u30c6\u30e0\u958b\u767a\u30fbZen Cart\u5c0e\u5165:\u6771\u4eac\u90fd\u4e2d\u592e\u533a\u9280\u5ea7", "eid":"1047377", "entry_url":"http://b.hatena.ne.jp/entry/http://www.ark-web.jp/", "screenshot":"http://screenshot.hatena.ne.jp/images/120x90/d/f/5/2/0/571e29202efd8abbf1fd91db361d92470a7.jpg" }
また、引数でコールバック関数名を指定することでJSONP化したテキストが返されます。仮にこの例でコールバック関数名をhundlerとすると
hundler( { "count":"14", "url":"http://www.ark-web.jp/", "bookmarks":[ { "timestamp":"2006/09/07 01:35:08", "comment":" \u4e2d\u91ce\u5b97\u3000webSig24/7", "user":"pongpongland", "tags":["web","design","contents","dh","TVBlog"] }, { "timestamp":"2006/08/10 15:35:29", "comment":"", "user":"keepintouch", "tags":["web\u5236\u4f5c"] }, { : : }, : ], "title":"Web\u5236\u4f5c(\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u4f5c\u6210) \u30a2\u30fc\u30af\u30a6\u30a7\u30d6:Web\u30c7\u30b6\u30a4\u30f3(HTML,Flash)\u30fb\u30b7\u30b9\u30c6\u30e0\u958b\u767a\u30fbZen Cart\u5c0e\u5165:\u6771\u4eac\u90fd\u4e2d\u592e\u533a\u9280\u5ea7", "eid":"1047377", "entry_url":"http://b.hatena.ne.jp/entry/http://www.ark-web.jp/", "screenshot":"http://screenshot.hatena.ne.jp/images/120x90/d/f/5/2/0/571e29202efd8abbf1fd91db361d92470a7.jpg" } );
が返されます。
JSONPはそのままでは単にテキストに過ぎないため、コールバック関数を起動してJSONデータをコールバック関数に渡す仕組みが必要です。この仕事をするために、Jason Levitt氏が作成、公開されているJSONscriptRequestクラスを利用しています。
その箇所が
<script type="text/javascript" src="./js/jsr_class.js"></script> <div id="hatena_bookmark_count_detail_$md5_page"> </div> <script type="text/javascript"> var hatena_jsonp_base = 'http://b.hatena.ne.jp/entry/json/'; var jsr = new JSONscriptRequest(hatena_jsonp_base + '?url=$permalink4bookmark&callback=hatena_footer_hundler_$md5_page'); jsr.buildScriptTag(); jsr.addScriptTag();
になります。
jsr_class.jsにJSONscriptRequestクラスは定義されているので、これをまずロードします。次に、JSONscriptRequestオブジェクトをはてなブックマークエントリー情報取得APIへのリクエストURLを引数にして作成します。引数に与えたURLのcallbackパラメータに指定した値がコールバック関数名になります。JSONscriptRequestオブジェクト(jsr)はbuildScriptTag()でコールバック関数を実行するscriptタグを生成し、addScriptTag()でページのhead内にそのscriptタグを埋め込みます。
以上によって、はてなブックマークエントリー情報取得APIが返すJSONデータを指定したコールバック関数で受けて処理することが可能になります。
JSONPコールバック関数の定義 †
コールバック関数の引数にははてなブックマークエントリー情報取得APIが返すJSONデータが格納されます。先の例で言えば、
{ "count":"14", "url":"http://www.ark-web.jp/", "bookmarks":[ { "timestamp":"2006/09/07 01:35:08", "comment":" \u4e2d\u91ce\u5b97\u3000webSig24/7", "user":"pongpongland", "tags":["web","design","contents","dh","TVBlog"] }, { "timestamp":"2006/08/10 15:35:29", "comment":"", "user":"keepintouch", "tags":["web\u5236\u4f5c"] }, { : : }, : ], "title":"Web\u5236\u4f5c(\u30db\u30fc\u30e0\u30da\u30fc\u30b8\u4f5c\u6210) \u30a2\u30fc\u30af\u30a6\u30a7\u30d6:Web\u30c7\u30b6\u30a4\u30f3(HTML,Flash)\u30fb\u30b7\u30b9\u30c6\u30e0\u958b\u767a\u30fbZen Cart\u5c0e\u5165:\u6771\u4eac\u90fd\u4e2d\u592e\u533a\u9280\u5ea7", "eid":"1047377", "entry_url":"http://b.hatena.ne.jp/entry/http://www.ark-web.jp/", "screenshot":"http://screenshot.hatena.ne.jp/images/120x90/d/f/5/2/0/571e29202efd8abbf1fd91db361d92470a7.jpg" }
が格納されることになります。例では引数名dataで受けているので、例えば、当該Wikiページに対するはてブ数を取得したい場合はdata.countで取得できます。
コールバック関数の冒頭では
function hatena_footer_hundler_$md5_page(data) { if (data != null && data.count > 0) {
によって、1件以上ブックマークされている場合にのみ後の処理を継続しています。
はてブ数カウント画像の取得はhttp://b.hatena.ne.jp/entry/image/$permalinkで取得しています。
はてブ詳細情報はまず、
// 各ブックマークの詳細をリスト表示させる var bookmarks = data.bookmarks;
でbookmarksにハッシュの配列を格納した上で、
for (var i=0, bookmark; bookmark = bookmarks[i]; i++) {
で配列の各要素(=各ブックマーク単位の情報)を処理しています。この時点でbookmarkには
{ "timestamp":"2006/09/07 01:35:08", "comment":" \u4e2d\u91ce\u5b97\u3000webSig24/7", "user":"pongpongland", "tags":["web","design","contents","dh","TVBlog"] }
のデータ構造が格納されているので、当該ブックマークのタイムスタンプが欲しければbookmark.timestamp、コメントが欲しければbookmark.commentで取得できます。
後は適当に整形して親のdivにappendChild()しているだけです。
コールバック関数内で最後に実行されるjsr.removeScriptTag()は、addScriptTag()で埋め込んだコールバック関数起動用スクリプトを取り除く後始末用メソッドです。
結局、JSONPで何が嬉しいのか? †
JavaScriptにおいてはJSONはXMLに比べて非常に簡易に操作できます。Ajax構築の際にJSONをデータ形式に選択できればとても嬉しいわけです。しかし、クロスドメインの制約から他ドメインのJSONデータをXMLHttpRequestでは読み込めません。そこで、JavaScriptがJSONPをJavaScriptソースとして丸ごとインクルードできることを利用して、他ドメインのJSONデータを読み込めるようにする仕組みとしてJSONPを利用します。JSONPを用いればサーバ側に対象データを取得してJSON形式に変換するスクリプトを置く必要もありません。
まとめると、JSONPで嬉しいのは他ドメインのJSONデータをJavaScriptのみで取得、操作できること、となります。
ですから、JSONPを吐くサービスが提供されれば、後はクライアントのJavaScriptが提供されるデータを様々に組み合わせることができるのです。しかも、JSON形式なので操作も簡単というわけです。
hatebufooterプラグイン ダウンロード †
- Ver 1.0 (Pukiwiki 1.4.6対応)
hatebufooter.inc.php
インストール †
- jsr_class.jsをhttp://www.xml.com/pub/a/2005/12/21/json-dynamic-script-tag.htmlからダウンロードして、PukiWiki直下のjsディレクトリに配置します
- hatebufooter.inc.phpをPukiWiki直下のpluginディレクトリに配置します
- hatebufooter.inc.phpのHATEBUFOOTER_HATENA_BOOKMARK_APPEND_IMG_URLにはてブ登録ボタン画像のURLを指定します