jQueryを使ったブックマークレットの作成方法

ユーザが閲覧しているWebページに対してなんらかのアクションをとらせたい場合、[ブックマークレット](http://ja.wikipedia.org/wiki/%E3%83%96%E3%83%83%E3%82%AF%E3%83%9E%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88)の提供が選択肢になります。たとえば、[Catchboxでもブックマークレットを提供しています](http://playcatchbox.com/guide/add)。

ブックマークレットは、JavaScriptのコードをブックマークバーに追加することで実装しますが、実現したい処理自体をブックマークとして登録させてしまうと、アップデートを行うことができなくなります。

そこで、サーバサイドに処理内容を記述したJavaScriptファイルを配置し、そのJavaScriptファイルを、閲覧しているWebページのbody部の最後に追加する処理を、ブックマークレットとしてユーザに登録させます。

これは、ユーザがブックマークバーに追加する処理です。（分かりやすいよう改行とコメントを加えていますが、実際には1行に圧縮して提供します）

```javascript
javascript:(function(){
    // サーバに配置したJSのドメイン
    // 変数名は、元のページのJavaScriptの変数と競合しないようにプレフィックスをつけます
    BML_D='playcatchbox.com';

    // サーバに配置したJSを読み込むためのscriptタグ
    BML_SCRIPT=document.createElement('script');
    BML_SCRIPT.type='text/javascript';
    BML_SCRIPT.src='//'+BML_D+'/b/b.js';

    // body部の最後にscriptタグを追加
    document.getElementsByTagName('body')[0].appendChild(BML_SCRIPT)
})();
```

これで、閲覧中のページにサーバ側に配置したJavaScriptを読み込ませる処理ができました。
次に、サーバ側に配置したJavaScriptです。

以下のようなフォルダ構成でファイルを用意します。

```
htdocs
    ・ b < JavaScriptを格納するフォルダ >
        ・ b.js <処理内容が記述されたJavaScriptファイル>
        ・ lib.all.js
        ・ lib.js
```

lib.all.jsには [jQueryの最新版](http://jquery.com/) と [jQuery postMessage](http://benalman.com/projects/jquery-postmessage-plugin/) の両方を記述します。
lib.jsには jQuery postMessageのみを記述します。

その際、jQuery postMessageに、jQuery 1.9系以降で廃止になった $.browser 関数が使われているため、これを置き換えます。

```javascript:lib.js
    // has_postMessage = window[postMessage] && !$.browser.opera;
    has_postMessage = window[postMessage] && !window.opera;
```

ブックマークレットの処理内容が書かれた r.js を見ていきます。

```javascript:b.js
(function() {
    // JQueryの最低バージョン
    var v = "1.3.2";

    if (window.jQuery === undefined || window.jQuery.fn.jquery < v) {
        // jQueryが元のページで使用されていなかったら読み込んで処理を実行
        BML_LOAD_LIB('lib.all.js', function() {
            BML_BOOKMARKLET();
        });

    } else if (false === jQuery.isFunction(jQuery.receiveMessage)) {
        // jQuery postMessageが元のページで使用されていなかったら読み込んで処理を実行
        BML_LOAD_LIB('lib.js', function() {
            BML_BOOKMARKLET();
        });

    } else {
        // ライブラリがすべて元のページで読み込まれていたら処理を実行
        BML_BOOKMARKLET();
    }

    function BML_LOAD_LIB(BML_LIB_FILENAME, BML_CALLBACK) {
        var BML_IS_DONE = false;

        // ライブラリの読み込み
        var BML_LIB = document.createElement("script");
        BML_LIB.src = '//' + BML_D + '/b/' + BML_LIB_FILENAME;
        BML_LIB.onload = BML_LIB.onreadystatechange = function() {
            if (!BML_IS_DONE && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
                // ライブラリが正しく読み込まれたら処理を実行
                BML_IS_DONE = true;
                BML_CALLBACK();
            }
        };
        document.getElementsByTagName("head")[0].appendChild(BML_LIB);
    }

    function BML_BOOKMARKLET() {
        (window.myBookmarklet = function($) {
            if ($("iframe#bookmarklet-overlay").length > 0) {
                return;
            }

            // 読み込むiframeタグを生成
            var $BML_IFRAME = $('<iframe/>');
            $BML_IFRAME.attr({
                'allowtransparency' : 'true',
                'scrolling' : 'no',
                'id' : 'bookmarklet-overlay',
                'name' : 'bookmarklet-overlay',
                'src' : '//' + BML_D + '/bookmarklet/' + encodeURIComponent(location.href)
            }).css({
                'border' : 'none',
                'height' : '100%',
                'width' : '100%',
                'position' : 'fixed',
                'z-index' : 2147483647,
                'top' : 0,
                'left' : 0,
                'display' : 'block'
            });

            $('body').append($BML_IFRAME);

            // iframe側からpostMessageで'close'のメッセージを受け取ったら、追加したiframeを削除
            $.receiveMessage(function(e) {
                if (e.data === 'close') {
                    $BML_IFRAME.remove();
                }
            });
        })(jQuery);
    }
})();

```

最後に、iframe内に表示されるページ (上記の例であれば http:// [ドメイン] /bookmarklet/ [読み込み元のURL] )から、自身を削除するメッセージを送信するための記述を見ていきます。

これは上記のサンプルコードが、元のページに覆いかぶさるようにiframeが配置するため、そのiframeが表示されている間は、その下に隠れる元のページを操作することができなくなります。

iframe内に表示されたページで処理を終えた後は、上に覆いかぶさったiframeを取り除いてあげる必要がありますが、iframe内に表示されるページと、iframeが追加されたページ(元のページ)はドメインが異なるため、このままでは、iframeを削除するための処理を、iframe内に表示されたページ側から行うことはできません。(クロスドメイン問題)

そこで、jQuery postMessage pluginを使用して、iframe内に表示させるページから、元のページへmessageを送り、そのmessageを受け取った元のページ側に読み込まれたJSファイルにより、iframeを除去します。

```html:bookmarklet/index.html
<script type="text/javascript">
    function close {
        // 'close'のメッセージを送信
        $.postMessage('close', 'http:// [元のページのURL]', parent);
    }
</script>
```

----

(現時点の課題)
* このブックマークレットは、外部JSの読み込みを許可していないサイト(Facebookなど)では作動しないです。