Ruby 的ブロック
昨日の NEWZingo の話からの発展です。
大した意味は無いんですが、News cloud を表示した時に、自動的に最初のタグ (タグクラウドの意味でのタグです) にフォーカスを置くようにしたいと思ったんです。で、探すと focus というメソッドが見つかりました。
さらに、最初のタグではなくて、最初の最も大きなタグにフォーカスを置くようにしてみよう、と思い付きました。こんなコードになりました:
find_biggest: function () { var biggest = '0'; var tags = {}; var links = document.all.tags('A'); for (var i = 0; i < links.length; i++) // タグのクラス名は "tag0" .. "tag7" となっている。その数字を得る if (links[i].className.match(/^tag(\d+)$/)) { var num = RegExp.$1; if (tags[num]) continue; // タグクラウド全体をキャッシュする必要は無いので else { tags[num] = links[i]; // 最大値を更新 if (parseInt(num) > parseInt(biggest)) biggest = num; } } return tags[biggest]; }
ハッシュ tags は、例えばこんな感じになります:
tags[2]: http://newzingo.com/US/tag/adobe tags[0]: http://newzingo.com/US/tag/afghanistan tags[5]: http://newzingo.com/US/tag/bird tags[3]: http://newzingo.com/US/tag/coast tags[7]: http://newzingo.com/US/tag/sharon tags[4]: http://newzingo.com/US/tag/stars
タグの各サイズにつき、最初に出くわしたアンカーが格納されているわけです。そして、変数 biggest は最も大きいサイズの値を持つようになっていますので、このメソッドは tags[7] を返します。したがって、呼び出し元でそのまま focus() を適用すれば期待の動作になります。
ここで、最小のタグを返すメソッドも作りたい、と考えるのが人情ですよね。
ところが、書こうとすると、重複する部分があまりにも多いことに気付きます。変更が必要なのはタグのサイズの比較部分だけなんです。
そこで、一般化できないか考えてみました。このようになりました:
find_biggest: function () { return this.find_tag(function (a, b){ return a > b; }); }, find_smallest: function () { return this.find_tag(function (a, b){ return a < b; }); }, find_tag: function (cmp) { var scale = '0'; var tags = {}; var links = document.all.tags('A'); for (var i = 0; i < links.length; i++) if (links[i].className.match(/^tag(\d+)$/)) { var num = RegExp.$1; if (tags[num]) continue; else { tags[num] = links[i]; if (cmp(parseInt(num), parseInt(scale))) scale = num; } } return tags[scale]; }
find_tag の中身は、前の find_biggest と殆ど同じです。違うのは変数名と、比較部分を関数呼び出しとしている点だけです。Ruby の、ブロックをメソッドの引数として取る機能と同様のものを、無名関数で実現できるという一例でした。