Home > JavaScript

JavaScript Archive

連続するページの JSONP を取得して最後に Callback 関数を呼ぶライブラリ

拙作のツール、「Twitter Stream Viewer」や「Twitter Follow Manager(作成中)」で、Twitter API から複数ページを取得するロジックがあるんですが、そのロジックがスマートではないのでライブラリを作ってみました。
使ってみたら結構便利だったので公開してみます。

jsonPageLoader

使い方はこんな感じです。

var loader = new jsonPageLoader({
  url     : 'http://twitter.com/statuses/friends.json',
  start   : 1,
  end     : function(result) { return result.length > 0; },
  onStart : function(page) { alert(page + 'ページ目の取得開始'); },
  onEnd   : function(result, page) { alert(page + 'ページ目の取得完了'); },
  callback: function(results, start, end) { alert('全ページの取得完了'); }
});

オブジェクトを new してあげて、引数にハッシュ形式でオプションを渡します。
url にはベースとなる URL を指定します。
start には開始するページ番号を指定します。省略すれば 1 ページ目からになります。
end には終了するページ番号または関数を指定します。必須。
5 と指定すれば 5 ページ目まで取得して処理が終了します。
関数を指定した場合、各ページの取得完了時に結果を引数としてこの関数が呼び出されます。false が返されるまでページ番号を 1 ずつ増やしながら処理が続行されます。上の例の場合、取得結果が 0 件になったところで処理が終了します。
onStart, onEnd は各ページの処理開始・終了時に呼び出される関数を指定します。省略可。
callback は全ページの取得完了時に呼び出される関数を指定します。引数の results はページ毎の結果を格納した配列です。ページ番号と添字が一致しています。

ライブラリのソースは以下で。

Continue reading

短縮 URL を展開する Greasemonkey

きっかけ

Twitter など字数制限があるサイトではよく使われている URL の短縮サービス。
フィッシング詐欺に使われることもあるそうなので注意が必要です。
でもいちいちリンクを踏むたびにプレビューするのも煩わしいと思ったので、短縮 URL を展開表示するグリモンを書いてみました。

今回、Long URL Please というところで TinyURL 等の代表的なものだけでなく、55 種類のサービスに対応した API を公開していたので、それを使わせていただきました。

なお、このグリモンは Firefox には対応していません(理由は後述)。対応しました。上記リンクからプラグインを入れると幸せになれると思います。
Safari で動作確認済。Opera でも多分動きます。Opera でも動作確認しました。oAutoPagerize 対応。

インストール

こちらからどうぞ。

Short URL Expander for Greasemonkey

ソース

// ==UserScript==
// @name           Short URL Expander
// @namespace      http://www.sukechan.net/
// @description    Expand shortened URLs.
// @include        *
// @version        1.1
// ==/UserScript==

