2010年12月20日月曜日

ブックマークレットってすごい可能性を持っている と思う

ブックマークレット
Wikipedia 
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

ブックマークレットって実は最近まで注目していなかったのですが、フォームへの半自動入力で使えないかと、知り合いに頼まれて少し調べてみました。

ブックマークレットはブラウザのURL入力欄に書ける1行のjavascriptをブックマークしたものです。たった1行ですが、javascriptなのでDOM操作や、ajax通信などもできます。DOM操作ができるということは、ボタンを表示したり、scriptタグを生成してjavascriptのライブラリを読み込んだり、linkタグを生成してスタイルシートを読み込んだりもできるわけです。
「自分が管理していないが、よく利用しているちょっと不便なサイト」で、ブラウザ上にあるコンテンツを利用していろいろなことができます。

  • フォームの入力を自動で行う
  • 入力のインターフェースを変えたり、入力のチェックを行う
  • 自分の見やすいように表示を並べ替える
  • 他のサイトの情報と合わせて加工して表示する

「javascriptを使えばそんなのできるの当たり前じゃん」と思われますが、自分の管理しているわけでないwebサイトのカスタマイズができちゃうわけです。

サンプルとして、pukiwikiのページの編集で、編集中文書の編集前との差分をExtJSのWindowで表示するブックマークレットを作成してみました。これは標準のpukiwikiにはない機能です。

サンプルの使い方

  • 1.まず下記のURL?をブックマーク(IEはお気に入り)に登録します。名前はなんでもいいです。(たとえば「pukiwiki Diff」)
