初めてChrome Extention作ってみました。
https://github.com/NewGyu/redmine_tickets_printable
そもそも論的なところはこんないいまとめ
- http://developer.chrome.com/extensions/overview.html
- http://dev.screw-axis.com/doc/chrome_extensions/guide/
があるので割愛して、自分が詰まったところをメモ代わりに書いておきます。
ざっくり要件
今回作ったのはRedmineのチケット一覧を付箋みたいにブロックを並べた見た目にするものです。
Redmineのチケット一覧はリスト形式で、それをA4印刷して貼りだすと小さくて読めないので大きく、付箋っぽくしようというものです。
そして、今回はブラウザアクションではなくページアクション(該当ページの場合にオムニボックスにアイコンが表示される奴)で、
というものです。
オムニボックス(アドレスバー)にアイコンが出ない
まず最初に詰まったのがアイコンが出ないんですね。
"page_action": {
"default_icon" : "icon/unko16.png"
,"default_title": "印刷用ページ出す"
},
こう書いとけば出るんだろうくらいに思ってましたが、実際には明示的にchrome.pageAction.showする必要がありました。
chrome.pageAction.showはbackgroundからじゃないと呼べない
どうもcontent scriptを使うのが常套手段のようです。content scriptは懐かしいGreaseMonkeyみたいに、ページにリアクションするやつですね。
ところが、chrome.pageAction.showはcontent scriptからはコールできないようです。そのため、次のような周りくどいことをする必要がありました。
1. まずバックグラウンドにメッセージを送る
chrome.runtime.sendMessage({issues: issues}, function(response) {});
今回はレスポンスを受けるcallbackに意味はありません。書かなきゃいけないから書いただけですね。
2. バックグラウンドでメッセージを受けてそこでchrome.pageAction.showする
onMessageのリスナーにリアクションするcallbackを登録し、その中でやります。
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
chrome.pageAction.show(sender.tab.id);
sendResponse({tabid:sender.tab.id});
});
今回はレスポンスに意味はありません。書かなきゃまずいのかと思って書いただけです。
pageAction.showがtabidを要求しているのですが、それは第二引数のsenderから取れるみたいです。
実はここ結構悩みました。
アイコンをクリックした時の新しいTabの作成ができない
さてさて、これでアイコンが出るようになったので次はアイコンをクリックした時にNew Tabを開きたいところです。これはchrome.tabs.createを呼べばいいようですね。アイコンがクリックされた時のハンドリングはバックグラウンドでpageAction.onClickedで。
chrome.pageAction.onClicked.addListener(function(tab){
chrome.tabs.create({url:"printable.html?tabid=" + tab.id},function(tab){
});
});
ん・・・が、出ない。
上記コードは最終的なもので動くのですが、この時点ではくだらないミスがあってcreateをcallする以前に実行時エラーでコケていたのです。
バックグラウンドのconsole.logが出ない
…が、ChromeのDevToolのconsoleにログがでなくてそこがわかりませんでした。
バックグラウンドのログは別のところに出ます。chrome://extensions/ のページからバックグラウンドページのデバッガを起動して、
そこのコンソールで確認する必要があります。
tab create したページに元ページの情報を渡せない
これでようやくウン○をクリックするとNew Tabが開くようになりました。
あとは元となるRedmineのチケット一覧ページから必要なチケット情報をつまんできて並べるだけなのでjQueryでやればいいやと思っていたのですが…New Tabのprintable.htmlから元となるRedmineのチケット一覧ページのElementsをどうやって持ってきたらいいの…? ここにイチバン悩んだのでした。
悩んだ末に結局とった方法は「バックグラウンドに一旦覚えさせる」でした。
もっといい方法があるような気もするのでここは教えていただけると助かります、というところです。
1. content script から runtime.sendMessageに送りつける
バックグラウンドに情報を渡すこと自体は、runtime.sendMessageで送れます。
が、しかし、渡せるものに制限があります。jQueryオブジェクトをドカーンと渡したりはできません。そこでほしい情報をドキュメントのままではなく一旦json化しました。(下記例のissuesです)
var issues = $("table.issues tbody>tr.issue").map(function() {
var td = $(this).find("td");
return {
id: td.filter(".id").text()
,tracker: td.filter(".tracker").text()
,project: td.filter(".project").text()
,subject: td.filter(".subject").text()
,requester: td.filter(".cf_57").text()
,client: td.filter(".cf_80").text()
,storyPoint: td.filter(".cf_64").text()
,hours: td.filter(".estimated_hours").text()
};
}).get();
chrome.runtime.sendMessage({issues: issues}, function(response) {
console.log("tabid="+response.tabid);
});
本当はこのjsonへの変換をウ○コをクリックした時にしたかったのですが、バックグラウンドからcontentへのアクセス方法がよくわからず。。Tabオブジェクトからたどれるんですかね?
2. バックグラウンドで大域変数に保存する
なーんかいまいちですが。localStrageに入れるのでもいいんでしょうね。
var issues = {};
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
chrome.pageAction.show(sender.tab.id);
issues[sender.tab.id] = request.issues;
sendResponse({tabid:sender.tab.id});
});
複数TABから同時使用の想定はないのですが、念のためtabId別に領域を確保しています。
3. printable.html からバックグラウンドの大域変数にアクセス
なんだかprintable.htmlに直接スクリプトを書くとエラーになったので実際には外部JSファイルです。
var template = $("#tmpl").html();
var bk = chrome.extension.getBackgroundPage();
var tabid= location.search.match(/tabid=([^&]+)/)[1];
document.write(Mustache.to_html(template, {issues: bk.issues[tabid]}));
テンプレートエンジンMustacheのコードが入っているので本質が見えづらいですが、キモはvar bk = chrome.extension.getBackgroundPage();
と、bk.issues
のところです。
というわけで大したことやってないけどいろいろ躓いたのでした。