(function() {
  var apiUrl = 'http://www.longurlplease.com/api/v1.1';
  var urls = new Array();
  var shortUrlsPattern = new RegExp("(^http(s?)://(adjix\.com|b23\.ru|bacn\.me|bit\.ly|bloat\.me|budurl\.com|cli\.gs|clipurl\.us|dwarfurl\.com|ff\.im|fff\.to|href\.in|idek\.net|is\.gd|korta\.nu|lin\.cr|ln\-s\.net|loopt\.us|merky\.de|moourl\.com|nanourl\.se|ow\.ly|peaurl\.com|ping\.fm|piurl\.com|pnt\.me|poprl\.com|reallytinyurl\.com|rubyurl\.com|short\.ie|short\.to|smallr\.com|sn\.vc|snipr\.com|snipurl\.com|snurl\.com|tiny\.cc|tinyurl\.com|tr\.im|tra\.kz|twurl\.cc|twurl\.nl|u\.mavrev\.com|ur1\.ca|url\.az|url\.ie|urlx\.ie|w34\.us|xrl\.us|yep\.it|zi\.ma|zurl\.ws)/[a-zA-Z0-9_-]*)|((^http(s?)://[a-zA-Z0-9_-]+\.notlong\.com)|(^http(s?)://[a-zA-Z0-9_-]+\.qlnk\.net)|(^http(s?)://chilp\.it/[?][a-zA-Z0-9_-]+))[/]?$");

  // JSONP Callback Function
  function createNamespace() {
    window.shortURLExpanderUserJs = {
      json_callback: function(r) {
        for(url in r) {
          for(var i = 0, l = this.items.length; i < l; i++) {
            var item = this.items[i];
            if(item.href == url) {
              item.href = r[url];
              if(r[url].length <= 40) {
                item.textContent = r[url];
              } else {
                item.textContent = r[url].substr(0, 30) + '...';
              }
            }
          }
        }
      },
      items: []
    };
  }

  var f = function() {
    var x = document.evaluate('//a', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for(var i = 0; i < x.snapshotLength; i++) {
      var url = x.snapshotItem(i).href;
      if(url.match(shortUrlsPattern)) {
        if((urls.length == 0) && (typeof GM_xmlhttpRequest == 'undefined')) {
          createNamespace();
        } else {
          var items = new Array();
        }
        urls.push(url);
        if(typeof GM_xmlhttpRequest == 'undefined') {
          shortURLExpanderUserJs.items.push(x.snapshotItem(i));
        } else {
          items.push(x.snapshotItem(i));
        }
      }
    }
    if(urls.length > 0) {
      var requestUrl = apiUrl + '?q=' + urls.join('&q=');
      if(typeof GM_xmlhttpRequest == 'undefined') {
        jsonp(requestUrl);
      } else {
        GM_xmlhttpRequest({
          method: 'GET',
          url: requestUrl,
          onload: function(x) {
            var r = eval('(' + x.responseText + ')');
            for(var url in r) {
              for(var i = 0, l = items.length; i < l; i++) {
                var item = items[i];
                if(item.href == url) {
                  item.href = r[url];
                  if(r[url].length <= 40) {
                    item.textContent = r[url];
                  } else {
                    item.textContent = r[url].substr(0, 30) + '...';
                  }
                }
              }
            }
          }
        });
      }
    }
  }
  function jsonp(url) {
    var s = document.createElement('script');
    s.src = url + '&callback=shortURLExpanderUserJs.json_callback';
    s.charset = 'UTF-8';
    document.body.appendChild(s);
  }

  f();
  addFilter(f);
  function addFilter(filter, i) {
    i = i || 4;
    if(window.AutoPagerize && window.AutoPagerize.addFilter) {
      window.AutoPagerize.addFilter(filter);
    } else if(i > 1) {
      setTimeout(arguments.callee, 1000, filter, i -1);
    }
  }
})();

Firefox に対応していない理由

このグリモンでは JSONP を使って API を実行しており、Callback 関数をグローバルに定義する必要がありました。
ただ、Firefox の Greasemonkey からグローバルの変数にアクセスする方法が無いらしいので、対応できませんでした><。
Firefox はプラグインがあるので、まあ要らないんですけどね。

(追記@20090315)
version 1.1 で Firefox / Greasemonkey に対応しました。
それと Firefox / Greasemonkey の場合、グローバル変数には unsafeWindow を使えばアクセスできます。

Greasemonkey で JSONP の Callback 関数を呼び出す方法

とあるグリモンを作っていて、JSONP をサポートしている API にリクエストを投げて、Callback 関数で結果を受け取りたい。例えば以下のような URL。

http://example.com/api?data=hoge&callback=fuga

無名関数内でこの JSONP を呼び出しても fuga は当然実行されないので、グローバルにオブジェクトを 1 つ作り(名前空間的なものです)、その中に Callback 関数を定義してみました。
Safari ではちゃんとグローバルになるんですが、Firefox ではグローバルにならず。
原因が分からずいろいろ調べてみたところ、Greasemonkey として実行される場合、前後に (function() { ~ })(); が付くらしい(Safari では付かない)。

次に、Callback パラメータ内にイベントを作成して、Callback 時にイベントを発生させられないかと思って試してみましたが、こちらは Safari で結果を受け取る方法が分からず断念。

(function() {

  // Callback
  document.addEventListener("onJSONPLoad", function(request) {
    alert('callback');
  });

  // JSONP 実行
  var s = document.createElement('script');
  s.type = 'text/javascript';
  s.charset = 'UTF-8';
  s.src = 'http://example.com/api?data=hoge&callback=(function(result){var%20c=document.createEvent(%22HTMLEvents%22)%3bc.initEvent(%22onJSONPLoad%22,true,true)%3bdocument.dispatchEvent(c)%3b})';
  document.body.appendChild(s);
})();

仕方ないので、script タグを動的に生成して、グローバルに 1 つ名前空間を作り、その中に関数を定義。

(function() {

  // JSONP 実行
  var s = document.createElement('script');
  s.src = param.url + '&callback=my_ns.json_callback';
  s.charset = 'UTF-8';
  document.body.appendChild(s);

  // Callback
  var s = document.createElement('script');
  s.type = 'text/javascript';
  s.charset = 'UTF-8';
  var t = document.createTextNode("var my_ns = { callback: function(r) { alert('callback'); } }");
  s.appendChild(t);
  document.body.appendChild(s);
})();

とりあえずこれで動きますが、もっとスマートな方法ありそうですね・・・。
JavaScript に対する理解がまだまだ浅いことを実感しました><

参考リンク

マク語に変換する bookmarklet

先日 Twitter で

マク語が局所的に話題になっていたので、マク語に変換する bookmarklet を作りました。まだ数は少ないです。

マク語に変換するでよ

ソース

(function() {
  var s = {
    'です。':'でよ。',
    'ですよ。':'てすよ。',
    'ここ':'こつら',
    'こちら':'こつら',
    'ありがとう':'サンクス・ァロツト',
    'これ':'こり',
    'みなさん':'みんなさん',
    'して':'すて',
    'した':'すた',
    'くれた':'くりーた',
    'してる':'してーる',
    'だった':'だた',
    'やって':'ヤッて',
    'ちょっと':'ちょと',
    'ください':'くだせぃ',
    'まったく':'またーく',
    '!':'あひーっ!',
    'すごい':'すげぇ',
    'いい':'ぃぃ',
    'そういえば':'そうゆえば',
    'あたらしい':'あたらしぃ',
    'もらった':'もらつた',
    'する':'ずる',
    'ない':'亡い',
    '無い':'亡い',
    'Apple':'アプール',
    'Mac':'マっク',
    'マック':'マっク',
    'グラフィック':'グラヒック',
    'アップル':'アプール',
    'デザイン':'デザィン',
    'スタンダード':'スタンダド',
    'サイト':'サィト',
    'サイズ':'サィズ',
    'ドラッグ':'ドラツグ',
    'ドロップ':'ドロツプ',
    'メール':'メィル',
    'サムネイル':'サムネェル',
    'サムネール':'サムネェル'
  }
  for(var i in s) {
    var r = new RegExp(i, 'g');
    document.body.innerHTML = document.body.innerHTML.replace(r, s[i]);
  }
})();

これで

どこでもマク語になるでよ、あひーっ!

JavaScript 第 5 版 9 章まとめ

だいぶ間が空いてしまいましたが、サイ本の続きです。
今回は 9 章。自分がちゃんと身につけたいと思っていたプロトタイプやコンストラクタの内容。
ちゃんと理解して実践に活かしたい。

9 章 クラスとコンストラクタとプロトタイプ

9.1 コンストラクタ

  • new 演算子と一緒に使うための関数をコンストラクタ関数という。
  • コンストラクタ関数が呼び出されるとそのオブジェクトが this キーワードの値になる。

9.2 プロトタイプと継承

  • メソッドとして関数を呼び出すと、関数を呼び出す時に使われたオブジェクトが this キーワードの値になる。
  • すべての JavaScript オブジェクトには、プロトタイプオブジェクトというオブジェクトへの参照が含まれ、プロトタイプオブジェクトからプロパティを継承する。
  • new 演算子を使ってオブジェクトを生成すると、そのオブジェクトに対してプロトタイプを設定する。
  • 継承はプロパティの値を探す処理の一部として自動的に行われる。
  • プロパティはプロトタイプからコピーされるのではなく、オブジェクトのプロパティであるかのように見える。
  • プロトタイプオブジェクトを使用することで、たくさんのオブジェクトがプロパティを継承するので、メモリを節約できる。
  • また、プロパティの値を探す処理でプロトタイプを参照するので、オブジェクトを生成した後にプロトタイプを追加した場合でも継承できる。
  • よって、そのクラスのすべてのオブジェクトに対して同一のプロパティを定義する場合(メソッドなど)に限って使用するのが適切である。

// コンストラクタ関数
function Rectangle(width, height) {
  this.width = width;
  this.height = height;
}

// プロトタイプオブジェクトを生成
Rectangle.prototype.area = function() {
  return this.width * this.height;
}

var r = new Rectangle(2, 3);
r.width;  // 2
r.area();  // 6 が返る。プロトタイプオブジェクトから継承されている。
for(var o in r) {
  alert(o);  // width, height, area。for/in でも調べられる。
}
r.hasOwnProperty("width");  // r 直属のプロパティなので true
r.hasOwnProperty("area");  // r から継承されたプロパティなので false

9.2.1 継承プロパティへのアクセス

  • 1 つのプロトタイププロパティが複数のオブジェクトによって継承される。よってプロパティの値の継承が有効なのは読み出す時に限られる。
  • プロトタイプオブジェクトのプロパティに値を書き込もうとすると、元のオブジェクトに新しいプロパティが生成され、以降はプロトタイプからはそのプロパティは継承しない。

9.2.2 組み込み型の拡張

  • String や Date などの組み込みクラスのプロトタイプオブジェクトにも値を代入できる。
  • Object.prototype にはプロパティを追加してはいけない。追加するとすべての JavaScript オブジェクトで、追加したプロパティやメソッドが for/in ループで調べられるようになるため、オブジェクトを連想配列として利用するとうまく動かなくなる。

9.3 JavaScript の「クラス」

  • JavaScript はオブジェクトと呼ばれるデータ型をサポートしているが、クラスという正式な概念はない(JavaScript 2 ではサポートされるとのこと)。

9.3.1 インスタンスプロパティ

  • オブジェクト毎にそれぞれ異なるプロパティのこと。
  • JavaScript のオブジェクトプロパティは、デフォルトでインスタンスプロパティになる。

9.3.2 インスタンスメソッド

  • 特定のオブジェクトを介して呼び出されるメソッドのこと。
  • ただし、必ずしもすべてのオブジェクトが自分専用のメソッドを保持しているわけではなく、クラスのすべてのインスタンスで共有される。
  • JavaScript の場合、コンストラクタのプロトタイプオブジェクトのプロパティに関数値を設定することで、クラスのインスタンスメソッドを定義する。

9.3.3 クラスプロパティ

  • クラスのインスタンスではなくクラスそのものに関連づけられたプロパティのこと。
  • JavaScript ではコンストラクタ関数自身のプロパティを定義することでクラスプロパティになる。

// コンストラクタ
function Rectangle(width, height) {
  this.width = width;
  this.height = height;
}

// クラスプロパティ
Rectangle.UNIT = new Rectangle(1, 1);  // 関数はオブジェクトなのでプロパティを定義できる

9.3.4 クラスメソッド

  • クラスのインスタンスではなくクラス自身に関連したメソッドのこと。
  • JavaScript ではコンストラクタ関数自身に関数を定義する。

// コンストラクタ
function Rectangle(width, height) {
  this.width = width;
  this.height = height;
}

// クラスメソッド
Rectangle.max = function(rect1, rect2) {
  if(rect1.width * rect1.height > rect2.width * rect2.height) {
    return rect1;
  } else {
    return rect2;
  }
}

var r1 = new Rectangle(5, 6);
var r2 = new Rectangle(7, 4);
Rectangle.max(r1, r2);

9.3.7 プライベートメンバ

  • JavaScript ではクロージャを使うことでプロパティをカプセル化できる。

function ImmutableRectangle(width, height) {
  // オブジェクトに対してアクセッサーメソッドを定義する
  this.getWidth = function() { return width; }
  this.getHeight = function() { return height; }
}
var rect = new ImmutableRectangle(2, 3);
rect.getWidth();  // 2
rect.width;  // undefined

9.5 スーパークラスとサブクラス

  • JavaScript の場合、Object クラスはすべての組み込むクラスのスーパークラスであり、その他のクラスは Object クラスのサブクラスと見なせる。
  • プロトタイプオブジェクトは Object.prototype からプロパティを継承する。

9.5.1 コンストラクタチェーン

  • 新しいオブジェクトに対してスーパークラスのコンストラクタを呼び出す処理をコンストラクタチェーンという。
  • 1 レベルのサブクラスしか持たない場合は、サブクラスのプロトタイプオブジェクトに superclass という名前のプロパティを追加することで対応できる。

9.5.2 オーバーライドされたメソッドの呼び出し

  • サブクラスでスーパークラスと同じ名前のメソッドを定義すると、サブクラスはそのメソッドをオーバーライド(上書き)する。
  • オーバーライドしたメソッドから元のメソッドを呼び出すために、メソッドチェーンという方法を利用する。

// スーパークラス Rectangle のコンストラクタ
function Rectangle(width, height) {
  this.width = width;
  this.height = height;
}
// スーパークラスに toString メソッドを定義する
Rectangle.prototype.toString = function() {
  return '[' + this.width + ', ' + this.height + ']';
}

// サブクラス PositionRectangle にも toString メソッドを定義する
PositionRectangle.prototype.toString = function() {
  return "(" + this.x + ", " + this.y + ") " + // このクラスのプロパティ
         Rectangle.prototype.toString.apply(this);  // スーパークラスの toString() を呼び出し
}

 Page 1 of 3  1  2  3 »

ホーム > JavaScript

Search
Feeds
Meta
あわせて読みたい
あわせて読みたいブログパーツ
Others...
フィードメーター - sukechan.net
track feed

Return to page top