先取り! Google Chrome Extensions

第2回 Chrome Extensionsの作り方#1

この記事を読むのに必要な時間:およそ 8 分

実用的なExtensionの作成

ここまででExtensionの開発に必要な知識は一通り学びました。ここで復習も兼ねて,少し実用的なExtensionを1つ作成してみます。

どういったExtensionが実用的であるかは悩ましいところですが,手軽さとの兼ね合いからソーシャルブックマークサービスでのブックマーク数を表示するExtensionを作成してみます。

FirefoxのAdd-onではSBMカウンタなどがあります。また,すでにChrome Extensionとして,Google Chrome 拡張(Chrome Extension) はてなブックマークのエントリー数を表示する Chrome拡張を作った - 忘れないようにメモBig Sky :: 被はてなブックマーク数を表示するGoogle Chrome拡張書いた。など幾つかの実装例があります。今回はこれらの実装をよりExtensionの特徴を生かして実装しつつ,はてなブックマークに加えてdeliciousにも対応してみます。

ToolstripのHTML/CSS

まずは,Toolstripによるインターフェース部分を作成します。ブックマーク数の表示のほか,ブックマーク詳細ページへのリンクとブックマーク追加画面へのリンクを設置することにします。

ToolstripのHTML(一部)

<ul id="sbmlist">
  <li class="add">
    <a id="add_hatena" target="_blank" title="はてなブックマークに追加">
      <img src="hatena.favicon.gif" id="icon_hatena">
    </a>
  </li>
  <li class="counter">
    <a id="text_hatena" target="_blank"> </a>
  </li>
  <li class="add">
    <a id="add_delicious" target="_blank" title="Save this bookmark">
      <img src="delicious.small.gif" id="icon_delicious">
    </a>
  </li>
  <li class="counter">
    <a id="text_delicious" target="_blank"> </a>
  </li>
</ul>

ToolstripのCSS(一部)

#sbmlist{
  display:table;
  margin:5px 3px;
  list-style-type:none;
  height:18px;
  min-width:8em;
}
#sbmlist li{
  display:table-cell;
  vertical-align:middle;
}
#sbmlist li.counter{
  min-width:2.5em;
  text-align:center;
  padding:0 3px 0 0;
}

HTMLはulとliのリストで構成し,CSSで display:table;,display:table-cell; を指定することで(少々強引ですが)レイアウトを調整しました。Chrome(WebKit)で正常にされれば良いので,-webkit系の拡張スタイルも使用できます。角丸なども -webkit-border-radius で実現できます。

APIを処理するJavaScriptの実装

続いて,JavaScript部分を実装します。はてなブックマークもdeliciousもブックマーク数を返すAPIが用意されていますので,クロスドメイン通信でブックマーク数を取得することにします。

ページのURLの取得⇒リクエスト⇒アップデート処理と,これらはクラス(ライク)にまとめると見通しがよくなりそうなので,SBMというクラスを作成してみます。

SBMクラスの実装

function SBM(service){
  this.text = document.getElementById('text_' + service.id);
  this.icon = document.getElementById('icon_' + service.id);
  this.add = document.getElementById('add_' + service.id);
  this.api_get = service.api_get;
  this.api_link = service.api_link;
  this.api_add = service.api_add;
  this.responceFilter = service.responceFilter;
  this._cache = {};
}
SBM.prototype = {
  request:function _sbm_request(only_request){
    var self = this, xhr = new XMLHttpRequest();
    var api_url = this.replace(this.api_get);
    xhr.open('GET', api_url, true);
    xhr.onload = function(){
      var count = self.responceFilter(xhr.responseText);
      self._cache[self.url] = {count:count};
      if (!only_request) self.update(count);
    };
    xhr.send();
  },
  set:function _sbm_set(url, force_request, only_request){
    if (!only_request) {
      this.text.textContent = '-';
    }
    this.url = url;
    this.encoded_url = encodeURIComponent(url);
    if ((force_request || only_request) || !this._cache[url]) {
      this.request(only_request);
    } else {
      this.update(this._cache[url].count);
    }
  },
  update:function _sbm_update(count){
    this.text.textContent = count;
    this.text.href = this.replace(this.api_link);
    this.add.href = this.replace(this.api_add);
  },
  replace:function _sbm_replace(str){
    var self = this;
    return str.replace(/#\{([^}]+)\}/g, function(_, _$){
      return self[_$] || '';
    });
  }
};

まずsetメソッドでURLを設定し,すでにキャッシュがあればupdateのみ,キャッシュしてなければrequestを行います。タブの切り替え時はキャッシュを使用して,リロードなどの更新時は改めて取得を行うことができるようにリクエストを強制するオプションを,またバックグラウンドで開いた際に先にキャッシュしておけるように,キャッシュを行うだけのオプションを実装しました。

APIのレスポンスの形式は各サービスごとに異なるので,responceFilterというメソッドで処理を行います。responceFilterはServicesというオブジェクトで定義しました。またServicesは各APIのURLなどサービス固有の部分を定義しています。

Servicesの定義

var Services = [
  {
    id:'hatena',
    api_get:'http://b.hatena.ne.jp/entry.count?url=#{encoded_url}',
    api_link:'http://b.hatena.ne.jp/entry/#{url}',
    api_add:'http://b.hatena.ne.jp/add?&url=#{encoded_url}',
    responceFilter:function(text){
      if (/\D/.test(text)) {
        return '';
      } else {
        return text;
      }
    }
  },
  {
    id:'delicious',
    api_get:'http://badges.del.icio.us/feeds/json/url/blogbadge?url=#{encoded_url}',
    api_link:'http://delicious.com/url/#{hash}',
    api_add:'http://delicious.com/save?v=5&jump=close&url=#{encoded_url}',
    responceFilter:function(text){
      try {
        var r = JSON.parse(text);
        if (r.length === 0) {
          return '';
        }
        this.hash = r[0].hash;
        return r[0].total_posts;
      } catch (e) {
        console.error(e);
        return '';
      }
    }
  }
];
var services = Services.map(function(service, i){
  return new SBM(service);
});

ChromeではJSON.parse,JSON.stringifyが実装されていますので,JSONを扱う際は安全のためevalではなくJSON.parseを使用するべきです。

ここまでは一般的なJavaScriptの実装です。ここからは本題であるExtensions APIの使い方を見ていきます。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/