javascript:(function(){var%20e=document.createElement('script');e.charset='utf-8';e.src='http://www123.ddo.jp/tools/edit00.js';document.body.appendChild(e);})()
  • 2.いつも使っているpukiwikiのサイトのページを編集中に、登録したブックマーク(お気に入り)を選びます。
  • 3.すると、「ページの更新」ボタンの横に「Diff」ボタンが表示されます。
  • 4.「Diff」ボタンを押すと、テキストエリアに何も変更を加えていない場合、「変更箇所はありません」とメッセージが表示され、変更があった場合、次のように変更点が表示されます
    ※このブックマークレットサンプルは、本家のPukiWikiや、派生版のPukiWikiPlus等で、使用可能です。utf-8、EUCのどちらで構築されていても動くと思います。

      ブックマークレットとスクリプトの解説

      見やすさのために改行とインデントを入れると以下のようになります
       javascript:(
           function(){
               var e=document.createElement('script');
               e.charset='utf-8';
               e.src='http://www123.ddo.jp/tools/edit00.js';
               document.body.appendChild(e);
           }
       )()
      見た通り、これでwww123.ddo.jpからedit.jsというスクリプトを読み込んで実行しているわけです。

      読み込んだedit00.jsは下記のような動作をします。

      • ExtJS 3.3.1を動かすために必要なライブラリやスタイルシートを読み込む
      • 'write'という名前のサブミットボタン(ページの更新)があることを確認する
      • 'wirte'ボタンの左に、押されたらdo_diffを実行するような「Diff」ボタンを生成する。
      • do_diffはフォーム中の'original'と'msg'という、フィールドの値を比べ結果を表示する

      edit00.jsのソースは最後に添付します。まあ、http://www123.ddo.jp/tools/edit00.js をDLしソースを確認すればその通りなのですが。

      セキュリティについて

      • http://www123.ddo.jp/tools/edit00.js を信じない方は賢明です。サイトが誰かに乗っ取られ、悪意のあるロジックを混入される恐れはいつでもあります。edit00.jsを信頼できるサイトにコピーして使用いただいて結構です。GPL ver3としますので、適当に改変してください。便利な機能を追加したなら公開していただくとありがたいです。
      • edit00.dsで、cacheflyのサイトからExtJSのライブラリを読み込んでいます。より安全に使いたければ、上記と同様に自分の制御下のサーバに入れることを検討してください。
      edit00.js ソースコード

          // edit.js v 00 -- pukiwikiの編集を支援
          // Bookmarklet example
          //   javascript:(function(){var%20e=document.createElement('script');e.charset='utf-8';e.src='http://www123.ddo.jp/tools/edit00.js?';document.body.appendChild(e);})()
          // Copyright(c) 2010 mashiki
          // License: GPL version 3
          (function(){
              var cf='http://extjs.cachefly.net/ext-3.3.1/';
              addEl('style', {type:'text/css',innerHTML:'td {background-color:transparent}'});
              addEl('link',{rel:'stylesheet',type:'text/css',href:cf+'resources/css/ext-all.css'});
              addEl('script',{src:cf+'adapter/ext/ext-base.js'});
              addEl('script',{src:cf+'ext-all.js'});
              function addEl(tag, cfg) {
                  var e=document.createElement(tag);
                  for (var key in cfg) e[key]=cfg[key];
                  document.body.appendChild(e);
              }
              var id=setInterval(function(){
                  if(window.Ext && Ext.MessageBox){
                      clearInterval(id);
                      var pv = document.getElementsByName('write');
                      if (pv[0]) {
                          pv = pv[0];
                      } else {
                          Ext.Msg.alert(
                              'pukiwiki編集支援機能',
                              '「ページの更新」ボタンが見つかりません。
      '
                              +'この機能はpukiwikiの編集画面でご使用ください'
                          );
                          return;
                      }
                      var df = document.createElement("input");
                      Ext.apply(df, {type:"button",value:"Diff",onclick:diff_click});
                      Ext.get(df).insertBefore(pv);
                  }
              },100);
            
              function diff_click() {
                  var msg = document.getElementsByName('msg');
                  var org = document.getElementsByName('original');
                  if (msg[0] && org[0]) {
                      msg = msg[0].value;
                      org = org[0].value;
                  } else {
                      Ext.Msg.alert(
                          'pukiwiki編集支援機能',
                          '"msg"または"original" フィールドが見つかりません。
      '
                          +'この機能はpukiwikiの編集画面でご使用ください'
                      );
                      return;
                  }
                  if (msg===org) {
                      Ext.Msg.alert('pukiwiki編集支援機能', '変更箇所はありません');
                  } else {
                      var res = diff(
                          org.replace(/^\n+|\n+$/g,'').split("\n"),
                          msg.replace(/^\n+|\n+$/g,'').split("\n")
                      );
                      // 結果加工
                      var map = {
                          '-':{edit:'-',style:'background-color:#FFDDDD;'},
                          '+':{edit:'+',style:'background-color:#DDDDFF;'},
                          '=':{edit:' ',style:'background-color:#FFFFFF;'}
                      }
                      var table = ['
      '];                 for (var i=0; d=res[i]; ++i) {                                        table.push(""                             + "");                 }                 table.push('
      "+map[d.edit].edit+""
                                  + d.arr[d.line].replace(//g,">")
                                  + "
      ');
                      var win = new Ext.Window({
                          title:'pukiwiki編集支援機能',
                          html: table.join(''),
                          width:400,
                          height:400,
                          autoScroll:true,
                          closeAction:'close',
                          plain: true,
                          buttons: [{
                              text: 'Close',
                              handler: function(){
                                  win.close();
                              }
                          }]
                      });
                      win.show();
                  }
              }

              function diff(arr1, arr2, rev) {
                  var len1=arr1.length,
                      len2=arr2.length;
                  // len1 <= len2でなければひっくり返す
                  if (!rev && len1>len2)
                      return diff(arr2, arr1, true);
                  // 変数宣言及び配列初期化
                  var k, p,
                      offset=len1+1,
                      delta =len2-len1,
                      fp=[], ed=[];
                  for (p=0; p
                      fp[p] = -1;
                      ed[p] = [];
                  }
                  // メインの処理
                  for (p=0; fp[delta + offset] != len2; p++) {
                      for(k = -p       ; k <  delta; ++k) snake(k);
                      for(k = delta + p; k >= delta; --k) snake(k);
                  }
                  return ed[delta + offset];

                  // snake
                  function snake(k) {
                      var x, y, e0, o,
                          y1=fp[k-1+offset],
                          y2=fp[k+1+offset];
                      if (y1>=y2) { // 経路選択
                          y = y1+1;
                          x = y-k;
                          e0 = ed[k-1+offset];
                          o = {edit:rev?'-':'+',arr:arr2, line:y-1}
                      } else {
                          y = y2;
                          x = y-k;
                          e0 = ed[k+1+offset];
                          o = {edit:rev?'+':'-',arr:arr1, line:x-1}
                      }
                      // 選択した経路を保存
                      if (o.line>=0) ed[k+offset] = e0.concat(o);

                      var max = len1-x>len2-y?len1-x:len2-y;
                      for (var i=0; i
                          // 経路追加
                          ed[k+offset].push({edit:'=', arr:arr1, line:x+i});
                      }
                      fp[k + offset] = y+i;
                  }
              }

          })();



      1 件のコメント:

      1. やってみたいこと。
        ・pukiwikiのTrackerのブラウザ上でのソート/絞り込み
        ・ハングル入力とGoogle翻訳の完全な結合

        とか

        返信削除